《从零开始学架构》读书笔记
2022-07-30 18:28:44 0 举报
AI智能生成
《从零开始学架构》的读书笔记,你将获得:1、理解架构设计的本质和目的;2、掌握高性能和高可用架构模式;3、从编程到架构,实现思维跃迁
作者其他创作
大纲/内容
开篇词
为什么技术能力强,业务能力不错,但架构设计一般??
原因总结<br>
架构设计 vs 程序设计
架构设计的关键思维是:<b><font color="#ff0000">判断和取舍(T</font></b><font color="#ff0000"><b>rade-Off</b></font><font color="#ff0000" style="font-weight: bold;">)</font><font color="#000000" style="">,结果没有标准答案</font>
程序设计的关键思维是:<font color="#ff0000" style="font-weight: bold;">逻辑和实现</font><font color="#000000" style="">,结果非常明确</font>
架构设计:目前没有体系化的培训和训练机制
程序员对架构设计的理解存在很多<font color="#ff0000"><b>误区</b></font>
要成为架构师必须要有很强的技术天分
架构师必须有很强的创造力
架构设计必须要高大上才能体现架构师能力
架构一定要具备高可用、高性能
架构师需要形成自己的一套<font color="#ff0000"><b>架构设计方法论</b></font>
基础架构
架构到底是指什么?
要想准确地理解架构的定义,关键就在于把<font color="#ff0000">如下三组</font>容易混淆的概念梳理清楚
系统与子系统
系统
系统泛指由<font color="#000000" style="">一群</font><font color="#ff0000"><b>有</b></font><font color="#ff0000" style="font-weight: bold;">关联的个体</font><font color="#000000">组成</font>,根据某种<font color="#ff0000"><b>规则</b></font>运作,能完成个别元件不能单独完成的工作的<font color="#ff0000"><b>群体</b></font>
它的意思是“总体”、“整体”或“联盟”
系统能力不是个体能力之和,而是产生了<font color="#ff0000"><b>新的能力</b></font>
子系统
子系统的定义和系统定义是一样的
只是<font color="#ff0000">观察的角度有差异</font>,一个系统可能是另外一个更大系统的子系统
一个系统的架构,只包括顶层这一个层级的架构,而不包括下属子系统层级的架构,即所谓的<font color="#ff0000"><b>顶层设计</b></font>
模块与组件
模块和组件都是系统的组成部分,只是从不同的角度拆分系统而已
模块
从<font color="#ff0000"><b>业务逻辑</b></font>的角度来拆分系统
主要目的:<font color="#ff0000"><b>职责分离</b></font>
组件
从<font color="#ff0000"><b>物理部署</b></font>的角度来拆分系统
主要目的:<font color="#ff0000"><b>单元复用</b></font>
架构师:首先从<font color="#ff0000"><b>业务逻辑</b></font>的角度把系统拆分成一个个<font color="#ff0000"><b>模块</b></font>角色,其次从<font color="#ff0000"><b>物理部署</b></font>的角度把系统拆分成<font color="#ff0000"><b>组件</b></font>角色
框架与架构
框架是一整套<font color="#ff0000"><b>开发规范</b></font>
架构是某一套开发规范下的<font color="#ff0000"><b>业务</b></font>的<font color="#ff0000"><b>具体落地方案</b></font>,包括各个模块之间的组合关系以及它们协同起来完成功能的运作规则
“<font color="#ff0000"><b>4R架构</b></font>”方法论
软件架构指软件系统的顶层(Rank)结构,它定义了系统由哪些角色(Role)组成,角色之间的关系(Relation)和运作规则(Rule)
Rank:层级
软件架构是分层的,对应“系统”和“子系统”的分层关系
通常情况下,我们只需要关注某一层的架构,最多展示相邻两层的架构,而不需要把每一层的架构全部糅杂在一起
无论是架构设计还是画架构图,都应该采取“自顶向下,逐步细化”的方式
Role:角色
软件系统包含哪些角色,每个角色都会负责系统的一部分功能
Relation:关系
软件系统的角色之间的关系,对应到架构图中其实就是连接线
Rule:规则
软件系统角色之间如何协作来完成系统功能
架构设计的历史背景
机器语言(1940 年之前)
直接使用二进制码 0 和 1 来表示机器可以识别的指令和数据
太难写、太难读、太难改!
汇编语言(20 世纪 40 年代)
用<font color="#ff0000">助记符</font>代替机器指令的<font color="#ff0000">操作码</font>,用<font color="#ff0000">地址符号</font>代替指令或操作数的<font color="#ff0000">地址</font>
本质上还是面向机器的
不同 CPU 的汇编指令和结构是不同的,相同的逻辑汇编代码不相同
高级语言(20 世纪 50 年代)
Fortran、LISP、Cobol等等
让程序员不需要关注机器底层的低级结构和逻辑,而只要关注具体的问题和业务即可
通过<font color="#ff0000"><b>编译程序</b></font>的处理,高级语言可以被编译为适合不同 CPU 指令的机器语言
第一次软件危机与<font color="#ff0000"><b>结构化程序设计</b></font>(20 世纪 60 年代~20 世纪 70 年代)
第一次软件危机的根源在于<font color="#ff0000"><b>软件的“逻辑”变得非常复杂</b></font>
软件质量低下、项目无法如期完成、项目严重超支等
因为软件而导致的重大事故时有发生
结构化程序设计方法
抛弃 goto 语句
自顶向下、逐步细化、模块化
将软件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度
结构化程序设计(面向过程):C语言
第二次软件危机与<font color="#ff0000"><b>面向对象</b></font>(20 世纪 80 年代)
第二次软件危机主要体现在<font color="#ff0000"><b>软件的“扩展”变得非常复杂</b></font>
结构化程序设计虽然能够缓解软件逻辑的复杂性,但是对于业务变化带来的<font color="#ff0000"><b>软件扩展</b></font>却无能为力
面向对象:C++、Java、C#
软件架构的历史背景(从 20 世纪 90 年代开始)
“软件架构”出现的背景并不是整个行业都面临类似相同的问题
“软件架构”也不是为了解决新的软件危机而产生的
只有大公司开发的规模较大的软件系统才会面临软件架构相关的问题
系统规模庞大,内部耦合严重,开发效率低
系统耦合严重,牵一发动全身,后续修改和扩展困难
系统逻辑复杂,容易出问题,出问题后很难排查和修复
结构化编程,创造了“模块”概念
面向对象编程,创造了“对象”概念
软件架构,创造了“组件”概念
架构设计的目的
架构设计的主要目的是为了解决软件系统<b><font color="#ff0000">复杂度</font></b>带来的问题
复杂度来源1:<font color="#ff0000"><b>高性能</b></font>
单机复杂度
按照 <font color="#ff0000"><b>机器硬件 -> 操作系统 -> 软件程序</b></font> 的顺序,性能逐渐<font color="#ff0000"><b>损耗变低</b></font>
进程 vs 线程
进程:操作系统<font color="#ff0000"><b>资源分配</b></font>的最小单位
线程:操作系统<font color="#ff0000"><b>调度</b></font>的最小单位
并发 vs 并行
并发:分时系统,不是真正的<font color="#ff0000"><b>多任务同时</b></font>处理
并行:多核系统,真正的<b><font color="#ff0000">多任务同时</font></b>处理
并行的三种解决方案
SMP:Symmetric Multi-Processor,对称多处理器结构
单服务器的多个CPU对称工作,无主次或从属关系
资源共享,如CPU、内存、I/O等
NUMA:Non-Uniform Memory Access,非一致存储访问结构
利用NUMA技术,把多个CPU组合在一台机器中,而每个CPU可以访问整个系统的内存
访问异地内存的时延远远超过访问本地内存
当CPU数量增加时,系统性能无法线性增加
MPP:Massive Parallel Processing,海量并行处理结构
MPP是由多台SMP服务器通过一定的节点互联网络进行连接,协同工作,完成相同的任务,从用户的角度来看是一个服务器系统
例如:Hadoop
集群复杂度
任务<font color="#ff0000"><b>分配</b></font>
<font color="#000000">每台机器</font><b style=""><font color="#ff0000">可以处理</font></b><font color="#000000">完整的任务</font>,不同的任务分配到不同的机器上执行
复杂度主要体现
增加任务分配器
任务分配器和真正的业务服务器之间的连接管理
分配算法
任务<font color="#ff0000"><b>分解</b></font>
每台机器<font color="#ff0000"><b>无法处理</b></font>完整的任务,需要多台机器协作完成处理
复杂度主要体现
大业务拆分成多个子业务,每个子业务是一个子系统
子系统之间的协作调度处理
复杂度来源2:<b><font color="#ff0000">高可用</font></b>
定义:系统<b><font color="#ff0000">无中断</font></b>地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一
系统的高可用方案,本质上是通过“<font color="#ff0000"><b>冗余</b></font>”来实现
高性能增加机器目的在于“扩展”处理性能
高可用增加机器目的在于“冗余”处理单元
通过冗余增强了可用性,但同时也带来了复杂性,主要体现在如下3点:
计算高可用
计算是无状态的
复杂度主要体现
增加任务分配器
任务分配器和真正的业务服务器之间的连接管理
分配算法
存储高可用
存储是有状态的,存在数据同步问题(<font color="#ff0000"><b>一致性问题</b></font>)
存储高可用的难点不在于如何备份数据,而在于如何减少或者规避<font color="#ff0000"><b>数据不一致对业务造成的影响</b></font>
CAP 定理
高可用状态决策
通过冗余来实现的高可用系统,<font color="#ff0000"><b>状态决策</b></font>本质上就不可能做到完全正确
几种常见的决策方式
独裁式:单点问题
协商式:例如,主备决策,信息交换出问题(主备连接中断)
民主式:例如,投票选举,多数取胜,存在<font color="#ff0000"><b>脑裂问题</b></font><br>
状态决策
对于多个任务分配器节点,如何选出主节点
对于主节点,需要判断业务服务器是否可用
复杂度来源3:<font color="#ff0000"><b>可扩展性</b></font>
定义:系统为了应对<font color="#ff0000"><b>需求变化</b></font>而提供的一种扩展能力,系统<font color="#ff0000"><b>不需要</b></font>或者仅需要<font color="#ff0000"><b>少量修改</b></font>就可以支持
设计<font color="#ff0000"><b>具备良好可扩展性</b></font>的系统,有两个基本条件
<font color="#ff0000"><b>正确</b></font>预测变化
<font color="#ff0000"><b>完美</b></font>应对变化
预测变化的复杂性
不能每个设计点都考虑可扩展性
不能完全不考虑可扩展性
所有的预测都存在出错的可能性
应对变化的复杂性
提炼出“变化层”和“稳定层”
提炼出“抽象层”和“实现层”
<b><font color="#ff0000">1 写 2 抄 3 重构</font></b>原则
1 写:新业务能否活下还不确定,暂不考虑太多可扩展性,直接实现业务
2 抄:业务前景还不明朗,暂不考虑太多可扩展,直接copy代码实现业务
3 重构:业务发展不错(活下来了),考虑重构系统,支持快速扩展
复杂度来源4:<font color="#000000" style="">低成本</font>
低成本 与高性能、高可用是<font color="#ff0000"><b>冲突</b></font>的,是架构设计的<font color="#ff0000"><b>附加约束</b></font>
往往只有<font color="#ff0000"><b>“创新”</b></font>才能达到低成本目标
<font color="#ff0000"><b>小公司</b></font>:基本都是靠<font color="#ff0000"><b>引入</b></font>新技术来达到低成本的目标
各种开源技术
云服务商提供的各种能力
<b><font color="#ff0000">大公司</font></b>:有足够的资源、技术和时间去<font color="#ff0000"><b>创造</b></font>新技术
复杂度来源5:<font color="#000000" style="">规模</font><br>
“量变引起质变”
复杂度体现在
功能越来越多,导致系统复杂度指数级上升
数据越来越多,系统复杂度发生质变
复杂度来源6:<font color="#000000" style="">安全</font><br>
功能安全
功能安全更多地是和具体的<font color="#ff0000"><b>编码相关</b></font>,与架构关系不大
架构安全
传统的架构安全主要依靠<font color="#ff0000"><b>防火墙</b></font>
网络隔离:将网络划分成不同的区域
访问控制:不同区域之间的数据访问策略
互联网的业务具有<font color="#ff0000"><b>海量用户访问</b></font>和<font color="#ff0000"><b>高并发</b></font>的特点,瞬时流量巨大
互联网系统的架构安全目前并<font color="#ff0000"><b>没有太好的方案</b></font>,更多地依靠运营商或者云服务商强大的带宽和流量清洗的能力
架构设计三原则
合适原则:“合适优于业界领先”
简单原则:“简单优于复杂”<br>
在建筑领域,“复杂”代表的是“领先”
在<font color="#000000" style="">软件领域</font>,“复杂”代表的是<font color="#ff0000"><b>“问题”</b></font>
演化原则:“演化优于一步到位”
在建筑领域,“永恒”是主题
在软件领域,<font color="#ff0000"><b>“变化”</b></font>是主题
架构设计第 1 步:识别复杂度
架构的复杂度主要来源于“高性能”“高可用”“可扩展”等几个方面
实际上大部分场景下,复杂度只是其中的某一个,少数情况下包含其中两个
架构设计第 2 步:设计备选方案
架构设计常见的错误
设计最优秀的方案
只做一个方案
方案过于详细
架构师需要设计多个方案
方案的数量以 3 ~ 5 个为最佳
方案的差异要比较明显
方案的技术不要只局限于已经<font color="#ff0000"><b>熟悉</b></font>的技术
架构设计第 3 步:评估和选择方案
列出我们需要关注的质量属性点
分别从这些质量属性的维度去评估每个方案
再综合挑选适合当时情况的最优方案
架构设计第 4 步:详细方案设计
将方案涉及的<font color="#ff0000"><b>关键技术细节</b></font>给确定下来
详细设计方案阶段可能遇到的一种极端情况:发现方案不可行
这种情况可以通过下面方式有效地避免
架构师不但要进行方案设计和选型,还需要对方案的关键细节有较深入的理解
通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度
高性能架构模式
<font color="#ff0000"><b>关系型</b></font>数据库<font color="#ff0000"><b>集群</b></font><br>
读写分离
基本实现
搭建主从集群:主机负责读写操作,<font color="#000000">从机只负责读操作</font>
<font color="#ff0000"><b>主机</b></font>通过复制将<font color="#ff0000"><b>数据同步</b></font>到<font color="#ff0000"><b>从机</b></font>,每台机器都存储了所有的业务数据
业务将<font color="#ff0000"><b>写操作</b></font>发给<font color="#ff0000"><b>主机</b></font>,将<font color="#ff0000"><b>读操作</b></font>发给<font color="#ff0000"><b>从机</b></font>
主从复制<font color="#ff0000"><b>延迟</b></font>带来的<font color="#ff0000"><b>数据不一致</b></font>问题
解决问题的3种<font color="#000000">思路</font>
写操作后的读操作指定发给数据库主服务器
读从机失败后再读一次主机
关键业务读写操作全部指向主机,非关键业务采用读写分离
分库分表
分库
按照业务模块将数据<font color="#ff0000"><b>分散到不同</b></font>的数据库服务器
带来的问题
join 操作问题
事务问题
成本问题
分表
垂直分表
适合将表中<font color="#ff0000" style="font-weight: bold;">不常用</font><font color="#000000" style="">且</font><font color="#ff0000" style="font-weight: bold;">占了大量空间</font>的列拆分出去
表与表之间通过<font color="#ff0000"><b>外键</b></font>关联<br>
水平分表
基于查询key进行数据切分,存储到不同的表中
水平分表相比垂直分表,会引入更多的复杂性
路由算法
范围路由
Hash路由
配置路由
join 操作
count 操作
order by 操作
具体实现方案
程序代码封装(SDK方案),例如:淘宝的TDDL(java)
中间件封装(数据库代理方案),例如:MySQL Router,360公司的Atlas
NoSQL数据库
关系型数据库的4个缺点
关系数据库存储的是行记录,无法存储<font color="#ff0000"><b>数据结构</b></font>
关系数据库的 <font color="#ff0000"><b>schema 扩展</b></font>很不方便
关系数据库在大数据场景下<font color="#ff0000"><b> I/O 较高</b></font>
关系数据库的<font color="#ff0000"><b>全文搜索</b></font>功能比较弱
常见的4类NoSQL方案
KV存储:解决关系数据库无法存储<font color="#ff0000"><b>数据结构</b></font>的问题,以 Redis 为代表
文档数据库:解决关系数据库强 <font color="#ff0000"><b>schema</b></font> 约束的问题,以 MongoDB 为代表
列式数据库:解决关系数据库大数据场景下的<font color="#ff0000"><b> I/O 问题</b></font>,以 HBase 为代表
全文搜索引擎:解决关系数据库的<font color="#ff0000"><b>全文搜索</b></font>性能问题,以 Elasticsearch 为代表
缓存架构
基本原理:将可能重复使用的数据放到内存中(<font color="#ff0000"><b>一次生成、多次使用</b></font>),避免每次使用都去访问存储系统
缓存穿透
缓存没有发挥作用:业务系统去缓存中查询无数据,而再次去存储系统查询数据
存储数据真不存在
对于真不存在的数据设置一个默认值存在缓存中
缓存数据生成耗费大量时间或者资源
没有好的解决方案
只能拆分缓存数据生成计算的逻辑
缓存雪崩
当缓存失效(过期)后引起系统性能急剧下降的情况
解决方案1:更新锁机制
保证同一条数据同一时刻只有一个更新任务
分布式锁
解决方案2:后台更新
业务只读,数据由后台更新
缓存热点
某些数据访问量巨大,缓存系统也扛不住
解决方案:缓存副本,减轻单台缓存机器的压力
单服务器
PPC:Process Per Connection
每次有<font color="#ff0000"><b>新的连接</b></font>就新建<font color="#ff0000"><b>一个进程</b></font>去专门处理这个连接的请求
主要流程
父进程接受连接
父进程“fork”子进程
子进程处理连接的读写请求:read、业务逻辑、write
子进程关闭连接
性能问题
fork 代价高
父子进程通信复杂
支持的并发连接数量有限
TPC:Thread Per Connection
每次有<font color="#ff0000"><b>新的连接</b></font>就新建<font color="#ff0000"><b>一个线程</b></font>去专门处理这个连接的请求
主要流程
父进程接受连接
父进程创建<font color="#ff0000"><b>子线程</b></font>
子线程处理连接的读写请求:read、业务逻辑、write
子线程关闭连接
性能问题
创建线程还是有一定代价的
线程间的互斥和共享问题
某个线程出现异常时,则整个进程退出
Reactor:反应堆模式
Reactor = Reactor监听(I/O多路复用) + 处理资源池(线程池、进程池)
单 Reactor 单进程
主要流程
Reactor 对象通过 select 监控事件,收到事件后通过 dispatch 进行分发
如果是连接建立的事件,则由 Acceptor 处理,并创建一个 Handler 来处理连接后续的各种事件
如果不是连接建立事件,则由连接对应的 Handler 来处理
Handler 会完成 read-> 业务处理 ->send 的完整业务流程
优点:模式简单,没有进程间通信,没有进程竞争
缺点:只有1个进程
无法发挥多核CPU的性能
Handler在处理业务时,无法接入新连接,存在性能瓶颈
该模式<font color="#ff0000"><b>只适合处理业务简单</b></font>的场景,例如:redis
单 Reactor 多线程
主要流程
主线程中,Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发
如果是连接建立的事件,则由 Acceptor 处理,并创建一个 Handler 来处理连接后续的各种事件
如果不是连接建立事件,则由连接对应的 Handler 来处理
Handler 只负责响应事件,不进行业务处理;Handler 通过 read 读取到数据后,会发给 Processor 进行业务处理
Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理
Handler 收到响应后通过 send 将响应结果返回给 client
架构图
优点:充分利用多核多 CPU 的处理能力
缺点
多线程数据共享和访问比较复杂
Reactor(主线程)承担所有事件的监听和响应,<font color="#ff0000"><b>瞬间高并发时</b></font>会成为性能瓶颈
多 Reactor 多进程 / 线程
主要流程
父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程
子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件
当有新的事件发生时,subReactor 会调用连接对应的 Handler 来进行响应
Handler 完成 read→业务处理→send 的完整业务流程
架构图
优点
父进程和子进程的职责非常明确:父进程只负责接收新连接,子进程负责完成后续的业务处理
父进程和子进程的交互很简单:父进程只需要把新连接传给子进程,子进程无须返回数据
子进程之间是互相独立的,无须同步共享之类的处理
例如:Nginx 采用的是多 Reactor 多进程;Memcache、Netty采用的是多 Reactor 多线程
Proactor
理论上 Proactor 比 Reactor 效率要高一些,异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠
但要实现真正的异步 I/O,操作系统需要做大量的工作
Windows 通过 IOCP <font color="#ff0000"><b>实现</b></font>了真正的异步 I/O
Linux <font color="#ff0000"><b>没有实现</b></font>真正的异步 I/O
服务器集群:负载均衡
分类
DNS负载均衡:一般用来实现<font color="#ff0000"><b>地理级别</b></font>的均衡
硬件负载均衡:通过单独的<font color="#ff0000"><b>硬件设备</b></font>来实现,例如:F5 和 A10
软件负载均衡:通过负载均衡<font color="#ff0000"><b>软件</b></font>来实现,例如:Nginx(7层) 和 LVS(4层)
软件 vs 硬件
<span style="color: rgb(51, 51, 51); font-family: "PingFang SC", Avenir, Tahoma, Arial, "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif; font-size: 16px;">性能:软件 < 硬件</span>
Nginx 的性能是<font color="#ff0000"><b>万级</b></font>;LVS 的性能是<font color="#ff0000"><b>十万级</b></font>
F5 性能是<font color="#ff0000"><b>百万级</b></font>
价格:软件 > 硬件
安全:软件 < 硬件
软件一般<font color="#ff0000"><b>不具备</b></font>防火墙和防DDoS攻击等安全功能
硬件一般<font color="#ff0000"><b>具备</b></font>防火墙和防DDoS攻击等安全功能
扩展性:软件 > 硬件
典型架构
DNS负载均衡:用于实现<font color="#ff0000"><b>地理级别</b></font>的负载均衡
硬件负载均衡:用于实现<font color="#ff0000"><b>集群级别</b></font>的负载均衡
软件负载均衡:用于实现<font color="#ff0000"><b>机器级别</b></font>的负载均衡
算法
轮询:按照<font color="#ff0000"><b>顺序轮流</b></font>分配到服务器上
加权轮询:根据服务器<font color="#ff0000"><b>权重</b></font>进行任务分配
负载最低优先:将任务分配给<font color="#ff0000"><b>当前负载最低</b></font>的服务器
性能最优类:优先将任务分配给处理<font color="#ff0000"><b>速度最快</b></font>的服务器
Hash 类:为了满足<font color="#ff0000"><b>特定的业务需</b></font>求,根据任务中的某些关键信息的<font color="#ff0000"><b>Hash计算值</b></font>来分配
高可用架构模式
CAP理论
CAP理论定义要点
分布式系统:<b><font color="#ff0000">互相连接</font></b>并<font color="#ff0000"><b>共享数据</b></font>的节点的集合
涉及<font color="#ff0000"><b>数据读写</b></font>的操作
<b><font color="#ff0000">C</font></b>onsistence:一致性
<b><font color="#ff0000">A</font></b>vailability:可用性
<b><font color="#ff0000">P</font></b>artition Tolerance:分区容错性
CAP三者只能<font color="#ff0000"><b>取二</b></font>
CAP应用
分布式环境下,因为网络本身无法做到 100% 可靠,我们必须选择 P(分区容忍)
分布式系统<font color="#ff0000"><b>理论上不可能选择 CA 架构</b></font>,只能选择 CP 或者 AP 架构
CAP关键细节点
CAP 关注的粒度是<font color="#ff0000"><b>数据</b></font>,而不是<font color="#ff0000"><b>整个系统</b></font>
系统中的一部分数据可以选择CP、另一部分系统可以选择AP
CAP 是忽略<font color="#ff0000"><b>网络延迟</b></font>的
理论上是不可能有完美的 CP 架构
分布式场景下,没有办法保证数据的<font color="#ff0000"><b>强一致性</b></font>,只能保证<font color="#ff0000"><b>最终一致性</b></font>
<b><font color="#ff0000">正常运行情况下</font></b>,不存在 CP 和 AP 的选择,可以同时满足 CA
放弃并不等于什么都不做,需要为<font color="#ff0000"><b>分区恢复</b></font>后做准备
ACID理论
ACID 是<font color="#ff0000"><b>数据库</b></font>管理系统为了保证事务的正确性而提出来的一个理论
<font color="#ff0000"><b>A</b></font>tomicity:原子性
一个事务中的所有操作,要么全部完成,要么全部不完成
<font color="#ff0000"><b>C</b></font>onsistency:一致性
在事务开始之前和事务结束以后,数据库的完整性没有被破坏
<font color="#ff0000"><b>I</b></font>solation:隔离性
数据库允许多个并发事务同时对数据进行读写和修改的能力
<b><font color="#ff0000">D</font></b>urability:持久性
事务处理结束后,对数据的修改就是永久的
BASE理论
BASE 是指基本可用(<b><font color="#ff0000">B</font></b>asically <b><font color="#ff0000">A</font></b>vailable)、软状态( <b><font color="#ff0000">S</font></b>oft State)、最终一致性( <font color="#ff0000"><b>E</b></font>ventual Consistency)
<b><font color="#ff0000">B</font></b>asically <font color="#ff0000"><b>A</b></font>vailable:基本可用
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
<b><font color="#ff0000">S</font></b>oft State:软状态
允许系统存在<b><font color="#ff0000">中间状态</font></b>,而该中间状态<font color="#ff0000"><b>不会影响</b></font>系统整体可用性
这里的中间状态就是 CAP 理论中的<font color="#ff0000"><b>数据不一致</b></font>
<b><font color="#ff0000">E</font></b>ventual Consistency:最终一致性
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 <font color="#ff0000"><b>AP 方案</b></font>的一个补充
CAP 理论是忽略延时的,而实际应用中延时是无法避免的
AP 方案中牺牲一致性只是指分区期间,而不是永远放弃一致性
存储高可用架构
双机架构
主备复制、主从复制
设计关键
主备间状态判断
切换决策
数据冲突解决
常见方案
互连式:主备机直接建立状态传递的渠道
中介式:在主备两者之外引入第三方中介(例如:ZooKeeper)
模拟式:备机模拟成一个客户端来判断主机状态
主主复制
主主复制指的是两台机器都是主机,互相将数据复制给对方
主主复制架构对数据的设计有严格的要求,较少使用
主主复制架构,一般适合于那些临时性、可丢失、可覆盖的数据场景
集群
数据集中集群:1主N从
数据分散集群:每个节点都会负责<font color="#ff0000"><b>存储一部分数据</b></font>
均衡性:每个节点上数据量尽量平均
容错性:当某个节点故障时,数据要转移到正常的节点上去
可伸缩性:当集群容量不够,扩充新的服务器后,数据再分配
数据集中集群 vs 数据分散集群
数据集中集群中,只有主机可以处理<font color="#ff0000"><b>读写</b></font>请求
数据分散集群中,每台服务器都可以处理<font color="#ff0000"><b>读写</b></font>请求
数据集中集群中,主机负责执行<font color="#ff0000"><b>数据分配算法;</b></font>主机可以是指定的(例如,Hadoop集群),也可以是选举出来的(例如,ElasticSearch集群)<br>
数据分区:应对地理级别的故障
计算高可用架构
主备:主机提供服务,备机不提供服务
冷备:程序部署文件准备好了,但是没有启动运行
温备:程序已经启动运行,等待请求
主从:主机、从机都提供服务,需要有一个任务分配器
集群
对称集群(负载均衡集群):服务器角色相同
非对称集群:服务器角色不同
集群会通过某种方式来区分不同服务器的角色
任务分配器将不同任务发送给不同服务器
当指定类型的服务器故障时,需要重新分配角色
业务高可用架构
异地多活架构
同城异区:将业务部署在<font color="#ff0000"><b>同一个城市</b></font>不同区的多个机房
跨城异地:业务部署在<font color="#ff0000"><b>不同城市</b></font>的多个机房
如果是<font color="#ff0000"><b>强一致性</b></font>要求的数据,实际上是<font color="#ff0000" style="font-weight: bold;">无法做到</font><font color="#000000" style=""><b>跨城异地多活</b>,而采用<b>同城异区多活</b></font>
强一致性要求的数据,例如:银行存款余额、支付宝余额等金融相关数据
跨国异地:业务部署在<font color="#ff0000"><b>不同国家</b></font>的多个机房
异地多活设计4大技巧
技巧 1:保证<font color="#ff0000"><b>核心业务</b></font>的异地多活
技巧 2:保证核心数据<font color="#ff0000" style="font-weight: bold;">最终一致性</font><font color="#000000" style="">,</font>不保证实时一致性
技巧 3:采用多种手段<font color="#ff0000"><b>同步数据</b></font>
技巧 4:只保证<font color="#ff0000"><b>绝大部分</b></font>用户的异地多活
异地多活设计4步走
第 1 步:业务分级
访问量大的业务
核心业务
产生大量收入的业务
第 2 步:数据分类
第 3 步:数据同步
存储系统同步
消息队列同步
重复生成
第 4 步:异常处理
多通道同步
同步和访问结合
日志记录
用户补偿
接口级别高可用
降级
降级是指系统将某些业务或者接口的功能降低,可以是<font color="#ff0000"><b>只提供部分功能</b></font>,也可以是<font color="#ff0000"><b>完全停掉所有功能</b></font>
降级的目的是应对<font color="#ff0000"><b>系统自身</b></font>的故障
熔断
熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障
熔断的目的是应对依赖的<font color="#ff0000"><b>外部系统故障</b></font>的情况
限流
限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被<font color="#ff0000"><b>丢弃</b></font>
限流的目的是应对<font color="#ff0000"><b>访问压力过大</b></font>的情况
常见的限流方式
基于请求限流:限制一段时间窗口内的请求总量
基于资源限流
限流算法
时间窗
桶算法
令牌桶算法
排队:限流的变种
超出系统访问能力的请求,不是丢弃,而是排队等待处理
可扩展架构模式
可扩展架构的基本思想和模式
所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:<font color="#ff0000"><b>拆!</b></font>
常见的三种拆分思路
面向<b><font color="#ff0000">流程</font></b>拆分:将整个业务流程拆分为几个阶段,每个阶段作为一部分
面向<b><font color="#ff0000">服务</font></b>拆分:将系统提供的服务拆分,每个服务作为一部分
面向<b><font color="#ff0000">功能</font></b>拆分:将系统提供的功能拆分,每个功能作为一部分
以 TCP/IP 协议栈为例来讲
面向流程拆分:应用层 → 传输层 → 网络层 → 物理 + 数据链路层
面向服务拆分:应用层有 HTTP、FTP、SMTP 等服务
面向功能拆分:HTTP 服务提供 GET、POST 等功能
不同的<b><font color="#ff0000">拆分方式</font></b>,本质上决定了系统的<font color="#ff0000" style="font-weight: bold;">扩展方式</font><font color="#000000" style="">,得到不同的</font><font color="#ff0000" style="font-weight: bold;">可扩展系统架构</font>
面向流程拆分 -> <font color="#ff0000"><b>分层架构</b></font>
扩展时大部分情况只需要修改某一层
少部分情况可能修改关联的两层
不会出现所有层都同时要修改
面向服务拆分 -> <font color="#ff0000"><b>SOA</b></font>、<font color="#ff0000"><b>微服务</b></font>
对某个服务扩展,或者要增加新的服务时,只需要扩展相关服务即可
无须修改所有的服务
面向功能拆分:<font color="#ff0000"><b>微内核架构</b></font>
对某个功能扩展,或者要增加新的功能时,只需要扩展相关功能即可
无须修改所有的服务
传统的可扩展架构模式:<font color="#ff0000"><b>分层架构</b></font>
根据不同的划分<b><font color="#ff0000">维度</font></b>和<font color="#ff0000"><b>对象</b></font>,可以得到多种不同的分层架构
C/S 架构、B/S 架构
划分的<b><font color="#ff0000">维度</font></b>是用户交互
划分的<font color="#ff0000"><b>对象</b></font>是整个业务系统
MVC 架构、MVP 架构
划分的<font color="#ff0000"><b>维度</b></font>是职责
<b><font color="#ff0000">M</font></b>odel:数据模型层
<b><font color="#ff0000">V</font></b>iew:展示层
<b><font color="#ff0000">C</font></b>ontroller:控制层
划分的<font color="#ff0000"><b>对象</b></font>是单个业务子系统
<b><font color="#ff0000">逻辑</font></b>分层架构
划分的<b><font color="#ff0000">对象</font></b>可以是单个业务子系统,也可以是整个业务系统
划分的<b><font color="#ff0000">维度</font></b>也是职责
逻辑分层架构中的层是<font color="#ff0000"><b>自顶向下依赖</b></font>的
设计核心
保证各层之间的<b><font color="#ff0000">差异足够清晰</font></b>,<b><font color="#ff0000">边界足够明显</font></b>
层与层之间的依赖是稳定的(接口),而不是变化的(具体实现)
一旦分层确定,整个业务流程是按照层进行<font color="#ff0000"><b>依次传递</b></font>的,不能<font color="#ff0000"><b>跨层跳跃</b></font>
好处:强制将分层依赖限定为<font color="#ff0000"><b>两两依赖</b></font>,<b><font color="#ff0000">降低了整体系统复杂度</font></b>
坏处:冗余开发量;性能损耗
传统的可扩展架构模式:<font color="#ff0000" style="font-weight: bold;">SOA</font><font color="#000000" style=""> -</font>“面向服务的架构”
SOA 出现 的背景:企业内部的 IT 系统<font color="#ff0000"><b>重复建设</b></font>且<font color="#ff0000"><b>效率低下</b></font>
为了应对上述问题,SOA 提出了3个关键概念
服务
所有业务功能都是一项服务
服务要对外提供开放的能力
<b><font color="#ff0000">ESB</font></b>:“企业服务总线”
ESB 将企业中各个不同的服务连接在一起
SOA 使用 ESB 来屏蔽异构系统对外提供各种不同的接口方式
ESB 很重很复杂
松耦合
松耦合的目的是减少各个服务间的依赖和互相影响
各个服务是相互独立运行的
SOA 架构是比较<b><font color="#ff0000">高层级</font></b>的架构设计理念
一般情况下我们可以说某个企业采用了 SOA 的架构来构建 IT 系统
但不会说某个独立的系统采用了 SOA 架构
SOA 最广<b><font color="#ff0000">为人诟病</font></b>的就是 ESB
ESB 需要实现与各种系统间的协议转换、数据转换、透明的动态路由等功能,从而让各个服务简单化
当 ESB 承载的消息太多时,ESB 本身会成为整个系统的性能瓶颈
微服务架构
微服务与 SOA 的关系
SOA 和 微服务 <b><font color="#ff0000">本质上</font></b>是两种<font color="#ff0000"><b>不同</b></font>的架构设计理念,只是在“服务”这个点上有交集而已
服务粒度:<b><font color="#ff0000">SOA</font></b> 的服务粒度<b><font color="#ff0000">粗</font></b>,而<b><font color="#ff0000">微服务</font></b>的服务粒度<font color="#ff0000"><b>细</b></font>
服务通信:SOA 要兼容<b><font color="#ff0000">各种协议</font></b>,<b><font color="#ff0000">重量级</font></b>;微服务 是统一<font color="#ff0000" style="font-weight: bold;">一种协议</font><font color="#000000" style="">,</font><font color="#ff0000" style="font-weight: bold;">轻量级</font>
服务交付:SOA <font color="#ff0000"><b>没有特殊要求</b></font>;微服务 要求<font color="#ff0000"><b>快速交付</b></font>
应用场景:SOA 更加适合于庞大、复杂、异构、稳定的企业级系统;微服务适合于快速、轻量级、业务变化快的互联网系统<br>
微服务的陷阱
服务划分过细,服务间关系复杂
服务数量太多,团队效率急剧下降
调用链太长,性能下降,问题定位困难
没有自动化支撑,无法快速交付
没有服务治理,微服务数量多了后管理混乱
微服务架构最佳实践
服务拆分粒度
“三个火枪手”原则:一个微服务三个人负责开发
“三个火枪手”的原则:主要应用于微服务<font color="#ff0000"><b>设计和开发阶段</b></font>
如果微服务处于稳定维护期,那么1 个人可以维护多个微服务
服务拆分方法
基于业务逻辑拆分
基于可扩展拆分
将已经成熟和改动不大的服务拆分为<font color="#ff0000"><b>稳定服务</b></font>
将经常变化和迭代的服务拆分为<font color="#ff0000"><b>变动服务</b></font>
基于可靠性拆分
避免非核心服务故障影响核心服务
核心服务高可用方案可以更简单
能够降低高可用成本
基于性能拆分
将性能要求高或者性能压力大的模块拆分出来
基础设施
微服务并没有减少复杂度,而只是将<font color="#ff0000"><b>复杂度转移</b></font>到了<font color="#ff0000"><b>基础设施</b></font>
<b><font color="#ff0000">服务发现</font></b>、<b><font color="#ff0000">服务路由</font></b>、<font color="#ff0000"><b>服务容错</b></font>:这是最基本的微服务基础设施
<b><font color="#ff0000">接口框架</font></b>:提升内部服务的开发效率
<b><font color="#ff0000">API 网关</font></b>:提升与外部服务对接的效率
<b><font color="#ff0000">自动化部署</font></b>、<b><font color="#ff0000">自动化测试</font></b>、<font color="#ff0000"><b>配置中心</b></font>:主要是为了提升测试和运维效率
<b><font color="#ff0000">服务监控</font></b>、<font color="#ff0000"><b>服务跟踪</b></font>、<font color="#ff0000"><b>服务安全</b></font>:主要是为了进一步提升运维效率
微内核架构(插件化架构)
微内核架构主要应用场景:<font color="#ff0000"><b>大单体</b></font>软件
基本架构
核心系统:core system
负责与<b style="color: rgb(255, 0, 0);">业务无关</b>的<font color="#ff0000"><b>通用</b></font>功能
例如:模块加载、模块间通信等
插件模块:plug-in modules
负责实现具体的<b><font color="#ff0000">业务逻辑</font></b>
设计关键点
插件管理:插件注册表
插件连接:插件如何连接到核心系统
插件通信:指插件间的通信
微内核架构示例:OSGi 架构、规则引擎架构
架构实战
架构师应该如何判断技术演进的方向?
面对层出不穷的<font color="#ff0000"><b>新技术</b></font>,架构师应该采取什么样的策略?
基于 业务发展 阶段进行 技术演进
潮流派
潮流派的价值观:新技术肯定能带来很大收益
问题点
成为新技术的<font color="#ff0000"><b>实验小白鼠</b></font>
新技术的<font color="#ff0000" style="font-weight: bold;">学习成本,</font><font color="#000000" style="">无法体现出</font><font color="#ff0000" style="font-weight: bold;">业务价值</font>
稳定派
稳定派的价值观:稳定压倒一切
问题点
不能享受新技术带来的收益
跟风派
跟风派的价值观:别人用了我就用
问题点
如果没有风可跟,怎么办
竞争对手的信息并不容易获取
适用于竞争对手的技术,并不一定适用于自己
技术演进的动力
绝大部分情况下,<font color="#ff0000"><b>业务</b></font>发展推动<font color="#ff0000"><b>技术</b></font>的发展
极小部分情况下,<font color="#ff0000"><b>技术</b></font>创新推动<font color="#ff0000" style="font-weight: bold;">业务</font><font color="#000000" style="">的</font>发展
技术演进的模式
基于业务发展阶段进行技术演进
互联网技术演进的模式
互联网业务具有“<font color="#ff0000"><b>规模决定一切</b></font>”的特点
互联网业务发展一般分为4个时期,不同时期的差别主要体现:<font color="#ff0000"><b>业务复杂性</b></font>、<font color="#ff0000"><b>用户规模</b></font>
初创期
业务点的重点不在于“完善”,而在于“创新”:只有创新才能<font color="#ff0000"><b>吸引用户</b></font>
业务对技术的要求只有一个:<font color="#ff0000"><b>“快”</b></font>
发展期
业务快速发展时期:将原来不完善的业务逐渐完善
业务对技术的要求:在系统中不断的<font color="#ff0000"><b>快速加入</b></font>各种新功能
技术演化方式:堆功能 -> 优化 -> 架构升级 -> 堆功能 --> ...
竞争期
业务继续发展已经形成一定规模后,新功能新系统越来越多
系统数量的<font color="#ff0000" style="font-weight: bold;">量变 </font><font color="#000000" style="">-></font><font color="#ff0000" style="font-weight: bold;"> </font>技术工作的<b style=""><font color="#ff0000">质变</font></b>
重复造轮子
系统交互乱
技术工作主要的解决手段
<b><font color="#ff0000">平台化</font></b>:解决“重复造轮子”问题
存储平台化
数据库平台化
缓存平台化
......
<font color="#ff0000"><b>服务化</b></font>:解决“系统交互乱”的问题
通过<font color="#ff0000"><b>消息队列</b></font>来完成系统间的<font color="#ff0000"><b>异步</b></font>通知
通过<b style=""><font color="#ff0000">服务框架</font></b>来完成系统间的<font color="#ff0000"><b>同步</b></font>调用
成熟期
当企业熬过竞争期,业务上<font color="#ff0000"><b>“求快求新”</b></font>已经没有增长空间,开始转向为<font color="#ff0000"><b>“求精”</b></font>
技术上其实也基本进入了成熟期,找出自己的弱项,然后逐项优化
互联网架构模板:“存储层”技术
SQL:关系型数据库
单机、双机主备
分布式集群(分库分表)
例如:百度的 DBProxy、淘宝的 TDDL、MySQL 官方推荐的 MySQL Router、360 开源的数据库中间件 Atlas
统一存储平台
例如:淘宝的 UMP(Unified MySQL Platform)系统
NOSQL:
分布式集群:NoSQL 一般自身就提供集群的功能
例如:Memcache 的一致性 Hash 集群、Redis 3.0 的集群
统一存储平台
小文件存储
统一存储平台
例如:淘宝的 TFS、京东 JFS、Facebook 的 Haystack
大文件存储(大数据平台)
Hadoop 生态圈
互联网架构模板:“开发层”和“服务层”技术
开发层技术
开发框架(开发语言)
使用统一的开发框架能够解决上面提到的各种问题,大大提升组织和团队的开发效率
优选成熟的框架,避免盲目追逐新技术
Web 服务器
“拿来主义”
容器
运维方式会发生革命性的变化
设计模式会发生本质上的变化:启动一个新的容器实例代价如此低,将鼓励设计思路朝“微服务”的方向发展
容器运维也是有成本的
服务层技术
配置中心
服务中心
方案1:服务名字系统(<font color="#ff0000"><b>服务注册发现</b></font>机制)
方案2:服务总线系统(服务网格mesh机制)
消息队列
互联网架构模板:“网络层”技术
负载均衡
DNS
Nginx 、LVS 、F5
CDN:将内容缓存在离用户最近的地方
多机房
同城多机房
跨城多机房
跨国多机房
多中心(多活)
不一定所有业务都能实现多中心
目前国内的银行、支付宝这类系统就没有完全实现多中心
不然也不会出现挖掘机一铲子下去,支付宝中断 4 小时的故障
互联网架构模板:“用户层”和“业务层”技术
用户层技术
用户管理
单点登录(SSO)
开源单点登录方案当属 CAS
授权登录(OAuth 2.0)
消息推送
存储云、图片云
基于“CDN + 小文件存储”的技术
业务层技术
系统“拆分”
系统“合并”
互联网架构模板:“平台”技术
运维平台:配置、部署、监控、应急
标准化
平台化
自动化
可视化
测试平台
用例管理
资源管理
任务管理
数据管理
数据平台
数据管理
数据采集:从业务系统搜集各类数据
数据存储:将从业务系统采集的数据存储到数据平台,用于后续数据分析
数据访问:负责对外提供各种协议用于读写数据
数据安全
数据分析
数据统计:根据原始数据统计出相关的总览数据(指标)
例如,PV、UV、交易额等
数据挖掘
机器学习、深度学习
数据应用
在线业务
推荐、广告
离线业务
报表、BI分析、欺诈检测
管理平台
管理平台的核心职责就是<font color="#ff0000"><b>权限管理</b></font>
身份认证:证明你是你
权限控制:判断你是否有权限
如何推动<font color="#ff0000"><b>架构重构</b></font>
第一式:有的放矢
从一大堆纷繁复杂的问题中识别出真正要通过架构重构来解决的<font color="#ff0000"><b>一类问题</b></font>,集中力量快速解决
而<b><font color="#ff0000">不是</font></b>想着通过架构重构来<font color="#ff0000"><b>解决所有</b></font>的问题
第二式:合纵连横
合纵:凭借<font color="#ff0000"><b>数据指标</b></font>告诉<font color="#ff0000"><b>业务</b></font>产品重构的好处
连横:<font color="#ff0000"><b>技术方</b></font>,站在对方的角度思考,<font color="#ff0000"><b>重构对他有什么好处</b></font>,能够帮他解决什么问题,带来什么收益
第三式:运筹帷幄
将要解决的问题根据优先级、重要性、实施难度等<b><font color="#ff0000">划分为</font></b><font color="#ff0000"><b>不同的阶段</b></font>,每个阶段聚焦于一个整体的目标,集中精力和资源解决一类问题
如何高效地学习<font color="#ff0000"><b>开源项目</b></font>
树立正确的观念
不管你是什么身份,都可以从开源项目中学到很多东西
不要只盯着<b><font color="#ff0000">数据结构</font></b>和<font color="#ff0000"><b>算法</b></font>
<font color="#ff0000"><b>源码</b></font>不是第一步,而是最后一步
采用<font color="#ff0000"><b>“自顶向下”</b></font>学习的5个步骤<br>
第 1 步:安装
这个系统的依赖组件
使用和运行的基本信息
系统提供了哪些工具方便我们使用
第 2 步:运行
系统具备哪些能力
第 3 步:原理研究
关键特性的基本实现原理
系统将会如何运行
优缺点对比分析
第 4 步:测试
测试一定要在原理研究之后做,不能安装完成立马就测试
第 5 步:源码研究
源码研究的主要目的是学习原理背后的具体编码如何实现,通过学习这些技巧来提升我们自己的技术能力
不建议通读所有源码
“自顶向下”学习的5个步骤的时间分配
第 1、2、3 步骤,不管是架构师,还是技术人员,在研究开源项目时都是必不可少的
第 4 步骤,可以在准备采用开源项目的时候才实施
第 5 步骤,可以根据自身时间情况来进行灵活安排,非必须
如何选择、使用以及二次开发开源项目
不要重复发明轮子,但要找到<font color="#ff0000"><b>合适</b></font>的轮子
选:如何选择一个开源项目
聚焦是否<font color="#ff0000"><b>满足业务</b></font>
聚焦是否<font color="#ff0000"><b>成熟</b></font>
尽量选择成熟的开源项目,降低风险
从这几个方面考察开源项目是否成熟
版本号:至少选 1.X 版本的
使用的公司数量
社区活跃度
聚焦<font color="#ff0000"><b>运维</b></font>能力
用:如何使用开源项目
深入研究,仔细测试
小心应用,灰度发布
做好应急,以防万一
改:如何基于开源项目做二次开发
保持纯洁,加以包装
不要改动原系统,而是要开发辅助系统
例如,redis没有集群版本时,开发一个proxy来支持,而不是改redis
发明你要的轮子
谈谈<font color="#ff0000"><b>App架构</b></font>的演进
Web App:包壳架构(App实质是个浏览器)
原生 App:基于Android 或者 iOS 操作系统提供的API开发
Hybrid App:混合模式
组件化 & 容器化
超级APP承载的业务功能太多,每个功能都是一个子系统
子系统作为单独组件独立开发;组件之间通过事先定义好的协议进行消息通信
多个组件一起组成超级APP
跨平台 App
问题:相同功能在 Android 和 IOS 都要实现一遍,浪费人力
解决方案:目前还不成熟
0 条评论
下一页