从零开始学架构(读书笔记)
2022-09-09 20:48:17 31 举报
AI智能生成
架构
作者其他创作
大纲/内容
第二部分 高性能架构模式
第4章 存储高性能
关系数据库
读写分离
本质是将访问压力分散到集群中的多个节点,但是没有分散存储压力
读写分离的基本原理是将数据库读写操作分散到不同的节点上
架构图
读写分离的实现逻辑并不复杂,但在实际应用过程中需要应对复制延迟带来的复杂性
解决主从复制延迟有几种常见的方法
写操作后的读操作指定发给数据库主服务器
这种方式和业务强绑定,对业务的侵入和影响较大
读从机失败后再读一次主机
二次读取和业务无绑定,只需要对底层数据库访问的 API进行封装即可,实现代价较小
不足之处在于如果有很多二次读取,将大大增加主机的读操作压力
关键业务读写操作全部指向主机,非关键业务采用读写分离
分库分表
既可以分散访问压力,又可以分散存储压力
当数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力会成为系统的瓶颈,主要体现在以下几个方面
数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会下降
数据文件会变得很大,数据库备份和恢复需要耗费很长时间
数据文件越大,极端情况下丢失数据的风险越高
业务分库
业务分库指的是按照业务模块将数据分散到不同的数据库服务器
示意图
虽然业务分库能够分散存储和访问压力,但同时也带来了新的问题
【join 操作问题】
【事务问题】
【成本问题】
对于初创业务,并不建议一开始就这样拆分
业务开始的时候并没有真正 的存储和访问压力,业务分库并不能为业务带来价值
业务分库后,表之间的 join 查询、数据库事务无法简单实现了
业务初创期间最重要的是快速实现、快速验证,业务分库会拖慢业务节奏
分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈
单表数据拆分有两种方式 :垂直分表和水平分表
示意图
并不强制要求单表切分为多表后一定要分散到不同数据库中
原因在于单表切分为多表后,新的表即使在同一个数据库服务器中,也可能带来可观的性能提升
垂直分表
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去
垂直分表引入的复杂性主要体现在表操作的数量要增加
水平分表
水平分表适合表行数特别大的表
引入的复杂性
【路由】
常见的路由算法
范围路由
范围路由设计的复杂点主要体现在分段大小的选取上
分段太小会导致切分后子表数量过多,增加维护复杂度
分段太大可能会导致单表依然存在性能问题
范围路由的优点是可以随着数据的增加平滑地扩充新的表
范围路由的一个比较隐含的缺点是分布不均匀
Hash路由
选取某个列(或者某几个列组合也可以)的值进行 Hash 运算,然后根据 Hash 结果分散到不同的数据库表中
Hash 路由设计的复杂点主要体现在初始表数量的选取上
表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题
Hash 路由的优缺点和范围路由基本相反,Hash 路由的优点是表分布比较均匀,缺点是扩充新的表很麻烦,所有数据都要重分布
配置路由
配置路由就是路由表,用一张独立的表来记录路由信息
配置路由设计简单,使用起来非常灵活,尤其是在扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了
配置路由的缺点就是必须多查询一次,会影响整体性能
【join 操作】
【count()操作】
count()相加
记录数表
【order by操作】
实现方法
读写分离需要将读/写操作区分开来,然后访问不同的数据库服务器
分库分表需要根据不同的数据访问不同的数据库服务器
两者本质上都是一种分配机制,即将不同的 SQL 语句发送到不同的数据库服务器
常见的分配实现方式有两种:程序代码封装和中间件封装
程序代码封装
指在代码中抽象一个数据访问层来实现读写分离、分库分表
架构图
淘宝的 TDDL (Taobao Distributed Data Layer,外号: 头都大了)
其基本原理是一个基于集中式配置的 jdbc datasource 实现,具有主备、读写分离、动态数据库配置等功能
中间件封装
指的是独立一套系统出来,实现读写分离和分库分表操作
对于业务服务器来说,访问中间件和访问数据库没有区别
架构图
MySQL 官方推荐 MySQL Router
主要功能有读写分离、故障自动切换、负载均衡、连接池等
架构图
实现复杂度
读写分离实现时只要识别 SQL 操作是读操作还是写操作即可,通过简单地判断 SELECT、 UPDATE、时SERT、DELETE 几个关键字就可以实现
分库分表的实现除了要判断操作类型,还要判断 SQL 中的具体需要操作的表、操作函数(例如,count 函数、order by、group by 操作等),然后根据不同的操作进行不同的处理
NoSQL
关系数据库的缺点
关系数据库存储的是行记录,无法存储数据结构
关系数据库的 schema 扩展很不方便
关系数据库在大数据场景下 I/O 较高
因为即使只针对其中某一列进行运算,关系数据库也会将整行数据读取
关系数据库的全文搜索功能比较弱
只能使用 like进行整表扫描匹配,性能非常低
KV 存储
解决关系数据库无法存储数据结构的问题,以 Redis 为代表
Redis 的 Value 是具体的数据结构,包括 string、hash、list、set、sorted set、bitmap 和 hyperloglog
常常被称为数据结构服务器
Redis 的事务只能保证隔离性和一致性(I和 C),无法保证原子性和持久性(A 和 D )
原子性
Redis事务不支持原子性,Redis不支持回滚操作,事务中间一条命令执行失败,既不会导致前面己经执行的命令被回滚,也不会中断后面的命令的执行
一致性
Redis 事务能够保证事务开始之前和事务结束以后,数据库的完整性没有被破坏
隔离性
Redis 不存在多个事务的问题,因为 Redis 是单进程单线程的工作模式
持久性
Redis 提供两种持久化的方式,即 RDB 和 AOF
RDB 持久化不能保证 Redis 事务的持久性
AOF 持久化是先执行命令,执行成功后再将命令追加到日志文件中
即使 AOF 每次执行命令后立刻将日志文件刷盘,也可能丢失一条命令数据
AOF 也不能严格保证 Redis 事务的持久性
文档数据库
解决关系数据库强 schema约束的问题,以 MongoDB为代表
文档数据库最大的特点就 是 no-schema,可以存储和读取任意的数据
给业务开发带来如下几个明显的优势
新增字段简单
历史数据不会出错
可以很容易存储复杂数据
文档数据库的这个特点,特别适合电商和游戏这类的业务场景
最主要的代价就是不支持事务
另外一个缺点就是无法实现关系数据库的 join 操作
例如,在常见的电商网站设计中,可以使用关系数据库存储商品库存信息、订单基础信息,而使用文档数据库来存储商品详细信息
列式数据库
解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表
列式数据库就是按照列来存储数据的数据库,与之对应的传统关系数据库被称为“行式数据库”
除了节省I/O,列式存储还具备更高的存储压缩比,能够节省更多的存储空间
一般将列式存储应用在离线的大数据分析和统计场景中
全文搜索引擎
解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表
索引无能为力,主要体现在如下几点
全文搜索的条件可以随意排列组合,如果通过索引来满足,则索引的数量会非常多
全文搜索的模糊匹配方式,索引无法满足,只能用 like 查询,而 like 查询是整表扫描,效率非常低
基本原理
全文搜索引擎的技术原理被称为“倒排索引” (Inverted index)
其基本原理是建立单词到文档的索引
之所以被称为“倒排索引”,是和“正排索引 ”相对的,“正排索引 ”的基本原理是建立文档到单词的索引
正排索引示例图
正排索引适用于根据文档名称来查询文档内容
倒排索引示例图
倒排索引适用于根据关键词来查询文档内容
与数据库结合
全文搜索引擎的索引对象是单词和文档,而关系数据库的索引对象是键和行
为了让全文搜索引擎支持关系型数据的全文搜索,需要做一些转换操作,即将关系型数据转换为文档数据
目前常用的转换方式是将关系型数据按照对象的形式转换为 JSON 文挡,然后将 JSON 文档输入全文搜索引擎进行索引
全文搜索引擎能够基于 JSON 文档建立全文索引,然后快速进行全文搜索
缓存
但在某些复杂的业务场景下,单纯依 靠存储系统的性能提升不够的,典型的场景如下
需要经过复杂运算后得出的数据,存储系统无能为力
读多写少的数据,存储系统有心无力
缓存的基本原理就是将可能重复使用的数据放到内存中,一次生成,多次使用,避免每次使用都去访问存储系统
缓存的架构设计要点
缓存穿透
缓存中没有数据
存储数据不存在
缓存空对象
布隆过滤器
缓存数据生成耗费大量时间或资源
存储系统中存在数据,但生成缓存数据需要耗费较长时间或耗费大量资源
缓存雪崩
指当缓存失效(过期)后引起系统性能急剧下降的情况
更新锁机制
对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或默认值
分布式集群的业务系统要完美实现更新锁机制,需要用到分布式锁
后台更新机制
由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存
定时读取
消息队列通知
后台更新机制还适合业务刚上线的时候进行缓存预热
缓存预热指系统上线后,将相关的缓存数据直接加载到缓存系统,而不是等待用户访问才来触发缓存加载
缓存热点
解决方案就是复制多份缓存,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力
第5章 计算高性能
高性能架构设计主要集中在两方面
尽量提升单服务器的性能,将单服务器的性能发挥到极致
如果单服务器无法支撑性能,设计服务器集群方案
架构设计决定了系统性能的上限,实现细节决定了系统性能的下限
单服务器高性能
单服务器高性能的关键之一就是服务器采取的网络编程模型
网络编程模型有如下两个关键设计点
服务器如何管理连接
服务器如何处理请求
以上两个设计点最终都和操作系统的 I/O 模型及进程模型相关
I/O模型:阻塞、非阻塞、同步、异步
进程模型:单进程、 多进程、多线程
PPC(Process per Connection)
每次有新的连接就新建一个进程去专门处理这个连接的请求
流程图
PPC 模式实现简单,比较适合服务器的连接数没那么多的情况。例如,数据库服务器
互联网兴起后,服务器的并发和访问量从几十剧增到成千上万,这种模式的弊端就凸显出来了,主要体现在如下几个方面
fork代价高
父子进程通信复杂
进程数量增大后对操作系统压力较大
PPC 方案能处理的并发连接数量最大也就几百
prefork
refork 就是提前创建进程 (pre-fork)
系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去“ fork”进程的操作
prefork 模式和 PPC 一样,还是存在父子进程通信复杂、支持的并发连接数量有限的问题
TPC(Thread per Connection)
每次有新的连接就新建一个线程去专门处理这个连接的请求
与进程相比
线程更轻量级,创建线程的消耗比进程要少得多
多线程是共享进程内存空间的,线程通信相比进程通信更简单
TPC 实际上是解决或弱化了PPC的问题1 (fork代价高)和问题2(父子进程通信复杂)
引入了新的问题
创建线程虽然比创建进程代价低,但并不是没有代价
无须进程间通信,但是线程间的互斥和共享又引入了复杂度,可能一不小心就导致了死锁问题
多线程会出现互相影响的情况,某个线程出现异常时,可能导致整个进程退出(例如内存越界)
除了引入了新的问题,TPC 还是存在 CPU 线程调度和切换代价的问题
在并发几百连接的场景下,反而更多的是采用 PPC 的方案,因为 PPC 方案不会有死锁的风险,也不会多进程互相影响,稳定性更高
prethread
prethread模式会预先创建线程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去创建线程的操作
多线程之间数据共享和通信比较方便
Apache服务器的 MPM worker模式本质上就是一种 prethread方案
默认支持 400 个并发处理线程
Reactor(反应堆)
资源复用,即不再单独为每个连接创建进程,而是创建一个进程池,将连接分配给进程,一个进程可以处理多个连接的业务
只有当连接上有数据的时候进程才去处理,这就是 I/O 多路复用技术的来源
计算机网络领域的 I/O 多路复用,其中的“多路”,就是指多条连接,“复用”指的是多条连接复用同 一个阻塞对象,这个阻塞对象和具体的实现有关
如果使用 select,则这个公共的阻塞到象就是 select 用到的 fd_set
如果使用 epoll,就是 epoll_create 创建的文件描述符
I/O 多路复用技术归纳起来有如下两个关键实现点
当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接
当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理
I/O 多路复用结合线程池,完美地解决了 PPC 和 TPC 模型的问题,而且“大神们”给它取了一个很牛的名字: Reactor,中文是“反应堆”
Reactor 模式也叫 Dispatcher 模式
可以通俗地理解为“来了一个事件我就有相应的反应”
Reactor模式的核心组成部分包括 Reactor和处理资源池 (进程池或线程池),其中 Reactor 负责监听和分配事件,处理资源池负责处理事件
三种典型的实现方案
单 Reactor单进程/线程
优点就是很简单,没有进程间通信,没有进程竞争,全部都在同一个进程内完成
缺点
只有一个进程,无法发挥多核 CPU 的性能
Handler在处理某个连接上的业务时, 整个进程无法处理其他连接的事件,很容易导致性能瓶颈
目前比较著名的开源软件中使用单 Reactor单进程的是 Redis
单 Reactor 多线程
能够充分利用多核多 CPU 的处理能力
缺点
多线程数据共享和访问比较复杂
Reactor 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈
多 Reactor 多进程/线程
采用多 Reactor 多进程实现的著名的开源系统是 Nginx
采用多 Reactor 多线程实现有 Memcache 和 Nety
Proactor(主动器)
非阻塞异步网络模式
Proactor 是异步网络模型
Proactor 可以理解为“来了事件我来处理,处理完了我通知你”
这里的“我” 就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写这些 1/0事件
在 Linux 系统下的 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 Reactor 模式为主
集群高性能
高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的计算能力
高性能集群设计的复杂度主要体现在任务分配这部分,需要设计合理的任务分配策略,将计算任务分配到多台服务器上执行
高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法
负载均衡分类
DNS负载均衡
一般用来实现地理级别的均衡
例如,北方的用户访问北京的机房,南方的用户访问深圳的机房
DNS 负载均衡的本质是 DNS 解析同 一个域名可以返回不同的 IP 地址
示意图
【优点】
简单 、成本低
负载均衡工作交给 DNS 服务器处理,无须自己开发或维护负载均衡设备
就近访问,提升访问速度
DNS 解析时可以根据请求来源 IP,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能
【缺点 】
更新不及时
DNS缓存的时间比较长,修改 DNS配置后,由于缓存的原因,还是有很多用户会继续访问修改前的 IP
扩展性差
DNS 负载均衡的控制权在域名商那里,无法根据业务特点针对其做更多的定制化功能和扩展特性
分配策略比较简单
DNS 负载均衡支持的算法少
不能区分服务器的差异
无法感知后端服务器的状态
硬件负载均衡
过单独的硬件设备来实现负载均衡功能,目前业界典型的硬件负载均衡设备有两款 : F5 和 A10
【优点 】
功能强大
全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡
性能强大
稳定性高
支持安全防护
【缺点 】
价格昂贵
扩展能力差
软件负载均衡
通过负载均衡软件来实现负载均衡功能
常见的有 Nginx 和 LVS
Nginx 是软件的 7 层负载均衡
LVS 是 Linux 内核的 4 层负载均衡
硬件负载均衡性能远远高于软件负载均衡性能
【优点】
简单
便宜
灵活
【缺点】
性能一般
一个 Nginx大约能支撑 5 万并发
功能没有硬件负载均衡那么强大
一般不具备防火墙和防 DDOS 攻击等安全功能
负载均衡架构
组合的基本原则为 : DNS 负载均衡用于实现地理级别的负载均衡;硬件负载均衡用于实现集群级别的负载均衡;软件负载均衡用于实现机器级别的负载均衡
示意图
负载均衡的算法
分类
任务平分类
负载均衡系统将收到的任务平均分配给服务器进行处理,这里的“平均”可以是绝对数量的平均,也可以是比例或权重上的平均
负载均衡类
负载均衡系统根据服务器的负载来进行分配
性能最优类
负载均衡系统根据服务器的响应时间来进行任务分配, 优先将新任务分配给响应最快的服务器
Hash 类
负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上
常见的负载均衡算法及它们的优缺点
轮询
“简单”是轮询算法的优点,也是它的缺点
加权轮询
这里的权重一般是根据硬件配置进行静态配置的
主要目的就是为了解决不同服务器处理能力有差异的问题
负载最低优先
将任务分配给当前负载最低的服务器
LVS 这种 4 层网络负载均衡设备,可以以“连接数”来判断服务器的状态
Nginx 这种 7 层网络负载系统,可以以“ HTTP 请求数”来判断服务器状态
如果我们自己开发负载均衡系统,可以根据业务特点来选择指标衡量系统压力
解决了轮询算法中无法感知服务器状态的问题
性能最优类
负载最低优先类算法是站在服务器的角度来进行分配的
而性能最优优先类算法则是站在客户端的角度来进行分配的
通过这种方式达到最快响应客户端的目的
Hash类
负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上,这样做的目的主要是为了满足特定的业务需求
源地址Hash
ID Hash
第四部分 可扩展架构模式
第11章 可扩展模式
如何避免扩展时改动范围太大,是软件架构可扩展性设计的主要思考点
可扩展的基本思想
所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆!
常见的拆分思路有如下三种
面向流程拆分
将整个业务流程拆分为几个阶段,每个阶段作为一部分
面向服务拆分
将系统提供的服务拆分,每个服务作为一部分
面向功能拆分
将系统提供的功能拆分,每个功能作为一部分
我们以 TCP/IP 协议栈为例,来说明“流程”“服务”“功能”的区别和联系
流程
应用层-》传输层-》网络层-》物理层+数据链路层,不管最上层的应用层是什么,这个流程都不会变
服务
对应应用层的 HTTP、 FTP、 SMTP 等服务,HTTP 提供 Web 服务, FTP 提供文件服务,SMTP 提供邮件服务
功能
每个服务都会提供相应的功能。例如,HTTP 服务提供 GET、 POST 功能,FTP 提供上 传下载功能,SMTP提供邮件发送和收取功能
我们再以一个简单的学生信息管理系统为例
面向流程拆分
展示层→业务层→数据层→存储层
图
面向服务拆分
将系统拆分为注册、登录、信息管理、安全设置等服务
图
面向功能拆分
每个服务都可以拆分为更多细粒度的功能
图
不同的拆分方式,本质上决定了系统的扩展方式
可扩展方式
合理的拆分,能够强制保证即使程序员出错,出错的范围也不会太广,影响也不会太大
不同拆分方式应对扩展时的优势如下
面向流程拆分
扩展时大部分情况只需要修改某一层,少部分情况可能修改关联的两层,不会出现所有层都同时要修改
面向服务拆分
对某个服务扩展,或者要增加新的服务时,只需要扩展相关服务即可,无须修改所有的服务
面向功能拆分
对某个功能扩展,或者要增加新的功能时,只需要扩展相关功能即可,无须修改所有的功能
不同的拆分方式,将得到不同的系统架构,典型的可扩展系统架构如下
面向流程拆分: 分层架构
面向服务拆分: SOA、 微服务
面向功能拆分 : 微内核架构
第12章 分层架构
分层架构类型
按照分层架构进行设计时,根据不同的划分维度和对象,可以得到多种不同的分层架构
C/S架构、 BIS架构
划分的对象是整个业务系统,划分的维度是用户交互,即将和用户交互的部分独立为一层,支撑用户交互的后台作为另外一层
MVC 架构、MVP 架构
划分的对象是单个业务子系统,划分的维度是职责,将不同的职责划分到独立层
逻辑分层架构
划分的对象可以是单个业务子系统,也可以是整个业务系统,划分的维度也是职责
逻辑分层架构中的层是自顶向下依赖的
典型的有操作系统内核架构、TCP/IP 架构
Android 操作系统架构
图
典型的 J2EE 系统架构
图
银行存储架构
图
分层架构详解
分层架构设计最核心的一点就是需要保证各层之间的差异足够清晰,边界足够明显
分层架构之所以能够较好地支撑系统扩展,本质在于:隔离关注点 (separation of concerns),即每个层中的组件只会处理本层的逻辑
并不是简单地分层就一定能够实现隔离关注点从而支撑快速扩展,分层时要保证层与层之间的依赖是稳定的,才能真正支撑快速扩展
Linux 内核为了支撑不同的文件系统格式,抽象了 VFS 文件系统接口
图
分层结构的另外一个特点就是层层传递,也就是说一旦分层确定,整个业务流程是按照层进行依次传递的,不能在层之间进行跳跃
分层结构的这种约束,好处在于强制将分层依赖限定为两两依赖,降低了整体系统复杂度
分层结构的代价就是冗余,也就是说,不管这个业务有多么简单,每层都必须要参与处理,甚至可能每层都写了一个简单的包装函数
分层架构的优势就体现在通过分层强制约束两两依赖,一旦自由选择绕过分层,时间一长,架构就会变得混乱
分层架构另外一个典型的缺点就是性能,因为每一次业务请求都需要穿越所有的架构分层,有一些事情是多余的,多少都会有一些性能的浪费
第13章 SOA架构
Service Oriented Architecture(面向服务的架构)
SOA 提出的背景是企业内部的 IT 系统重复建设且效率低下
企业各部门有独立的 IT 系统,比如人力资源系统、财务系统、销售系统,这些系统可能都涉及人员管理,各 IT 系统都需要重复开发人员管理的功能
随着业务的发展,复杂度越来越高,更多的流程和业务需要多个 IT 系统合作完成
各个独立的 IT 系统可能采购于不同的供应商,实现技术不同,企业自己也不太可能基于这些系统进行重构
SOA 详解
SOA 提出了三个关键概念
服务
所有业务功能都是一项服务,服务就意味着要对外提供开放的能力,当其他系统需要使用这项功能时,无须定制化开发
ESB
ESB 的全称是 Enterprise Service Bus,中文翻译为“企业服务总线”
使用 ESB 来屏蔽异构系统对外提供各种不同的接口方式,以此来达到服务间高效的互联互通
松耦合
松辑合的目的是减少各个服务间的依赖和互相影响
典型的 SOA 架构
图
SOA 架构是比较高层级的架构设计理念, -般情况下我们可以说某个企业采用了 SOA 的 架构来构建 IT 系统,但不会说某个独立的系统采用了 SOA 架构
SOA 解决了传统 IT 系统重复建设和扩展效率低的问题,但其本身也引入了更多的复杂性
SOA 最广为人垢病的就是 ESB,ESB 需要实现与各种系统间的协议转换、数据转换、透明的动态路由等功能
当 ESB 承载的消息 太多时, ESB 本身会成为整个系统的性能瓶颈
第14章 微服务
微服务与SOA的关系
关于 SOA 和微服务的关系和区别,大概分为几个典型的观点
微服务是SOA的实现方式
微服务是去掉ESB后的SOA
微服务是一种和 SOA 相似但本质上不同的架构理念
对比
服务粒度
整体上来说,SOA 的服务粒度要粗一些,而微服务的服务粒度要细一些
服务通信
SOA 采用了 ESB 作为服务间通信的关键组件,负责服务定义、服务路由 、消息转换、 消息传递,总体上是重量级的实现
微服务推荐使用统一的协议和格式,例如,RSTful 协议、RPC协议,无须ESB这样的重量级实现
MartinFlower将微服务架构的服务通信理念称为“ Smart endpoints and dumb pipes,简单翻译为“聪明的终端,愚蠢的管道”
微服务的“ dumb pipes”仅仅做消息传递,对消息格式和内容一无所知
服务交付
SOA对服务的交付并没有特殊要求,因为 SOA 更多考虑的是兼容己有的系统
微服务的架构理念要求“快速交付”,相应地要求采取自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践
应用场景
SOA 更加适合于庞大、复杂、异构的企业级系统,只能采用兼容的方式进行处理,而承担兼容任务的就是 ESB
微服务更加适合于快速、轻量级、基于 Web 的互联网系统,这类系统业务变化快,需要快速尝试、快速交付
SOA 和微服务对比总结
图
因此两者的关系应该是第三种模式
smal、 lightweight、automated,基本上浓缩了微服务的精华,也是微服务与 SOA 的本质区别所在
微服务的陷阱
既不考虑团队的规模,也不考虑业务的发展,也没有考虑基础技术的支撑
微服务具体有哪些坑
服务划分过细,服务间关系复杂
服务划分过细,单个服务的复杂度确实下降了,但整个系统的复杂度却上升了,因为微服务将系统内的复杂度转移为系统间的复杂度了
服务数量太多,团队效率急剧下降
调用链太长,性能下降
调用链太长,问题定位困难
没有自动化支撑,无法快速交付
没有服务治理,微服务数量多了后管理混乱
微服务最佳实践
微服务的坑可以提炼为以下几点
微服务拆分过细,过分强调“ small”
微服务基础设施不健全,忽略了“ automated”
微服务并不轻量级,规模大了后,“ lightweight”不再适应
服务粒度
建议基于团队规模进行拆分
首先,从系统规模来讲, 3 个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能够进行分工的粒度
其次,从团队管理来说,3 个人可以形成一命稳定的备份
最后,从技术提升的角度来讲, 3 个人的技术小组既能够形成有效的讨论,又能够快速达成一致意见
拆分方法
基于业务逻辑拆分
将系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务
基于可扩展拆分
将系统中的业务模块按照稳定性进行排序,将己经成熟和改动不大的服务拆分为稳定服务, 将经常变化和法代的服务拆分为变动服务
基于可靠性拆分
将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用
这样拆分带来如下几个好处
避免非核心服务故障影响核心服务
核心服务高可用方案可以更简单
能够降低高可用成本
基于性能拆分
将性能要求高或性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务
基础设施
图
自动化测试
自动化部署
配置中心
接口框架
API网关
服务发现
【自理式】
【代理式】
服务路由
服务容错
【请求重试】
【流控】
【服务隔离】
服务监控
服务跟踪
服务安全
服务安全主要分为三部分
接入安全
接入安全指只有经过允许,某个微服务才能访 问另外一个微服务,否则被访问的微服务会直接拒绝服务
数据安全
数据安全指某些数据相关的操作只允许授权的微服务进行访问,否则被访问的微服务会拒绝数据操作
传输安全
传输安全指某些敏感数据在传输过程中需要进行防窃取、防篡改处理 ,以保证数据的真实性和有效性
通常情况下,服务安全可以集成到配置中心系统中进行实现,即配置中心配置微服务的接入安全策略和数据安全策略,微服务节点从配置中心获取这些配置信息,然后在处理具体的微服务调用请求时根据安全策略进行处理
第15章 微内核架构
Microkernel Architecture,中文翻译为微内核架构,也被称为插件化架构( Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构
通常用于实现基于产品(英文原文为 product-based,指存在多个版本、需要下载安装才能使用,与 web-based 相对应)的应用
微内核架构包含两类组件:核心系统( core system)和插件模块( plug-in modules)
核心系统负责和具体业务功能无关的通用功能
例如模块加载、模块间通信等
插件模块负责实现具体的业务逻辑
例如我们前面提到的“ 学生信息管理”系统 中的“手机号注册”功能
基本架构示意图
核心系统功能比较稳定,不会因为业务功能扩展而不断修改,插件模块可以根据业务功能的需要不断地扩展
微内核架构通过隔离变化到插件的方式提供了灵活性、可扩展性
设计关键点
插件管理
核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件
常见的实现方法是插件注册表机制
核心系统提供插件注册表,插件注册表含有每个插件模块的信息,包括它的名字、位置、加载时机
插件连接
插件连接指插件如何连接到核心系统
通常来说,核心系统必须制定插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可
常见的连接机制有OSGi(Eclipse使用)、消息模式、依赖注入(Spring使用),甚至使用分布式的协议都是可以的,比如 RPC 或 HTTP Web 的方式
插件通信
插件通信指插件间的通信
虽然设计的时候插件间是完全解稿的,但实际业务运行过程中,必然会出现某个业务流程需要多个插件协作,这就要求两个插件间进行通信
由于插件之间没有直接联系,通信必须通过核心系统,因此核心系统需要提供插件通信机制
微内核的核心系统也必须提供类似的通信机制,各个插件之间才能进行正常的通信
OSGi 架构简析
OSGi 的全称是 Open Services Gateway initiative
OSGi 成为首选的插件化标准
OSGi是一个插件化的标准,而不是一个可运行的框架
Eclipse 采用的OSGi框架称为 Equinox, 类似的实现还有 Apache 的 Felix、 Spring 的 SpringDM
OSGi 框架的逻辑架构图
模块层( Module 层)
模块层完成插件管理功能
OSGi 中,插件被称为 Bundle,每个 Bundle 是一个 Java 的 JAR 文件,每个 Bundle 里面都包含一个元数据文件 MANIFEST.MF,这个文件包含了 Bundle 的基本信息
如,Bundle 的名称、描述、开发商、classpath,以及需要导入的包和输出的包,等等
OSGi 核心系统会将这些信息加载到系统中用于后续使用
生命周期层( Lifecycle层)
生命周期层完成插件连接功能,提供了执行时模块管理、模块对底层 OSGi 框架的访问
生命周期层精确地定义了 bundle 生命周期的操作(安装、更新、启动、停止、卸载), Bundle 必须按照规范实现各个操作
服务层( Service层)
服务层完成插件通信的功能
OSGi 提供了一个服务注册的功能,用于各个插件将自己能提供的服务注册到 OSGi 核心的服务注册中心,如果某个服务想用其他服务,则直接在服务注册中心搜索可用服务就可以了
规则引擎架构简析
规则引擎从结构上来看也属于微内核架构的一种具体实现
其中执行引擎可以看作微内核,执行引擎解析配置好的业务流,执行其中的条件和规则,通过这种方式来支持业务的灵活多变
规则引擎在计费、保险、促销等业务领域应用较多
如,电商促销,常见的促销规则有
图
规则引擎却能够很灵活地应对这种需求,主要原因在于
可扩展
通过引入规则引擎,业务逻辑实现与业务系统分离,可以在不改动业务系统的情况下扩展新的业务功能
易理解
规则通过自然语言描述,业务人员易于理解和操作,而不像代码那样只有程序员才能理解和开发
高效率
规则引擎系统一般提供可视化的规则定制、审批、查询及管理,方便业务人员快速配置新的业务
规则引擎的基本架构
1、开发人员将业务功能分解提炼为多个规则,将规则保存在规则库中
2、业务人员根据业务需要,通过将规则排列组合,配置成业务流程,保存在业务库中
3、规则引擎执行业务流程,实现业务功能
对照微内核架构的设计关键点,我们来看看规则引擎是具体是如何实现的
插件管理
规则引擎中的规则就是微内核架构的插件,引擎就是微内核架构的内核
规则可以被引擎加载和执行
在规则引擎架构中,规则一般保存在规则库中,通常使用数据库来存储
插件连接
规则引擎的插件连接实现机制其实就是规则语言
规则引擎规定了规则开发的语言,业务人员需要基于规则语言来编写规则文件,然后由规则引擎加载执行规则文件来完成业务功能
插件通讯
规则引擎的规则之间进行通信的方式就是数据流和事件流
由于单个规则并不需要依赖其他规则 ,因此规则之间没有主动的通信,规则只需要输出数据或事件,由引擎将数据或事件传递到下一个规则
目前最常用的规则引擎是开源的 JBoss Drools,采用 Java 语言编写, 基于 Rete 算法
第一部分 概念和基础
第1章 架构基础
“架构到底是什么”
系统与子系统
关联
规则
能力
模块与组件
模块(Module)
从逻辑的角度来拆分后得到的单元就是“模块”
划分模块的主要目的是职责分离
组件(component)
从物理的角度来拆分系统得到的单元就是“组件”
划分组件的主要目的是单元复用
“组件”的英文单词 component 对应中文的“零件” 一词,“零件”更容易理解一些,“零件”是一个物理的概念 , 并且具备“独立且可替换”的特点
举例
假设我们要做一个学生信息管理系统
这个系统从逻辑的角度来拆分,可以分为“登录注册模块”“个人信息模块”“个人成绩模块”
从物理的角度来拆分,可以拆分为 Nginx、 Web 服务器、 MySQL
框架与架构
框架(Framework)
关注的是“规范”
架构(Architecture)
关注的是“结构”
重新定义架构
软件架构指软件系统的顶层结构
架构设计的目的
架构设计的误区
因为架构很重要,所以要做架构设计
不是每个系统都要做架构设计吗
公司流程要求系统开发过程中必须有架构设计
为了高性能、高可用、可扩展, 所以要做架构设计
以史为鉴
探索一个事物的目的,最好的方式就是去追寻这个事物出现的历史背景和推动因素
机器语言
主要问题是三难 :太难写、太难读、太难改
汇编语言
除了编写本身复杂,还有另外一个复杂的地方在于:不同 CPU 的汇编指令和结构是不同的
高级语言
高级语言可以被编译为适合不同 CPU 指令的机器语言
第一次软件危机与结构化程序设计
结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软件的复杂度控制在一定范围内
第二次软件危机与面向对象
第一次软件危机的根源在于软件的“逻辑”变得非常复杂,而第二次软件危机主要体现在软件的“扩展” 变得非常复杂
软件架构
第一次软件危机引出了“结构化编程”, 创造了“模块”概念;
第二次软件危机引出了“面向对象编程”,创造了“对象”概念
“软件架构”开始流行,创造了“组件”概念
“模块”“对象”“组件”本质上都是对达到一定规模的软件进行拆分,差别只是在于随着软件的复杂度不断增加,拆分的粒度越来越粗,拆分的角度越来越高
架构设计的真正目的
整个软件技术发展的历史,其实就是一部与“复杂度” 斗争的历史,架构的出现也不例外
架构设计的主要目的是为了解决复杂度带来的问题
架构设计是为了解决软件复杂度
复杂度来源
高性能
复杂度主要体现在两方面
单台计算机内部为了高性能带来的复杂度
单机复杂度
计算机内部复杂度最关键的地方就是操作系统
多进程、多线程、进程间通信、多线程并发等
多台计算机集群为了高性能带来的复杂度
集群的复杂度
任务分配(集群)
指每台机器都可以处理完整的业务任务,不同的任务分配到不同的机器上执行
增加一台业务服务器
图
需要增加一个任务分配器
硬件网络设备(例如, F5、交换机等)
软件网络设备(例如, LVS )
负载均衡软件(例如, Nginx、 HAProxy)
自己开发的系统
任务分配器和真正的业务服务器之间有连接和交互
连接建立、连接检测、连接中断后如何处理等
任务分配器需要增加分配算法
采用轮询算法,还是按权重分配,又或者按照负载进行分配
如果按照服务器的负载进行分配,则业务服务器还要能够上报自己的状态给任务分配器
增加N台业务服务器
图
任务分配器从 1 台变成了多台,这个变化带来的复杂度就是需要将不同的用户分配到不同的任务分配器上
常见的方法包括 DNS 轮询、智能 DNS、CDN(内容分发网络,ContentD巳liveryNetwork)、 GSLB 设备(全局负载均衡,Global Server Load Balance)等
任务分配器和业务服务器的连接从简单的“ 1 对多”(l 台任务分配器连接多台业务服务器)变成了“多对多”(多台任务分配器连接多台业务服务器)的网状结构。
机器数量从 3 台扩展到 30 台,状态管理、故障处理复杂度也大大增加
任务分解(拆系统
通过任务分配的方式,我们能够突破单台机器处理性能的瓶颈,通过增加更多的机器来满足业务的性能需求
如果业务本身也越来越复杂,单纯只通过任务分配的方式来扩展性能, 收益会越来越低
如果业务越来越复杂,1 台机器扩展到 10 台,性能可能只提升了 5 倍。造成这种现象的主要原因是业务越来越复杂,单台机器处理的性能会越来越低
图
后台架构从逻辑上将各个子业务进行了拆分
把原来大一统但复杂的业务系统,拆分成小而简单但需要多个系统配合的业务系统
为何通过任务分解就能够提升性能呢?
简单的系统更容易做到高性能
系统的功能越简单,影响性能的点就越少,就更容易进行有针对性的优化
可以针对单个任务进行扩展
任务分解带来的性能收益是有一个度的,并不是任务分解得越细越好
高可用
本质上都是通过“冗余”来实现高可用
高性能增加机器的目的在于“扩展”处理性能 : 高可用增加机器的目的在于“冗余”处理单元
复杂性
计算高可用
单机变双机
图
需要增加一个任务分配器
任务分配器和真正的业务服务器之间有连接和交互,需要选择合适的连接方式 ,并且对连接进行管理
任务分配器需要增加分配算法
双机算法有主备、主主
主备方案又可以细分为冷备、温备、热备
高可用集群架构
图
分配算法更加复杂,可以是 I 主 3 备、 2 主 2 备、 3 主 l 备、 4 主 0 备
存储高可用
图
传输速度限制
传输线路本身也存在可用性问题
存储无法进行同步,整个系统的数据是不一致的
无论正常情况下的传输延迟,还是异常情况下的传输中断,都会导致系统的数据在某个时间点或时间段是不一致的,而数据的不一致又会导致业务问题
存储高可用的难点不在于如何备份数据,而在于如何减少或规避数据不一致对业务造成的影晌
高可用状态决策
【独裁式】
图
当决策者本身故障时,整个系统就无法实现准确的状态决策
【协商式】
图
如果两者的信息交换出现问题(比如主备连接中断),此时状态决策应该怎么做
【民主式】
图
可扩展性
设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化
预测变化
复杂性
不能每个设计点都考虑可扩展性
不能完全不考虑可扩展性
所有的预测都存在出错的可能性
应对变化
第一种应对变化的常见方案是将“变化”封装在一个“变化层 ”,将不变的部分封装在一个独立的“稳定层”
通过剥离变化层和稳定层的方式应对变化
系统需要拆分出变化层和稳定层
需要设计变化层和稳定层之间的接口
第二种常见的应对变化的方案是提炼出一个“抽象层”和一个“实现层”
低成本
低成本本质上是与高性能和高可用冲突的
低成本很多时候不会是架构设计的首要目标,而是架构设计的附加约束
往往只有“创新”才能达到低成本目标
NoSQL (Memcache、 Redis 等)的出现是为了解决关系型数据库无法应对高并发访问带来的访问压力
全文搜索引擎( Sphinx、 Elasticsearch、 Solr)的出现是为了解决关系型数据库 like 搜索的低效的问题
Hadoop 的出现是为了解决传统文件系统无法应对海量数据存储和计算的问题
安全
对业务和企业形象影响非常大
安全可以分为两类: 一类是功能上的安全,另一类是架构上的安全
【功能安全】
功能安全是“防小偷”
小偷会翻墙、开锁、爬窗、钻狗洞进入我们的房子;黑客和小偷的手法都是利用系统不完善的地方潜入系统进行破坏或盗取
常见的 XSS攻击、CSRF攻击、SQL 注入、Windows 漏洞、密码破解等,本质上是因为系统实现有漏洞,黑客有了可乘之机
从实现的角度来看,功能安全更多的是和具体的编码相关,与架构关系不大
我们永远无法预测系统下一个漏洞在哪里,也不敢说自己的系统肯定没有任何问题,所以功能安全是一个“攻”与“防”的矛盾,只能在这种攻防大战中逐步完善,不可能在系统架构设计的时候一劳永逸地解决
【架构安全】
架构安全是“防强盗”
强盗会直接用锤子将大门砸开,或者用炸药将围墙炸倒
小偷是偷东西,而强盗很多时候就是故意搞破坏,对系统的影响也大得多
传统的架构安全主要依靠防火墙,防火墙最基本的功能就是隔离网络,通过将网络划分成不同的区域,制定出不同区域之间的访问控制策略,来控制不同信任程度区域间传送的数据流
图
防火墙的功能虽然强大,但性能一般,所以在传统的银行和企业应用领域应用较多。但在互联网领域,防火墙的应用场景并不多
更多是依靠运营商或云服务商强大的带宽和流量清洗的能力,较少自己来设计和实现
规模
规模带来复杂度的主要原因就是“ 量变引起质变”,当数量超过一定的阔值后,复杂度会发生质的变化
功能越来越多,导致系统复杂度指数级上升
数据越来越多,系统复杂度发生质变
目前的大数据理论基础是 Google 发表的三篇大数据相关论文
Google File System 是大数据文件存储的技术理论
Google BigTable 是列式数据存储的技术理论
Google MapReduce 是大数据运算的技术理论
本章小节
第2章 架构设计原则
合适原则
原则宣言:“合适优于业界领先”
没那么多人,却想干那么多活,是失败的第一个主要原因
没有那么多积累,却想一步登天,是失败的第二个主要原因
没有那么卓越的业务场景,却幻想灵光一闪成为天才,是失败的第三个主要原因
真正优秀的架构都是在企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起井发挥出最大功效,井且能够快速落地
简单原则
原则宣言 : “简单优于复杂”
结构的复杂性
组成复杂系统的组件数量更多
组件之间的关系也更加复杂
逻辑复杂性
架构设计时如果简单的方案和复杂的方案都可以满足需求,一定要选择简单的方案,
演化原则
原则宣言: “演化优于一步到位”
对于建筑来说,永恒是主题;而对于软件来说,变化才是主题!
软件架构需要根据业务发展不断变化
应该认真分析当前业务的特点,明确业务面临的主要问题,设计合理的架构,快速落地以满足业务需要,然后在运行过程中不断完善架构,不断随着业务演化架构
本章小节
第3章 架构设计流程
有的放矢 识别复杂度
正确的做法是将主要的复杂度问题列出来,然后根据业务、技术、 团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题
按图索骥 设计备选方案
基于已有的技术或架构模式进行组合,然后调整
第一种常见的错误 : 设计最优秀的方案!
选合适自己业务、团队、技术能力的方案才是好方案
第二种常见的错误:只做一个方案!
做备选方案
备选方案的数量以 3 ~ 5 个为最佳
备选方案的差异要比较明显
选方案的技术不要只局限于己经熟悉的技术
第三种常见的错误:备选方案过于详细
正确的做法是备选阶段关注的是技术选型,而不是技术细节,技术选型的差异要比较明显
深思熟虑 评估和选择备选方案(360 度环评)
业务背景
一个垂直电商网站
备选方案设计
【方案 1:横向扩展】
加机器
【方案 2:系统拆分 】
拆服务
备选方案360度环评
分析本次方案设计需要关注的架构质量属性
最终的质量属性包括性能、复杂度、成本、可扩展 、可用性
图
看似正确但实际错误的做法
数量对比法
问题在于把所有质量属性的重要性等同,而没有考虑质量属性的优先级
加权法
问题是无法客观地给出每个质量属性的权重得分
正确的做法是“按优先级选择”
即设计师综合当前的业务发展情况、团队人员规模和技能、业务发展预测等因素,将质量属性按照优先级排序,首先挑选满足第一优先级的,如果方案都满足,那就再看第二优先级......以此类推
集群方案”不可避免地会遇到 MySQL单点问题和单体系统不方便扩展的问题,最终还是要演化到“拆分架构”
精雕细琢 详细方案设计
详细方案设计就是将方案涉及的关键技术细节给确定下来
架构师不但要进行备选方案设计和选型 ,还需要对备选方案的关键细节有较深入的理解
汇集团队经验,减少思维和经验盲区
本章小节
第三部分 高可用架构模式
第6章 CAP
CAP 理论
对于一个分布式计算系统,不可能同时满足一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三个设计约束
在一个分布式系统中(指互相连接并共享数据的节点的集合),当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲
一致性(Consistency)
客户端读操作能够获取最新的写结果
可用性(Availability)
非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)
分区容忍性(Partition Tolerence)
当出现网络分区后,系统能够继续“履行职责”
CAP 应用
放到分布式环境下来思考,我们会发现必须选择 P (分区容忍)要素,因为网络本身无法做到 100%可靠,有可能出故障,所以分区是一个必然的现象
分布式系统理论上不可能选择 CA 架构,只能选择 CP 或 AP 架构
CP
如下图所示,为了保证一致性,当发生分区现象后,N1 节点上的数据己经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 需要返回 Error,提示客户端 C “系统现在发生了错误”,这种处理方式违背了可用性 Availability 的要求,因此 CAP 三者只能满足 CP
示例图
AP
如下图所示,为了保证可用性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由 于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时, N2 将当前自己拥有的数据 x 返回给客户端 C 了,而实际上当前最新的数据己经是 y 了,这就不满足一致性 Consistency 的要求了,因此 CAP 三者只能满足 AP
示例图
CAP 细节
CAP关注的粒度是数据,而不是整个系统
在实际设计过程中,每个系统不可能只处理一种数据,而是包含多种类型的数据,有的数据必须选择 CP,有的数据必须选择 AP。 而如果我们做设计时,从整个系统的角度去选择 CP 还是 AP,就会发现顾此失彼,无论怎么做都是有问题的
所以在 CAP 理论落地实践时,我们需要将系统内的数据按照不同的应用场景和要求进行分类,每类数据选择不同的策略(CP 还是 AP ),而不是直接限定整个系统所有数据都是同一策略
CAP 是忽略网络延迟的
实际情况下,从节点 A 复制数据到节点 B, 总是需要花费一定时间的
CAP 理论中的 C 在实践中是不可能完美实现的,在数据复制的过程中,节点 A 和节点 B 的数据并不一致
因此单个用户的余额、单个商品的库存,理论上要求选择 CP 而实际上 CP 都做不到,只能选择 CA。也就是说,只能单点写入,其他节点做备份,无法做到分布式情况下多点写入???
正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA
要求架构设计的时候既要考虑分区发生时选择 CP 还是 AP,也要考虑分区没有发生时如何保证 CA
放弃并不等于什么都不做,需要为分区恢复后做准备
分区期间放弃 C或 A,并不意味着永远放弃 C 和 A,我们可以在分区期间进行一些操作,从而让分区故障解决后,系统能够重新达到 CA 的状态
最典型的就是在分区期间记录一些日志,当分区故障解决后,系统根据日志进行数据恢复,使得重新达到CA状态
ACID、BASE
当谈到数据一致性时, CAP、 ACID、 BASE 难免都会被拿出来进行讨论
ACID
原子性(Atomicity)
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节
事务在执行过程中发生错误,会被回滚到事务开始前 的状态, 就像这个事务从来没有执行过 一样
一致性(Consistency)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏
隔离型(Isolation)
数据库允许多个并发事务同时对数据进行读写和修改的能力
隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
事务隔离级别
读未提交(Read Uncommitted)
读已提交(Read Committed)
可重复读(Repeatable Read)
串行化(Serializable)
持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
ACID 中的 C 是指数据库的数据完整性, CAP 中的 C 是指分布式节点中的数据一致性
结合 ACID 的应用场景是数据库事务, CAP 关注的是分布式系统数据读写这个差异点
BASE
BASE 是指基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventual Consistency)
其核心思想是即使无法做到强一致性( CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性 (Eventual Consistency)
基本可用(Basically Available)
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
软状态(Soft State)
允许系统存在中间状态,而该中间状态不会影响系统整体可用性
这里的中间状态就是 CAP 理论中的数据不一致
最终一致性(Eventual Consistency)
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
BASE理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP方案的一个补充
CAP 理论是忽略延时的,而实际应用中延时是无法避免的
AP 方案中牺牲一致性只是指分区期间,而不是永远放弃一致性
综合上面的分析, ACID 是数据库事务完整性的理论, CAP 是分布式系统设计理论, BASE 是 CAP 理论中 AP 方案的延伸
第7章 FEMA
FMEA
Failure mode and effects analysis,故障模式与影响分析
一种在各行各业都有广泛应用的可用性分析方法
通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响
FMEA 并不是指导我们如何做架构设计,而是当我们设计出一个架构后,再使用 FMEA 对这个架构进行分析,看看架构是否还存在某些可用性的隐患
FMEA 方法
在架构设计领域, FMEA 的具体分析方法如下
给出初始的架构设计图
假设架构中某个部件发生故障
分析此故障对系统功能造成的影响
根据分析结果,判断架构是否需要进行优化
常见的 FMEA 分析表格包含如下
功能点
这里的“功能点”指的是从用户的角度来看的,而不是从系统各个模块功能点划分来看的
故障模式
故障模式指的是系统会出现什么样的故障,包括故障点和故障形式
这里的故障模式并不需要给出真正的故障原因,我们只需要假设出现某种故障现象即可
因为在实际应用过程中,不管哪种原因,只要现象是一样的,对业务的影响就是一样的
故障影响
当发生故障模式中描述的故障时,功能点具体会受到什么影响
严重程度
严重程度指站在业务的角度,故障的影响程度,一般分为“致命/高/中/低/无”五个档次
故障原因
“故障模式”中只描述了故障的现象,并没有单独列出故障原因
故障概率
指某个具体故障原因发生的概率
风险程度
风险程度就是综合严重程度和故障概率来一起判断某个故障的最终等级,风险程度=严重程度×故障概率
己有措施
针对具体的故障原因,系统现在是否提供了某些措施来应对,包括检测告警、容错、自恢复等
检测告警
最简单的措施就是检测故障,然后告警,系统自己不针对故障进行处理,需要人工干预。
容错
检测到故障后,系统能够通过备份手段应对
自恢复
检测到故障后,系统能够自己恢复
规避措施
规避措施指为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段
解决措施
解决措施指为了能够解决问题而做的一些事情,一般都是技术手段
为了解决密码暴力破解,增加密码重试次数限制
为了解决拖库导致数据泄露,将数据库中的敏感数据加密保存
为了解决非法访问,增加白名单控制
后续规划
针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划
FMEA 实战
图
第8章 存储高可用
存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题
要从以下几个方面去进行思考和分析
数据如何复制
各个节点的职责是什么
如何应对复制延迟
如何应对复制中断
常见的高可用存储架构有主备、主从、主主、集群、分区
主备复制
基本实现
图
主备方案详细设计如下
主机存储数据,通过复制通道将数据复制到备机
常情况下,客户端无论读写操作 ,都发送给主机,备机不对外提供任何读写服务
主机故障情况下,客户端不会自动将请求发给备机,此时整个系统处于不可用状态,不能读写数据,但数据并没有全部丢失, 因为备机上有数据
如果主机能够恢复(不管是人工恢复还是自动恢复),客户端继续访问主机,主机继续将数据复制给备机
如果主机不能恢复(例如,机器硬盘损坏,短时间内无法恢复), 则需要人工操作, 将备机升为主机,然后让客户端访问新的主机(即原来的备机);同时,为了继续保持主备架构, 需要人工增加新的机器作为备机
主机不能恢复的情况下,成功写入了主机但还没有复制到备机的数据会丢失,需要人工进行排查和恢复,也许有的数据就永远丢失了,业务上需要考虑如何应对此类风险
如果主备间数据复制延迟,由于备机并不对外提供读写操作,因此对业务没有影响,但如果延迟较多,恰好此时主机又宕机了,则可能丢失较多数据 ,因此对于复制延迟也不能掉以轻心。 一般的做法是做复制延迟的监控措施,当延迟的数据量较大时及时报警,由人工干预处理
主备架构中的“备机”主要还是起到 一个备份作用,并不承担实际的业务读写操作
优缺点分析
优点
对于客户端来说,不需要感知备机的存在
双方只需要进行数据复制即可,无须进行状态判断和主备倒换这类复杂的操作
缺点
备机仅仅只为备份,并没有提供读写操作,硬件成本上有浪费
故障后需要人工干预,无法自动恢复
内部的后台管理系统使用主备复制架构的情况会比较多
主从复制
主机负责读写操作,从机只负责读操作,不负责写操作
基本实现
图
主从复制方案详细解释如下
主机存储数据,通过复制通道将数据复制到从机
正常情况下,客户端写操作发送给主机,读操作可发送给主机也可以发送给从机
主机故障情况下,客户端无法进行写操作,但可以将读操作发送给从机,此时和写操作相关的业务不可用但和读操作相关的操作不受影响
如果主机能够恢复(不管是人工恢复还是自动恢复),客户端继续将写操作请求发送给主机,主机继续将数据复制给备机
如果主机不能恢复(例如,机器硬盘损坏,短时间内无法恢复), 则 需要人工操作, 将备机升为主机,然后让客户端访问新的主机(即原来的备机);同时,为了继续保持主备架构,需要人工增加新的机器作为备机
主机不能恢复的情况下,成功写入了主机但还没有复制到备机的数据会丢失,需要人
工进行排查和恢复,也许有的数据就永远丢失了,业务上需要考虑如何应对此类风险
工进行排查和恢复,也许有的数据就永远丢失了,业务上需要考虑如何应对此类风险
如果主从间数据复制延迟,则会出现主从读取的数据不一致的问题
如果主从间延迟较多,恰好此时主机又宕机了,则可能丢失较多数据,因此对于复制延迟也不能掉以轻心 。一般的做法是做复制延迟的监控措施,当延迟的数据 量较大 时及时报警,由人工干预处理
优缺点分析
主从复制在主机故障时,读操作相关的业务不受影响
主从复制架构的从机提供读操作,发挥了硬件的性能
主从复制要比主备复制复杂更多,主要体现在客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理
主从复制同样具备和主备复制一样的缺点:故障时需要人工干预
写少读多的业务使用主从复制的存储架构比较多
主备倒换与主从倒换
设计关键
主备复制和主从复制方案存在两个共性的问题
主机故障后,无法进行写操作
如果主机无法恢复,需要人工指定新的主机角色
主备倒换和主从倒换方案就是为了解决上述两个问题而产生的
即系统自动决定主机角色, 并完成角色切换
几个关键的设计点
主备间状态判断
状态传递的渠道
是相互间互相连接,还是第三方仲裁?
状态检测的内容
例如,机器是否掉电,进程是否存在,响应是否缓慢等等
倒换决策
倒换时机
什么情况下备机应该升级为主机? 是机器掉电后备机才升级,还是主机上的进程不存在就 升级,还是主机响应时间超过 2s 就升级,还是 3 分钟内主机连续重启 3 次就升级等等
倒换策略
原来的主机故障恢复后,要再次倒换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机 ?
自动程度
倒换是完全自动的,还是半自动的?
数据冲突解决
常见架构
根据状态传递渠道的不同
互连式
图
互连式主备倒换主要的缺点在于 : 如果状态传递的通道本身有故障,那么备机也会认为主机故障了从而将自己升级为主机,而此时主机并没有故障,最终就可能出现两个主机
中介式
图
连接管理更简单
状态决策更简单
虽然中介式架构在状态传递和状态决策上更加简单,但并不意味着这种优点是没有代价的, 其关键代价就在于如何实现中介本身的高可用
模拟式
图
模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模
拟的读写操作,根据读写操作的响应情况来判断主机的状态
拟的读写操作,根据读写操作的响应情况来判断主机的状态
模拟式倒换与互连式倒换相比,具有如下优缺点
实现更加简单, 因为省去了状态传递通道的建立和管理工作
模拟式读写操作获取的状态信息没有互连式那样多样,基于有限的状态来做状态决策,可能出现偏差
主主复制
图
主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作
相比主备倒换架构,主主复制架构具有如下特点
两台都是主机,不存在倒换的概念
客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以
主主复制架构从总体上来看要简单很多,无须状态信息传递,也无须状态决策和状态切换
有其独特的复杂性
如果采取主主复制架构,必须保证数据能够双向复制,而很多数据是不能双向复制的
一般适合于那些临时性、可丢失、可覆盖的数据场景
数据集群
主备、主从、 主主架构本质上都有一个隐含的假设:主机能够存储所有数据,但主机本身的存储和处理能力肯定是有极限的
数据集中集群
图
数据集中集群与主备、主从这类架构相似,我们也可以称数据集中集群为1主多备或1主多从
数据都只能往主机中写
复杂度整体上更高一些,具体体现在
主机如何将数据复制给备机
多条复制通道首先会增大主机复制的压力
多条复制通道可能会导致多个备机之间数据不一致
备机如何检测主机状态
多台备机都需要对主机状态进行判断,而不同的备机判断的结果可能是不同的
主机故障后,如何决定新的主机
有多台备机都可以升级为主机,但实际上只能允许一台备机升级为主机
目前开源的数据集中式集群以 Zookeeper 为典型, ZooKeeper 通过 ZAB 协议来解决上述提到的几个问题
一般来说,数据集中式集群适合数据量不大,集群机器数量不多的场景
ZooKeeper 集群,一 般推荐 5 台机器左右,数据量是单台服务器就能够支撑
数据分散集群
每台服务器都会负责存储一部分数据,每台服务器又会备份一部分数据
复杂点在于如何将数据分配到不同的服务器上
算法需要考虑如下设计点
均衡性
算法需要保证服务器上的数据分区基本是均衡的
容错性
当出现部分服务器故障时,算法需要将原来分配给故障服务器的数据分区分配给其他服务器
可伸缩性
当集群容量不够,扩充新的服务器后,算法能够自动将部分数据分区迁移到新服务器, 并保证扩容后所有服务器的均衡性
数据分散集群和数据集中集群的不同点:在于数据分散集群中的每台服务器都可以处理读写请求,因此不存在数据集中集群中负责写的主机那样的角色。但在数据分区集群中,必须有一个角色来负责执行数据分配算法,这个角色可以是独立的一台服务器,也可以是集群自己选举出的一台服务器
数据集中集群架构中,客户端只能将数据写到主机;数据分散集群架构中,客户端可以向任意服务器中读写数据
据分散式集群,由于其良好的可伸缩性,适合业务数据量巨大,集群机器数量庞大的业务场景
Hadoop集群、HBase集群
分布式事务算法
2PC(Two-phase commit protocol)
二阶段提交算法主要由两个阶段组成,分别是 Commit 请求阶段和 Commit 提交阶段
二阶段提交算法的成立基于以下假设
在分布式系统中,存在一个节点作为协调者( Coordinator),其他节点作为参与者(Cohorts),且节点之间可以进行网络通信
所有节点都采用预写式日志,且日志被写入后即保持在可靠的存储设备上,即使节点损坏,也不会导致日志数据的消失
所有节点不会永久性损坏,即使损坏,仍然可以恢复
算法基本说明
第一阶段(提交请求阶段)【投票阶段】
图
协调者向所有参与者发送 QUERY TO COMMIT 消息,询问是否可以执行提交事务,并开始等待各参与者的响应
参与者执行询问发起为止的所有事务操作,并将 Undo 信息和 Redo 信息写入日志,返回 Yes 消息给协调者;如果参与者执行失败,则返回 No 消息给协调者
第二阶段(提交执行阶段)
图
【成功】
当协调者从所有参与者获得的相应消息都为“ Yes”时
协调者向所有参与者发出 “COMMIT” 的请求
参与者完成 COMMIT 操作,并释放在整个事务期间占用的资源
参与者向协调者发送“ ACK”消息
协调者收到所有参与者反馈的“ACK"消息后,完成事务
【失败】
当任一参与者在第一阶段返回的响应消息为“No”,或者协调者在第一阶段的询问超时之前无法获取所有参与者的响应消息时
协调者向所有参与者发出“ROLLBACK”的请求
参与者利用之前写入的 Undo 信息执行回滚,并释放在整个事务期间占用的资源
参与者向协调者发送“ ACK”消息
协调者收到所有参与者反馈的“ ACK”消息后,取消事务
优缺点
2PC 是强一致性算法,优点是实现简单
缺点
同步阻塞
协调者与参与者互相等待对方的响应消息,难以支撑高并发的应用场景
状态不一致
极端情况下无论怎么处理都可能出现状态不一致的情况
单点故障
协调者是整个算法的单点,如果协调者故障,则参与者会一直阻塞下去
3PC(Three-phase commit protocol)
三阶段提交算法是针对二阶段提交算法在的“单点故障”而提出的解决方案
通过在二阶段提交算法中的第一阶段和第二阶段之间插入一个新的阶段“准备阶段”,当协调者故障后,参与者可以通过超时提交来避免一直阻塞
具体算法描述
第一阶段(提交判断阶段)
协调者向参与者发送 canCommit 消息,询问参与者是否可以提交事务
参与者收到 canCommit 消息后,判别自己是否可以提交该事务,如果可以执行就返回 yes,不可以则返回 no
如果协调者收到任何一个 no 或参与者超时,则事务终止,同时会通知参与者终止事务,如果在超时时间内收到所有 yes,则进入第二阶段
第二阶段( 准备提交阶段)
协调者发送 preCommit 消息给所有参与者,告诉参与者准备提交
参与者收到 preCommit消息后,执行事务操作, 将 undo和 redo信息记录到事务日志中, 然后返回ACK消息
第三阶段(提交执行阶段)
协调者在接收到所有 ACK 消息后会发送 doCommit, 告诉参与者正式提交;否则会给参与者发出终止消息,事务回滚
参与者收到 doCommit 消息后提交事务,然后返回 haveCommitted消息
如果参与者收到一个 preComrnit 消息并返回了 ACK,但等待 doComrnit 消息超时(例如协调者崩溃或超时), 参与者则会在超时后继续提交事务
分布式一致性算法
分布式事务算法的主要目的是为了保证分散在多个节点上的数据统一提交或回滚,以满足 ACID 的要求
分布式一致性算法的主要目的是为了保证同一份数据在多个节点上的一致性,以满足 CAP 中的 CP 要求
复制状态机是实现分布式一致性的常用技术
主要角色有三个
副本
多个分布式服务器组成一个集群,每个服务器都包含完整状态机的一个副本
状态机
状态机接受输入,然后执行操作,将状态改变为下 一个状态
算法
使用算法来协调各个副本的处理逻辑,使得副本的状态机保持一致
复制状态机的核心就是分布式一致性算法
Paxos
Paxos 存在两个很明显的问题
特别复杂
缺失很多细节,难以实现
在工程实践中实现 Paxos 算法的一般步骤如下
以 Paxos算法为基础,开始尝试实现
发现有的地方很难实现,或者没有明确规定如何实现,于是想了一个方案去实施
最终看起来完成了 Paxos 算法 ,但算法的正确性已经无法完全保证,可能在某些场景下算法达不到一致性的目的
理解 Paxos 算法有以下几个关键点
Paxos算法是多数一致性(Quorum),不是全体一致性
Client 发起的请求可以是任何操作,而不仅限于写操作,读操作也可以
即使是读一个数据,也要按照算法完整地执行一遍,这和通常理解上的数据一致性不同,通常认为数据一致性是和写操作相关的
Paxos算法中的角色(Proposer、 Acceptor、 Leamer、 Leader)是逻辑上的划分,不是集群上的物理节点要这样划分
Raft
Raft从理论上来讲确实不是一个完备的分布式一致性算法
Raft简化了一些处理,以保证绝大部分情况下都是能保证一致性的
Raft算法通过将分布式一致性问题拆分为 3个子问题来简化算法:Leader选举、日志复制、 安全保证
ZAB
ZAB 的全称是 ZooKeeper Atomic Broadcast Protocol,是 ZooKeeper 系统中采用的分布式一致性算法
ZAB的实现和Raft其实是类似的
ZAB、Paxos和 Raft有一个较大的差异就是复制的方式, Paxos和 Raft采用的是 state machine replication (又称 active replication),,ZAB 采用的是 primary backup (又称 passive replication),,两种实现方式差异如下
state machine replication
各个节点间 复制的是具体的操作,然后每个节点自己执行操作
primary backup
Leader 节点执行操作,将执行结果复制给其他节点
数据分区
基于硬件故障而设计的高可用架构不再适用,我们需要基于地理级别的故障来设计高可用架构,这就是数据分区架构产生的背景
数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响
采用了数据分区的架构后,即使某个地区发生严重的自然灾害或事故,受影响的也只是一部分数据,而不是全部数据都不可用
当故障恢复后,其他地区备份的数据也可以帮助故障地区快速恢复业务
设计一个良好的数据分区架构,需要从多方面去考虑
数据量
数据量越大,分区规则会越复杂,考虑的情况也越多
分区规则
地理位置有近有远,因此可以得到不同的分区规则,包括洲际分区、国家分区、城市分区 。具体采取哪种或哪几种规则, 需要综合考虑业务范围、成本等因素
城市分区由于都在同一个国家或地区内,网络延迟较低,业务相似,分区同时对外提供服务,可以满足业务多活之类的需求
复制规则
即使是分区架构,同样需要考虑复制方案
常见的分区复制规则有三种 : 集中式、互备式和独立式
集中式
集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心
图
优缺点
设计简单,各分区之间井无直接联系,可以做到互不影响
扩展容易,如果要增加一个分区,只需要将该分区的数据复制到备份中心即可,其他分区不受影响
成本较高,需要建设一个独立的备份中心
互备式
互备式备份指每个分区各份另外一个分区的数据
图
优缺点
设计比较复杂,各个分区除了要承担业务数据存储,还需要承担备份功能 ,相互之间互相关联和影响
扩展麻烦
成本低,直接利用己有的设备
独立式
独立式备份指每个分区自己有独立的备份中心
图
优缺点
设计简单,各分区互不影响
扩展容易,新增加的分区只 需要搭建自己的备份中心即可
成本高,每个分区需要独立的备份中心,这个成本比集中式备份都要高很多
第9章 计算高可能
计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行
通过增加更多服务器来达到计算高可用
计算高可用架构的设计复杂度主要体现在任务管理方面 ,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行
计算高可用架构设计的关键点
哪些服务器可以执行任务
第一种方式和计算高性能中的集群类似,每个服务器都可以执行任务
第二种方式和存储高可用中的集群类似,只有特定服务器(通常叫“主机”)可以执行任务
任务如何重新执行
第一种策略是对于已经分配的任务即使执行失败也不做任何处理,系统只需要保证新的任务能够分配到其他非故障服务器上执行即可
第二种策略是设计一个任务管理器来管理需要执行的计算任务,服务器执行完任务后,需要向任务管理器反馈任务执行结果,任务管理器根据任务执行结果来决定是否需要将任务重新分配到另外的服务器上执行
“任务分配器”是一个逻辑的概念,并不一定要求系统存在一个独立的任务分配器模块
主备
主备架构是计算高可用最简单的架构,和存储高可用的主备复制架构类似,但是要更简单一些,因为计算高可用的主备架构无须数据复制
图
主备方案详细设计如下
主机执行所有计算任务
当主机故障时,任务分配器不会自动将计算任务发送给备机,此时系统处于不可用状态
如果主机能够恢复,任务分配器继续将任务发送给主机
如果主机不能够恢复,则需要人工操作,将备机升为主机,然后让任务分配器将任务发送给新的主机(即原来的备机);同时,为了继续保持主备架构,需要人工增加新的机器作为备机
根据备机状态的不同,主备架构又可以细分为冷备架构和温备架构
冷备
备机上的业务系统没有启动
温备
备机上的业务系统己经启动,只是不对外提供服务
优缺点
主备架构的优点就是简单,主备机之间不需要进行交互,状态判断和倒换操作由人工执行,系统实现很简单
缺点正好也体现在“人工操作”这点上
比较适合与内部管理系统、 后台管理系统这类使用人数不多,使用频率不高的业务,不太适合在线的业务
主从
和存储高可用中的主从复制架构类似,计算高可用的主从架构中的从机也是要执行任务的
图
主从方案详细设计如下
正常情况下,主机执行部分计算任务,备机执行部分计算任务
当主机故障时, 任务分配器不会自动将原本发送给主机的任务发送给从机,而是继续发送给主机,不管这些任务执行是否成功
如果主机能够恢复,任务分配器继续按照原有的设计策略分配任务
如果主机不能够恢复,则需要人工操作,将原来的从机升级为主机,增加新的机器作为从机,新的从机准备就绪后,任务分配器继续按照原有的设计策略分配任务
子主题
主从架构与主备架构相比,优缺点如下
优点 : 主从架构的从机也执行任务,发挥了从机的硬件性能
缺点 : 主从架构需要将任务分类,任务分配器会复杂一些
高可用集群
对称集群
主备架构和主从架构通过冗余一台服务器来提升可用性,且需要人工来切换主备或主从
我们需要系统能够自动完成切换操作,这就是高可用集群方案
高可用计算的集群根据集群中服务器节点角色的不同,可以分为两类
对称集群
即集群中每个服务器的角色都是一样的,都可以执行所有任务
非对称集群
集群中的服务器分为多个不同的角色,不同的角色执行不同的任务,例如,最常见的 Master-Slave 角色
对称集群更通俗的叫法是负载均衡集群
图
负载均衡集群详细设计如下
正常情况下,任务分配器采取某种策略(随机、轮询等)将计算任务分配给集群中的不同服务器
当集群中的某台服务器故障后,任务分配器不再将任务分配给它,而是将任务分配给其他服务器执行
当故障的服务器恢复后,任务分配器重新将任务分配给它执行
负载均衡集群的设计关键点在于两点
任务分配器需要检测服务器状态
任务分配器需要选取分配策略
非对称集群
非对称集群中不同服务器的角色是不同的,不同角色的服务器承担不同的职责
图
非对称集群架构详细设计如下
集群会通过某种方式来区分不同服务器的角色
任务分配器将不同任务发送给不同服务器
当指定类型的服务器故障时,需 要重新分配角色
非对称集群相比负载均衡集群,设计复杂度主要体现在两个方面
任务分配策略更加复杂
角色分配策略实现比较复杂
第10章 业务高可用
异地多活
无论高可用计算架构,还是高可用存储架构,其本质的设计目的都是为了解决部分服务器故障的场景下,如何保证系统能够继续提供服务
异地多活架构的关键点就是异地、多活
异地就是指地理位置上不同的地方, 类似于“不要把鸡蛋都放在同一篮子里”
多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思
判断一个系统是否符合异地多活,需要满足如下两个标准
正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务
某地系统异常情况下,用户访问到其他地方正常的业务系统,也能够得到正确的业务服务
与“活“对应的是字是“备”,备是备份,正常情况下对外不提供服务,如果需要提供服务,则需要大量的人工干预和操作,花费大量的时间才能让“备”变“活”
单纯从异地多活的描述来看,异地多活很强大,能够保证在灾难的情况下业务都不受影响
代价
系统复杂度会发生质的变化,需要设计复杂的异地多活架构
成本会上升,毕竟要多在一个或多个机房搭建独立的一套业务系统
异地多活架构
根据地理位置上的距离来划分
同城异区
将业务部署在同一个城市不同区的多个机房
同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度
虽然是两个不同地理位置上的机房,但逻辑上我们可以将它们看作同一个机房,这样的设计大大降低了复杂度,异地多活的设计和实现复杂度及成本减少了
结合复杂度、 成本、故障发生概率来综合考虑,同城异区是应对机房级别故障的最优架构
跨城异地
业务部署在不同城市的多个机房,而且距离最好要远一些
支付宝等金融相关的系统,对余额这类数据,不能做跨城异地的多活架构,而只能采用同城异区这种架构
跨国异地
业务部署在不同国家的多个机房
主要应用场景
为不同地区用户提供服务
只读类业务做多活
例如,谷歌的搜索业务
异地多活设计技巧
架构的关键点
同城异区
关键在于搭建高速网络将两个机房连接起来,达到近似一个本地机房的效果。架构设计上可以将两个机房当作本地机房来设计,无须额外考虑
跨城异地
关键在于数据不一致的情况下,业务不受影响或影响很小,这从逻辑的角度上来说其实是矛盾的,架构设计的主要目的就是为了解决这个矛盾
跨国异地
主要是面向不同地区用户提供业务,或者提供只读业务,对架构设计要求不高
跨城异地多活是架构设计复杂度最高的一种架构
技巧一:保证核心业务的异地多活
技巧二:核心数据最终一致性
数据同步是异地多活架构的设计核心
异地多活理论上就不可能很快,因为这是物理定律决定的
异地多活架构面临 一 个无法彻底解决的矛盾:业务上要求数据快速同步,物理上正好做不到数据快速同步
方法
尽量减少异地多活机房的距离,搭建高速网络
尽量减少数据同步,只同步核心业务相关的数据
保证最终一致性,不保证实时一致性
技巧三:采用多种手段同步数据
技巧四:只保证绝大部分用户的异地多活
异地多活设计的理念可以总结为一句话: 采用多种手段,保证绝大部分用户的核心业务异地多活!
异地多活设计步骤
第一步:业务分级
访问量大的业务
核心业务
产生大量收入的业务
第二步:数据分类
数据量
唯一性
实时性
可丢失性
可恢复性
第三步: 数据同步
存储系统同步
消息队列同步
重复生成
第四步:异常处理
目的
问题发生时,避免少量数据异常导致整体业务不可用
问题恢复后,将异常的数据进行修正
对用户进行安抚,弥补用户损失
措施
多通道同步
同步和访问结合
日志记录
用户补偿
接口级的故障应对方案
异地多活架构主要应对系统级的故障。 例如,机器宕机、机房故障、网络故障等问题
这些系统级的故障虽然影响很大,但发生概率较小
故障影响可能没有系统级那么大,但发生的概率较高,这就是接口级的故障
接口级故障的典型表现就是系统并没有宕机,网络也没有中断,但业务却出现问题了,例如,业务响应缓慢,大量访问超时,大量访问出现异常
这类问题的主要原因在于系统压力太大,负载太高,导致无法快速处理业务请求,由此引发更多的后续问题
导致接口级故障的原因
内部原因
程序 bug 导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等
外部原因
黑客攻击、促销或抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等
解决接口级故障的核心思想和异地多活基本类似:优先保证核心业务,优先保证绝大部分用户
具体措施
降级
降级指系统将某些业务或接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能
降级的核心思想就是丢车保帅,优先保证核心业务
常见的实现降级的方式有如下几种
系统后门降级
例如, 系统提供一个降级 URL, 当访问这个 URL 时,就相当于执行降级指令,具体的降级指令通过 URL 的参数传入即可
系统后门降级的方式实现成本低,但主要缺点是如果服务器数量多,需要一台一台去操作, 效率比较低
这种方案有一定的安全隐患,所以也会在 URL 中加入密码这类安全措施
独立降级系统
为了解决系统后门降级方式的缺点,我们将降级操作独立到一个单独的系统中,可以实现复杂的权限管理、批量操作等功能
图
熔断
降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况
熔断机制实现的关键是需要有一个统一的 API 调用层,由 API 调用层来进行采样或统计, 如果接口调用散落在代码各处就没法进行统一处理了
熔断机制实现的另外一个关键是阈值的设计
限流
降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障
限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃
限流一般都是系统内实现的,常见的限流方式可以分为两类
基于请求限流
指从外部访问的请求角度考虑限流,常见的方式有限制总量和限制时间量
基于资源限流
基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的, 即找到系统内部影响性能的关键资源,对其使用上限进行限制
常见的内部资源有连接数、文件句柄、线程数、请求队列等
基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力
排队
排队实际上是限流的一个变种 ,限流是直接拒绝用户 ,排队是让用户等待很长时间
图
第五部分 架构实战
第16章 消息队列设计实战
第17章 互联网架构演进
技术演进
技术演进的动力
【产品类】
技术创新推动业务发展!
【服务类】
服务发展推动技术的发展!
技术演进的模式
复杂性要么来源于功能不断叠加,要么来源于规模扩大,从而对性能和可用性有了更高的要求
答案就是基于业务发展阶段进行判断
互联网业务发展
业务复杂性
用户规模
性能
可用性
量变到质变
互联网业务驱动技术发展的两大主要因素是复杂性和用户规模,而这两个因素的本质原因其实都是 “量变带来质变”
第18章 互联网架构模版
总体结构
互联网的标准技术架构如下图
存储层技术
SOL
将数据拆分到多个数据库实例才能满足业务的性能需求
数据库拆分满足了性能的要求,但带来了复杂度的问题:数据如何拆分、数据如何组合
互联网公司流行的做法是业务发展到一定阶段后,就会将这部分功能独立成中间件
例如百度的 DBProxy、淘宝的 TDDL
中小公司建议使用开源方案,例如,MySQL 官方推荐的 MySQL Router、 360 开源的数据库中间件 Atlas、Apache ShardingSphere、Mycat
什么是Mycat?为什么要使用MyCat?
NoSQL
NoSQL 的这两个特点很好地弥补了关系数据库的不足
非结构化数据
性能
NoSQL发展到一定规模后,通常都会在 NoSQL 集群的基础之上再实现统一存储平台
统一存储平台主要实现如下几个功能
资源动态按需动态分配
例如,同一台 Memcache 服务器,可以根据内存利用率,分配给多个业务使用
资源自动化管理
例如,新业务只需要申请多少 Memcache 缓存空间就可以了,无须关注具体是哪些 Memcache 服务器在为自己提供服务
故障自动化处理
例如,某台 Memcache 服务器挂掉后,有另外一台备份 Memcache 服务器能立刻接管缓存请求,不会导致丢失很多缓存数据
小文件存储
这些数据具有 3 个典型特征
一是数据小,一般在 lMB 以下
二是数量巨大
三是访问量巨大
自然而然的想法就是将小文件存储做成统一的和业务无关的平台
例如, HBase、Hadoop、Hypertable、FastDFS 等都可以作为小文件存储的底层平台,只需要在这些开源方案再包装一下基本上就可以用了
典型的有:淘宝的 TFS、京东 JFS、 Facebook 的 Haystack
淘宝 TFS 的架构
图
大文件存储
互联网行业的大文件主要分为两类
一类是业务上的大数据,例如 Youtube 的视频、电影网站的电影
一类是海量的日志数据,例如各种访问日志、操作日志 、用户轨迹日志等
说到大文件,特别要提到 Google 和 Yahoo
Google 的 3 篇大数据论文(BigTable/Map- Reduce/GFS)开启了一个大数据的时代
Yahoo 开源的 Hadoop 系列(HDFS、 HBase 等),基本上垄断了开源界的大数据处理
所以大数据存储和处理这块反而是最简单的,因为你别无选择,只能用这几个流行的开源方案。例如,Hadoop、HBase、Storm、Hive 等
Hadoop 的生态圈
图
开发层技术
开发框架
优选成熟的框架,避免盲目追逐新技术!
Web 服务器
容器
服务层技术
服务层的主要目标其实就是为了降低系统间相互关联的复杂度
配置中心
服务中心
服务名字系统( Service Name System)
图
服务总线系统( Service Bus System)
由总线系统完成调用,服务请求方都不需要直接和服务提供方交互了
图
消息队列
图
消息队列系统基本功能的实现比较简单,但要做到高性能、高可用、消息时序性、消息事务性则比较难
网络层技术
负载均衡
DNS
一般用来实现地理级别的均衡
缺点
DNS 缓存的时间比较长,即使将某台业务机器从 DNS 服务器上删除,由于缓存的原因,还是有很多用户会继续访问己经被删除的机器
DNS 不够灵活一一DNS 不能感知后端服务器的状态,只能根据配置策略进行负载均衡,无法做到更加灵活的负载均衡策略
Nginx&LVS&F5
用于同一地点内机器级别的负载均衡
Nginx 是软件的 7 层负载均衡,LVS 是内核的 4 层负载均衡, F5 是硬件做 4 层负载均衡
4层和7层的区别就在于协议和灵活性
图
CDN
本质上是一种 “以空间换时间” 的加速策略,即将内容缓存在离用户最近的地方,用户访问的是缓存的内容,而不是站点实时的内容
多机房
从架构上来说,单机房就是一个全局的网络单点,在发生比较大的故障或灾害时,单机房难以保证业务的高可用
例如,停电、机房网络中断、地震、水灾等都有可能导致一个机房完全瘫痪
多机房设计最核心的因素就是如何处理时延带来的影响,常见的策略如下
【同城多机房】
【跨城多机房】
业务上需要做一定的妥协和兼容,不需要数据的实时强一致性,保证最终一致性
【跨国多机房】
跨国多机房一般仅用于备份和服务本国用户
多中心
多中心必须以多机房为前提,但从设计的角度来看,多中心相比多机房是本质上的飞越,难度也高出一个等级。
多机房的主要目标是灾备
多中心要求每个中心都同时对外提供服务,且业务能够自动在多中心之间切换,故障后不需人工干预或很少人工干预就能自动恢复
多中心设计的关键就在于“数据一致性”和“数据事务性”如何保证
目前国内的银行、支付宝这类系统就没有完全实现多中心
用户层技术
用户管理
第一个目标
SSO,单点登录,又叫统一登录
最有名的开源方案当属 CAS
第二个目标
授权登录
最流行的授权登录就是 OAuth 2.0 协议
用户管理关键的技术如下图
消息推送
消息推送根据不同的途径,分为短信、邮件、站内信、App 推送
iOS 系统比较规范和封闭,基本上只能使用苹果的 APNS
Android 的消息推送就五花八门
消息推送主要包含3个功能
设备管理(唯一标识、注册、注销)
连接管理
消息管理
技术上面临的主要挑战如下
海量设备和用户管理
简单来说就是将用户和设备关联起来
连接保活
例如,应用互相拉起、找于机厂商开白名单等
消息管理
设计方案可以采取规则引擎之类的微内核架构技术
存储云与图片云
这些文件具备如下几个典型特点
数据量大
文件体积小
访问有时效性
需要用到前面介绍“存储层”技术时提到的“小文件存储”技术
通常的实现都是“CDN + 小文件存储”
业务层技术
所以业务层没有办法提炼一些公共的系统或组件
业务层面对的主要技术挑战是“复杂性”
面对业务层的技术挑战,我们有一把“屠龙宝刀”,不管什么业务难题,用上“屠龙宝刀”一试问题都迎刃而解。这把“屠龙宝刀”就是“拆”
降低复杂性最好的方式就是“拆”,化整为零、分而治之,将整体复杂性分散到多个子业务或子系统里面去
随着子系统数量越来越多,如果达到几百上千个,则复杂度问题又会凸显出来
终答案还是“合”,正所谓“合久必分、分久必合”
采取的“合”的方式是按照“高内聚、低耦合”的原则,将职责关联比较强的子系统合成一个虚拟业务域,然后通过网关对外统一呈现,类似于设计模式中的 Facade 模式
平台技术
运维平台
运维平台核心的职责分为四大块:配置、部署、监控、应急
配置:主要负责资源的管理。例如,机器管理、IP 地址的管理 、虚拟机的管理等
部署:主要负责将系统发布到线上。例如,包管理、灰度发布管理、回滚等
监控:主要负责收集系统上线运行后的相关数据并进行监控,以便及时发现问题
应急: 主要负责系统出故障后的处理。例如,停止程序、下线故障机器、切换 IP 等
运维平台的核心设计要素是“四化”: 标准化、平台化、自动化、可视化
标准化
需要制定运维标准,规范配置管理、部署流程、监控指标、应急能力等
平台化
因此需要在运维标准化的基础上,将运维的相关操作都集成到运维平台中,通过运维平台来完成运维工作
自动化
可视化
测试平台
测试平台的核心目的是提升测试效率,从而提升产品质量,其设计关键就是自动化
测试平台的基本架构如下图
用例管理
资源管理
任务管理
任务管理的主要职责是将测试用例分配到具体的资源上执行,跟踪任务的执行情况
它将测试平台的各个部分串联起来从而完成自动化测试
数据管理
数据平台
数据平台的核心职责主要包括三部分:数据管理、数据分析和数据应用
数据平台架构如下图
数据管理
数据采集
数据存储
数据访问
数据安全
数据分析
数据统计
数据挖掘
机器学习、深度学习
属于数据挖掘的一种具体实现方式
数据应用
推荐、广告等属于在线应用
报表、欺诈检测、异常检测等属于离线应用
管理平台
管理平台的核心职责就是权限管理
权限管理主要分为两部分:身份认证、权限控制,基本架构如下图所示
身份认证
权限控制
第19章 架构重构
架构重构对架构师的要求更高,主要体现在如下方面
业务己经上线,不能停下来
关联方众多,牵一发动全身
旧架构的约束
有的放矢
我们来看几个具体的重构案例
后台系统重构一一解决不合理的耦合
游戏接入系统重构一一解决全局单点的可用性问题
X 系统一一 解决大系统带来的开发效率问题
需要透过问题表象看到问题本质,找出真正需要通过架构重构解决的核心问题
合纵连横
合纵
遇到的一个问题是凭感觉而不是凭数据说话
连横
有效的策略是“换位思考、合作双赢、关注长期”
运筹帷幄
架构师在识别系统关键的复杂度问题后,还需要识别为了解决这个问题,需要做哪些准备事项,或者还要先解决哪些问题
最简单的做法是每次从中挑一个解决,最终总会把所有的问题都解决
第一个原因是没有区分问题的优先级,所有问题都一视同仁,没有集中有限资源去解决最重要或最关键的问题,导致最后做了大半年,回头一看好像做了很多事情,但没取得什么阶段性的成果。
第二个原因是没有将问题分类,导致相似问题没有统筹考虑,方案可能出现反复,效率不高
第三个原因是会迫于业务版本的压力,专门挑容易做的实施,到了稍微难一点的问题的时候,就因为复杂度和投入等原因被搁置,达不到真正重构的真正目的
总结一下重构的做法,其实就是“分段实施”,将要解决的问题根据优先级、重要性、实施难度等划分为不同的阶段,每个阶段聚焦于一个整体的目标,集中精力和资源解决一类问题
具体如何制定“分段实施”的策略呢?以下经验可以参考
划分优先级
问题分类
先易后难
文武双全一一顶目管理+技术能力
“有的放矢”是关于找目标的、“合纵连横”是关于沟通协调的、“运筹帷帽”是关于项目规划的
架构设计的主要目的是为了解决系统的复杂性
第20章 开源系统
选:如何选择一个开源顶目
聚焦是否满足业务
这里的设计决策遵循架构设计原则中的“合适原则”和”演化原则”
聚焦是否成熟
聚焦运维能力
用:如何使用开源万案
深入研究,仔细测试
小心应用,灰度发布
做好应急,以防万一
改:如何基于开源顶自做二次开发
保持纯洁,加以包装
发明你要的轮子
0 条评论
下一页