架构
2021-12-23 20:35:02 0 举报
AI智能生成
1.架构基础 2.高性能架构模式 3.高可用架构模式 4.可扩展架构模式
作者其他创作
大纲/内容
复杂度来源
高性能
单机复杂度
进程
进程间的通信问题
管道
消息队列
信号量
共享存储
进程间的协作问题
支持串行处理
不支持并行处理,需要引入线程
线程
支持进程间的并行处理
单机性能优化有极限,并不能支撑无限拓展
集群复杂度
为了短时间内处理海量的请求,需要采取集群的方式,且这种规模的集群,机器数量都是万台以上的。
让多台机器配合起来的方式
任务分配
需要增加任务分配器
硬件
F5
交换机
软件网络设备
LVS
Nginx
自研系统
用户分配至任务分配器的算法
DNS轮询
CDN
GSLB设备
任务分配器和业务服务器之间的连接和交互
合适的连接方式
连接的管理
连接建立
连接检测
连接中断处理方法
任务分配器需要采取合适的分配算法
轮训
负载
权重
状态管理,故障处理复杂度很大
适合处理任务并不是很复杂的情景
任务分解
适合业务本身复杂的,通过增加机器收益较少的情况
做法是将一个复杂的任务拆解成多个较为简单的任务
任务分解既不会减少功能,更不会减少代码量(事实上会增加)
任务分解提升性能的因素
简单的系统更加容易做到高性能
可以针对单个任务进行拓展
高可用
概念
系统无中断地执行其功能的能力
本质是通过冗余来实现高可用
高性能增加机器目的在于拓展处理性能
高可用增加机器目的是在于冗余处理单元
分类
计算高可用
准则
无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的
架构
需要增加一个任务分配器
考虑因素
性能
成本
可维护性
可用性
任务分配器和业务服务器的连接和交互
选择合适连接方式
连接管理
连接建立
连接检测
连接中断后的处理
任务分配器选择合适分配算法
双机算法
主备
冷备
温备
热备
主主
集群
1主3备
2主2备
3主1备
4主0备
...
示例
ZooKeeper是1主多备
Memcached是全主0备
存储高可用
概念
存储与计算相比,有一个本质上的区别:将数据从一台机器搬到到另一台机器,需要经过线路进行传输
线路传输速度有差别
理论上毫秒级别
同机房能够做到几毫秒
分布在不同地方的机房耗时几十甚至上百毫秒
跨省,跨国,跨洲更不用说了
关键点和难点
不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。
高可用状态决策
概念
无论是计算高可用还是存储高可用,其基础都是“状态决策
通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确
决策方式
独裁式
概念
存在一个独立的决策主体,负责收集信息然后进行决策
所有冗余的个体,都将状态信息发送给决策者
优点
独裁式的决策方式不会出现决策混乱的问题,因为只有一个决策者
缺点
当决策者本身故障时,整个系统就无法实现准确的状态决策
协商式
概念
指的是两个独立的个体通过交流信息,然后根据规则进行决策
最常用的协商式决策就是主备决策
流程
2 台服务器启动时都是备机
2 台服务器建立连接
2 台服务器交换状态信息
某 1 台服务器做出决策,成为主机;另一台服务器继续保持备机身份
难点
两者的信息交换出现问题(主备连接中断),状态决策应该怎么做
备机连接中断情况下认为主机故障,备机升级为主机,实际上主机并没有故障,那么就是与之前设计初衷的一主一备不符合
如果备机在连接中断的情况下不认为主机故障,则此时如果主机真的发生故障,那么系统就没有主机了,这同样与设计初衷(1 主 1 备)是不符合的
民主式
概念
多个独立的个体通过投票的方式来进行状态决策
ZooKeeper 集群在选举 leader 时就是采用这种方式。
与协商式类似,都是按照多数取胜,不同点是民主决策比协商复杂多了,zk的选举算法ZAB,很复杂
缺点
脑裂
原来统一的集群因为连接中断,造成了两个独立分隔的子集群,每个子集群单独进行选举,于是选出了 2 个主机,相当于人体有两个大脑了。
可拓展性
概念
指系统为了应对将来需求变化而提供的一种能力
系统在新需求到来的时候,无须整个系统重构或者重建
关键点
正确预测变化
完美封装变化
预测变化
预防变化的复杂性
不能每个设计点都考虑可扩展性
不能完全不考虑可扩展性
所有的预测都存在出错的可能性
根据对系统的充分了解,猜测系统可能的演进方向,并充分做好准备工作
应对变化
应对办法
将变化封装在一个变化层,将不变的部分封装在一个独立的稳定层
系统需要拆分出变化层和稳定层
需要设计变化层和稳定层之间的接口
提炼出一个抽象层和一个实现层
抽象层是稳定的,实现层可以根据具体业务需要定制开发
低成本
当架构方案规模较小,只有几台机器,一般成本不是重点关注的
如果架构规模较大,出现几百上千甚至上万台服务器的时候,成本就会变成一个非常重要的架构设计考虑点
通过创新才能达到低成本目的
安全
概念
安全本身是一个庞大而又复杂的技术领域,并且一旦出问题,对业务和企业形象影响非常大
种类
功能安全
常见的 XSS 攻击、CSRF 攻击、SQL 注入、Windows 漏洞、密码破解等
本质上是因为系统实现有漏洞,黑客有了可乘之机
黑客会利用各种漏洞潜入系统,这种行为就像小偷一样,黑客和小偷的手法都是利用系统或家中不完善的地方潜入,并进行破坏或者盗取。因此形象地说,功能安全其实就是“防小偷”。
架构安全
如果说功能安全是“防小偷”,那么架构安全就是“防强盗”
传统的架构安全主要依靠防火墙,防火墙最基本的功能就是隔离网络,通过将网络划分成不同的区域,制定出不同区域之间的访问控制策略来控制不同信任程度区域间传送的数据流
规模
规模带来复杂度的主要原因就是“量变引起质变”
功能越来越多,导致系统复杂度指数级上升
数据越来越多,系统复杂度发生质变
设计三原则
合适原则
合适原则宣言:“合适优于业界领先”
简单原则
简单原则宣言:“简单优于复杂”
软件领域的复杂性体现
结构的复杂性
逻辑的复杂性
演化原则
演化原则宣言:“演化优于一步到位”。
软件架构设计其实更加类似于大自然“设计”一个生物,通过演化让生物适应环境,逐步变得更加强大
可扩展架构模式
分类
分层架构
概念
分层架构是很常见的架构模式,它也叫 N 层架构,通常情况下,N 至少是 2 层。例如,C/S 架构、B/S 架构。常见的是 3 层架构(例如,MVC、MVP 架构
分类
C/S架构,B/S架构
MVC架构,MVP架构
逻辑分层架构
分层架构之所以能够较好地支撑系统扩展,本质在于隔离关注点(separation of concerns),即每个层中的组件只会处理本层的逻辑。
分层结构的另外一个特点就是层层传递
SOA架构
SOA 的全称是 Service Oriented Architecture,中文翻译为“面向服务的架构”
关键概念
服务
所有业务功能都是一项服务,服务就意味着要对外提供开放的能力,当其他系统需要使用这项功能时,无须定制化开发
服务可大可小,可简单也可复杂
ESB
ESB 的全称是 Enterprise Service Bus,中文翻译为“企业服务总线”
ESB 参考了计算机总线的概念
因为各个独立的服务是异构的,如果没有统一的标准,则各个异构系统对外提供的接口是各式各样的。
SOA 使用 ESB 来屏蔽异构系统对外提供各种不同的接口方式,以此来达到服务间高效的互联互通。
松耦合
松耦合的目的是减少各个服务间的依赖和互相影响
微服务
微内核
基本思想
可扩展性架构的设计方法很多,但万变不离其宗,所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆
拆,就是将原本大一统的系统拆分成多个规模小的部分,扩展时只修改其中一部分即可,无须整个系统到处都改,通过这种方式来减少改动范围,降低改动风险
拆分的思路
面向流程拆分
将整个业务流程拆分为几个阶段,每个阶段作为一部分
面向服务拆分
将系统提供的服务拆分,每个服务作为一部分
面向功能拆分
将系统提供的功能拆分,每个功能作为一部分
区别与联系
流程 > 服务 > 功能
实例1
TCP/IP 协议栈为例,来说明“流程”“服务”“功能”的区别和联系
流程
对应 TCP/IP 四层模型,因为 TCP/IP 网络通信流程是:应用层 → 传输层 → 网络层 → 物理 + 数据链路层,不管最上层的应用层是什么,这个流程都不会变
服务
对应应用层的 HTTP、FTP、SMTP 等服务,HTTP 提供 Web 服务,FTP 提供文件服务,SMTP 提供邮件服务,以此类推
功能
每个服务都会提供相应的功能。例如,HTTP 服务提供 GET、POST 功能,FTP 提供上传下载功能,SMTP 提供邮件发送和收取功能
实例2
以一个简单的学生信息管理系统为例
面向流程拆分
展示层 → 业务层 → 数据层 → 存储层
面向服务拆分
将系统拆分为注册、登录、信息管理、安全设置等服务
面向功能拆分
每个服务都可以拆分为更多细粒度的功能
注册服务:提供多种方式进行注册,包括手机号注册、身份证注册、学生邮箱注册三个功能
登录服务:包括手机号登录、身份证登录、邮箱登录三个功能
信息管理服务:包括基本信息管理、课程信息管理、成绩信息管理等功能
安全设置服务:包括修改密码、安全手机、找回密码等功能
典型的可拓展系统架构
面向流程拆分
分层架构
面向服务拆分
SOA
微服务
面向功能拆分
微内核架构
开源项目的学习方法
安装
这个系统的依赖组件,而依赖的组件是系统设计和实现的基础
以 Nginx 为例,源码安装 Nginx 依赖的库有 pcre、pcre-devel、openssl、openssl-devel、zlib,光从名字上看都能够了解一些信息,例如 openssl 可能和 https 有关,zlib 可能和压缩有关
再以 Memcache 为例,最大的依赖就是 libevent,而根据 libevent 是一个高性能的网络库,我们就能大概推测 Memcache 的网络实现应该是 Reactor 模型的。
安装目录也能够提供一些使用和运行的基本信息
系统提供了哪些工具方便我们使用
运行
命令行和配置文件
系统具备哪些能力和系统将会如何运行。这些信息是我们窥视系统内部运行机制和原理的一扇窗口
原理研究
关键特性的基本实现原理
优缺点对比分析
测试
测试一定要在原理研究之后做,不能安装完成立马就测试!
源码研究
通过调用栈来理解基础库的处理逻辑和过程
不建议通读所有源码
高可用架构模式
理论
CAP
又被称作布鲁尔定理
概念
在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
一致性(Consistency)
对某个指定的客户端来说,读操作保证能够返回最新的写操作结果
实际上对于节点来说,可能同一时刻拥有不同数据(same time + different data)
对于系统执行事务来说,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致,
可用性(Availability)
非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)
分区容忍性(Partition Tolerance)
当出现网络分区后,系统能够继续“履行职责”
发生了分区现象,不管是什么原因,可能是丢包,也可能是连接中断,还可能是拥塞,只要导致了网络分区,就通通算在里面
基于 Paxos 算法构建的分布式系统,属于 CAP 架构中的哪一种
paxos的核心思想是少数服从多数,在节点数为2n+1的集群中,只要n+1节点上完成了写入就能保证接下来的读操作能够读到最新的值。从这一点来说它满足C(对某个指定的客户端来说,读操作保证能够返回最新的写操作结果);
一个实现了paxos协议的集群最多可以容忍n个节点故障(n个节点同时故障的概率是比较小的),非故障节点组成的集群仍能正常提供服务,从这个角度来讲它满足A(非故障的节点在合理的时间内返回合理的响应,不是错误和超时的响应);
paxos集群发生分区肯能存在两种情况,第一种情况是发生分区后没有发生重新选举,这种情况下集群仍能正常工作,因此满足P(当出现网络分区后,系统能够继续“履行职责”)
另一种情况是发生分区后原来的集群达不到多数派,集群不在对外提供服务,因此不满足P,当发生这种情况的时候,一般会快速修复
总的来说在某种意义上来看paxos满足CAP。
细节
CAP 关注的粒度是数据,而不是整个系统
所以在 CAP 理论落地实践时,我们需要将系统内的数据按照不同的应用场景和要求进行分类,每类数据选择不同的策略(CP 还是 AP),而不是直接限定整个系统所有数据都是同一策略。
不能把整个系统设置为cp或者ap
CAP 是忽略网络延迟的
理论上在定义一致性的时候并没有将延迟考虑进去,当事务提交时,数据能够瞬间复制到所有节点
但实际情况下,从节点 A 复制数据到节点 B,总是需要花费一定时间的
正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA
AP 理论告诉我们分布式系统只能选择 CP 或者 AP,但其实这里的前提是系统发生了“分区”现象
既要考虑分区发生时选择 CP 还是 AP,也要考虑分区没有发生时如何保证 CA
放弃并不等于什么都不做,需要为分区恢复后做准备
CAP 理论告诉我们三者只能取两个,需要“牺牲”(sacrificed)另外一个,这里的“牺牲”是有一定误导作用的,因为“牺牲”让很多人理解成什么都不做。实际上,CAP 理论的“牺牲”只是说在分区过程中我们无法保证 C 或者 A,但并不意味着什么都不做
最典型的就是在分区期间记录一些日志,当分区故障解决后,系统根据日志进行数据恢复,使得重新达到 CA 状态
假如你来设计电商网站的高可用系统,按照 CAP 理论的要求,你会如何设计
对于会员模块,包括登录,个人设置,个人订单,购物车,收藏夹等,这些模块保证AP,数据短时间不一致不影响使用。
订单模块的下单付款扣减库存操作是整个系统的核心,我觉得CA都需要保证,在极端情况下牺牲P是可以的。
商品模块的商品上下架和库存管理保证CP
搜索功能因为本身就不是实时性非常高的模块,所以保证AP就可以了。
促销是短时间的数据不一致,结果就是优惠信息看不到,但是已有的优惠要保证可用,而且优惠可以提前预计算,所以可以保证AP
现在大部分的电商网站对于支付这一块是独立的系统,或者使用第三方的支付宝,微信。其实CAP是由第三方来保证的,支付系统是一个对CAP要求极高的系统,C是必须要保证的,AP中A相对更重要,不能因为分区,导致所有人都不能支付
ACID
Atomicity(原子性)
一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
Consistency(一致性)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
Isolation(隔离性)
数据库允许多个并发事务同时对数据进行读写和修改的能力。
隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
Durability(持久性)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
与CAP的区别
可以看到,ACID 中的 A(Atomicity)和 CAP 中的 A(Availability)意义完全不同,而 ACID 中的 C 和 CAP 中的 C 名称虽然都是一致性,但含义也完全不一样
ACID 中的 C 是指数据库的数据完整性,而 CAP 中的 C 是指分布式节点中的数据一致性
BASE
BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)
基本可用(Basically Available)
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
软状态(Soft State)
允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是 CAP 理论中的数据不一致。
最终一致性(Eventual Consistency)
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。
FMEA方法
FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等
具体方法
给出初始的架构设计图
假设架构中某个部件发生故障
分析此故障对系统功能造成的影响。
根据分析结果,判断架构是否需要进行优化
包含的模块
功能点
当前的 FMEA 分析涉及的功能点,注意这里的“功能点”指的是从用户角度来看的,而不是从系统各个模块功能点划分来看的
对于一个用户管理系统,使用 FMEA 分析时 “登录”“注册”才是功能点,而用户管理系统中的数据库存储功能、Redis 缓存功能不能作为 FMEA 分析的功能点。
故障模式
故障模式指的是系统会出现什么样的故障,包括故障点和故障形式
此外,故障模式的描述要尽量精确,多使用量化描述,避免使用泛化的描述
例如,推荐使用“MySQL 响应时间达到 3 秒”,而不是“MySQL 响应慢”
故障影响
常见的影响有:功能点偶尔不可用、功能点完全不可用、部分用户功能点不可用、功能点响应缓慢、功能点出错等
故障影响也需要尽量准确描述。例如,推荐使用“20% 的用户无法登录”,而不是“大部分用户无法登录”。
严重程度
严重程度指站在业务的角度故障的影响程度,一般分为“致命 / 高 / 中 / 低 / 无”五个档次。
严重程度
低:20% 的用户无法修改头像
低:10% 的用户登录时间超过 5 秒
中:所有用户都无法修改资料
中:所有用户登录时间超过 5 秒
高:超过 30% 的用户无法登录
致命:超过 70% 用户无法登录
故障原因
不同的故障原因发生概率不相同
不同的故障原因检测手段不一样
不同的故障原因的处理措施不一样
故障概率
一般分为“高 / 中 / 低”
重点关注
硬件
开源系统
自研系统
风险程度
风险程度就是综合严重程度和故障概率来一起判断某个故障的最终等级,风险程度 = 严重程度 × 故障概率
已有措施
针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等。
措施
检测告警
容错
自恢复
规避措施
规避措施指为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段
处理手段
技术手段
为了避免新引入的 MongoDB 丢失数据,在 MySQL 中冗余一份
管理手段
为了降低磁盘坏道的概率,强制统一更换服务时间超过 2 年的磁盘
解决措施
解决措施指为了能够解决问题而做的一些事情,一般都是技术手段
措施
为了解决密码暴力破解,增加密码重试次数限制
为了解决拖库导致数据泄露,将数据库中的敏感数据加密保存
为了解决非法访问,增加白名单控制
后续规划
综合前面的分析,就可以看出哪些故障我们目前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划。这些规划既可以是技术手段,也可以是管理手段;可以是规避措施,也可以是解决措施。同时需要考虑资源的投入情况,优先将风险程度高的系统隐患解决。
高可用存储架构
双机架构
对于高可用方案的考虑方面
数据如何复制
各个节点的职责是什么
如何应对复制延迟
如何应对复制中断
常见的双机高可用架构
主备
主从
主备/主从切换
主主
主备复制
主备复制是最常见也是最简单的一种存储高可用方案,几乎所有的存储系统都提供了主备复制的功能,例如 MySQL、Redis、MongoDB 等。
优点
对于客户端来说,不需要感知备机的存在,即使灾难恢复后,原来的备机被人工修改为主机后,对于客户端来说,只是认为主机的地址换了而已,无须知道是原来的备机升级为主机。
对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作
缺点
备机仅仅只为备份,并没有提供读写操作,硬件成本上有浪费
故障后需要人工干预,无法自动恢复
主从复制
主机负责读写操作,从机只负责读操作,不负责写操作。
优点
主从复制在主机故障时,读操作相关的业务可以继续运行
主从复制架构的从机提供读操作,发挥了硬件的性能
缺点
主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高
主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。
故障时需要人工干预
综合主从复制的优缺点,一般情况下,写少读多的业务使用主从复制的存储架构比较多。例如,论坛、BBS、新闻网站这类业务,此类业务的读操作数量是写操作数量的 10 倍甚至 100 倍以上。
双机切换
主从或主备存在的缺点
主机故障后,无法进行写操作
如果主机无法恢复,需要人工指定新的主机角色
双机切换就是为了解决这两个问题而产生的,包括主备切换和主从切换两种方案。简单来说,这两个方案就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换
方案的设计关键点
主备间状态判断
状态传递的渠道
是相互间互相连接,还是第三方仲裁
状态检测的内容
例如机器是否掉电、进程是否存在、响应是否缓慢等
切换决策
切换时机
什么情况下备机应该升级为主机
是机器掉电后备机才升级,还是主机上的进程不存在就升级,还是主机响应时间超过 2 秒就升级,还是 3 分钟内主机连续重启 3 次就升级等。
切换策略
原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机?
自动程度
切换是完全自动的,还是半自动的?例如,系统判断当前需要切换,但需要人工做最终的确认操作(例如,单击一下“切换”按钮)。
数据冲突解决
当原有故障的主机恢复后,新旧主机之间可能存在数据冲突
常见架构
互连式
互连式就是指主备机直接建立状态传递的渠道
在主备复制的架构基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的
缺点
如果状态传递的通道本身有故障,那么备机也会认为主机故障了从而将自己升级为主机,而此时主机并没有故障,最终就可能出现两个主机。
中介式
中介式指的是在主备两者之外引入第三方中介,主备机之间不直接连接,而都去连接中介,并且通过中介来传递状态信息
连接管理更简单
状态决策更简单
开源方案已经有比较成熟的中介式解决方案,例如 ZooKeeper 和 Keepalived。
模拟式
模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态
优点
模拟式切换与互连式切换相比,优点是实现更加简单,因为省去了状态传递通道的建立和管理工作。
缺点
基于有限的状态来做状态决策,可能出现偏差。
主主复制
特点
两台都是主机,不存在切换的概念
客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以
如果采取主主复制架构,必须保证数据能够双向复制,而很多数据是不能双向复制的
因此,主主复制架构对数据的设计有严格的要求,一般适合于那些临时性、可丢失、可覆盖的数据场景。例如,用户登录产生的 session 数据(可以重新登录生成)、用户行为的日志数据(可以丢失)、论坛的草稿数据(可以丢失)等。
集群和分区
数据集群
主备、主从、主主架构本质上都有一个隐含的假设:主机能够存储所有数据,但主机本身的存储和处理能力肯定是有极限的,因此需要使用集群(多台机器)
数据集中集群
复杂度体现方面
主机如何将数据复制给备机
多条复制通道首先会增大主机复制的压力,某些场景下我们需要考虑如何降低主机复制压力,或者降低主机复制给正常读写带来的压力。
其次,多条复制通道可能会导致多个备机之间数据不一致,某些场景下我们需要对备机之间的数据一致性进行检查和修正。
备机如何检测主机状态
主备和主从架构中,只有一台备机需要进行主机状态判断
在数据集中集群架构中,多台备机都需要对主机状态进行判断,而不同的备机判断的结果可能是不同的,如何处理不同备机对主机状态的不同判断,是一个复杂的问题
主机故障后,如何决定新的主机
主从架构中,如果主机故障,将备机升级为主机即可
而在数据集中集群架构中,有多台备机都可以升级为主机,但实际上只能允许一台备机升级为主机,那么究竟选择哪一台备机作为新的主机,备机之间如何协调,这也是一个复杂的问题。
目前开源的数据集中集群以 ZooKeeper 为典型,ZooKeeper 通过 ZAB 算法来解决上述提到的几个问题,但 ZAB 算法的复杂度是很高的
数据分散集群
数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;
同时,为了提升硬件利用率,每台服务器又会备份一部分数据。
复杂体现方面
数据分散集群的复杂点在于如何将数据分配到不同的服务器上
均衡性
算法需要保证服务器上的数据分区基本是均衡的,不能存在某台服务器上的分区数量是另外一台服务器的几倍的情况。
容错性
当出现部分服务器故障时,算法需要将原来分配给故障服务器的数据分区分配给其他服务器。
可伸缩性
当集群容量不够,扩充新的服务器后,算法能够自动将部分数据分区迁移到新服务器,并保证扩容后所有服务器的均衡性
两种集群对比
数据集中集群
数据集中集群架构中,客户端只能将数据写到主机
一般来说,数据集中集群适合数据量不大,集群机器数量不多的场景
例如,ZooKeeper 集群,一般推荐 5 台机器左右,数据量是单台服务器就能够支撑
数据分散集群
数据分散集群架构中,客户端可以向任意服务器中读写数据
而数据分散集群,由于其良好的可伸缩性,适合业务数据量巨大、集群机器数量庞大的业务场景。
例如,Hadoop 集群、HBase 集群,大规模的集群可以达到上百台甚至上千台服务器
数据分区
概念
指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响
设计架构应考虑的方面
数据量
数据量的大小直接决定了分区的规则复杂度
例如,使用 MySQL 来存储数据,假设一台 MySQL 存储能力是 500GB,那么 2TB 的数据就至少需要 4 台 MySQL 服务器;
而如果数据是 200TB,并不是增加到 800 台的 MySQL 服务器那么简单。
复杂度体现的方面
多集群定位故障服务器困难,运维复杂度极高
增加新的服务,分区相关的配置甚至规则需要修改,较为复杂
大数据量,应该考虑地理容灾
分区规则
地理位置有近有远,因此可以得到不同的分区规则,包括洲际分区、国家分区、城市分区
洲际分区主要用于面向不同大洲提供服务,由于跨洲通讯的网络延迟已经大到不适合提供在线服务了,因此洲际间的数据中心可以不互通或者仅仅作为备份
国家分区主要用于面向不同国家的用户提供服务,不同国家有不同语言、法律、业务等,国家间的分区一般也仅作为备份
城市分区由于都在同一个国家或者地区内,网络延迟较低,业务相似,分区同时对外提供服务,可以满足业务异地多活之类的需求
复制规则
复制规则
集中式
集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心
优点
设计简单,各分区之间并无直接联系,可以做到互不影响
扩展容易,如果要增加第四个分区(例如,武汉分区),只需要将武汉分区的数据复制到西安备份中心即可,其他分区不受影响
缺点
成本较高,需要建设一个独立的备份中心
互备式
互备式备份指每个分区备份另外一个分区的数据
缺点
设计比较复杂,各个分区除了要承担业务数据存储,还需要承担备份功能,相互之间互相关联和影响
扩展麻烦
优点
成本低,直接利用已有的设备
独立式
独立式备份指每个分区自己有独立的备份中心
各个分区的备份并不和原来的分区在一个地方
优点
设计简单,各分区互不影响
扩展容易,新增加的分区只需要搭建自己的备份中心即可
缺点
成本高,每个分区需要独立的备份中心,备份中心的场地成本是主要成本,因此独立式比集中式成本要高很多。
计算高可用架构
概念
计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。
因此计算高可用的本质是通过冗余来规避部分故障的风险,单台服务器是无论如何都达不到这个目标的。
所以计算高可用的设计思想很简单:通过增加更多服务器来达到计算高可用
架构分类
主备
冷备
备机上的程序包和配置文件都准备好,但备机上的业务系统没有启动(注意:备机的服务器是启动的),主机故障后,需要人工手工将备机的业务系统启动,并将任务分配器的任务请求切换发送给备机。
温备
机上的业务系统已经启动,只是不对外提供服务,主机故障后,人工只需要将任务分配器的任务请求切换发送到备机即可。冷备可以节省一定的能源,但温备能够大大减少手工操作时间,因此一般情况下推荐用温备的方式
优点
简单,主备机之间不需要进行交互,状态判断和切换操作由人工执行,系统实现很简单
缺点
人工操作的时间不可控,可能系统已经发生问题了,但维护人员还没发现
发现后人工切换的操作效率也比较低
手工操作过程中容易出错
主从
优点
主从架构的从机也执行任务,发挥了从机的硬件性能
缺点
主从架构需要将任务分类,任务分配器会复杂一些
集群
对称集群
任务分配器需要选取分配策略
任务分配器需要检测服务器状态
非对称集群
非对称集群中不同服务器的角色是不同的,不同角色的服务器承担不同的职责
以 Master-Slave 为例,部分任务是 Master 服务器才能执行,部分任务是 Slave 服务器才能执行
业务高可用
异地多活架构
概念
顾名思义,异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”
正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务
某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。
架构模式
同城异区
同城异区指的是将业务部署在同一个城市不同区的多个机房
同城的两个机房,距离上一般大约就是几十千米,通过搭建高速的网络,同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度
跨城异地
跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些
有效应对极端灾难事件
距离增加带来的最主要问题是两个机房的网络传输速度会降低
中间传输各种不可控的因素也非常多
支付宝等金融相关的系统,对余额这类数据,一般不会做跨城异地的多活架构,而只能采用同城异区这种架构
而对数据一致性要求不那么高,或者数据不怎么改变,或者即使数据丢失影响也不大的业务,跨城异地多活就能够派上用场了
跨国异地
跨国异地指的是业务部署在不同国家的多个机房
为不同地区用户提供服务
只读类业务做多活
异地多活设计4大技巧
保证核心业务的异地多活
很多业务无法做异地多活
注册问题
用户信息问题
保证核心数据最终一致性
减少影响的措施
尽量减少异地多活机房的距离,搭建高速网络
尽量减少数据同步,只同步核心业务相关的数据
保证最终一致性,不保证实时一致性
采用多种手段同步数据
实例
MySQL 的主备复制
MySQL 5.1 版本的复制是单线程的复制,在网络抖动或者大量数据同步时,经常发生延迟较长的问题,短则延迟十几秒,长则可能达到十几分钟
Redis 的 Cluster 功能
Redis 3.0 之前没有 Cluster 功能,只有主从复制功能,而为了设计上的简单,Redis 2.8 之前的版本,主从复制有一个比较大的隐患:从机宕机或者和主机断开连接都需要重新连接主机,重新连接主机都会触发全量的主从复制。这时主机会生成内存快照,主机依然可以对外提供服务,但是作为读的从机,就无法提供对外服务了,如果数据量大,恢复的时间会相当长。
Elasticsearch 的集群功能
手段
消息队列方式
二次读取方式
存储系统同步方式
回源读取方式
重新生成数据方式
只保证绝大部分用户的异地多活
核心思想
异地多活设计的理念可以总结为一句话:采用多种手段,保证绝大部分用户的核心业务异地多活
异地多活设计4步骤
业务分级
分级标准
访问量大的业务
核心业务
产生大量收入的业务
数据分类
数据特征分析纬度
数据量
唯一性
实时性
可恢复性
可丢失性
数据同步
数据同步方案
存储系统同步
消息队列同步
重复生成
异常处理
目的
问题发生时,避免少量数据异常导致整体业务不可用
问题恢复后,将异常的数据进行修正
对用户进行安抚,弥补用户损失
处理措施
多通道同步
同步和访问结合
日志记录
用户补偿
接口级故障
导致故障原因
内部原因
外部原因
核心思想
优先保证核心业务和优先保证绝大部分用户
措施
降级
概念
降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能
降级的核心思想就是丢车保帅,优先保证核心业务
系统后门降级
独立降级系统
熔断
概念
降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。
熔断机制实现的关键是需要有一个统一的 API 调用层,由 API 调用层来进行采样或者统计,如果接口调用散落在代码各处就没法进行统一处理了。
熔断机制实现的另外一个关键是阈值的设计
限流
概念
降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障
限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。
类型
基于请求限流
基于资源限流
排队
排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间
高性能架构模式
高性能数据库集群
读写分离
原理
读写分离的基本原理是将数据库读写操作分散到不同的节点上
主从与主备的区别
从机的从可以理解为仆从,仆从是要帮主人干活的,从机需要提供读数据的功能
备机一般被认为仅仅提供备份功能,不提供访问功能
基本实现
数据库服务器搭建主从集群,一主一从、一主多从都可以
数据库主机负责读写操作,从机只负责读操作
数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
业务服务器将写操作发给数据库主机,将读操作发给数据库从机
设计复杂度
主从复制延迟
以 MySQL 为例,主从复制延迟可能达到 1 秒,如果有大量数据同步,延迟 1 分钟也是有可能的
带来的问题
如果业务服务器将数据写入到数据库主服务器后立刻(1 秒内)进行读取,此时读操作访问的是从机,主机还没有将数据复制过来,到从机读取数据是读不到最新数据的,业务上就可能出现问题
解决主从复制延迟常见方法
写操作后的读操作指定发给数据库主服务器
读从机失败后再读一次主机
关键业务读写操作全部指向主机,非关键业务采用读写分离
分配机制
将读写操作区分开来,然后访问不同的数据库服务器,一般有两种方式:程序代码封装和中间件封装。
程序代码封装
代码中抽象一个数据访问层,实现读写操作分离和数据库服务器连接的管理
特点
实现简单,而且可以根据业务做较多定制化的功能
每个编程语言都需要自己实现一次,无法通用,如果一个业务包含多个编程语言写的多个子系统,则重复开发的工作量比较大。
障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启
开源方案
淘宝的 TDDL(Taobao Distributed Data Layer,外号: 头都大了)
原理
它是一个通用数据访问层,所有功能封装在 jar 包中提供给业务代码调用。
其基本原理是一个基于集中式配置的 jdbc datasource 实现,具有主备、读写分离、动态数据库配置等功能
中间件封装
中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理
中间件对业务服务器提供 SQL 兼容的协议,业务服务器无须自己进行读写分离。对于业务服务器来说,访问中间件和访问数据库没有区别,事实上在业务服务器看来,中间件就是一个数据库服务器
特点
能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准 SQL 接口
数据库中间件要支持完整的 SQL 语法和数据库服务器的协议(例如,MySQL 客户端和服务器的连接协议),实现比较复杂,细节特别多,很容易出现 bug,需要较长的时间才能稳定。
数据库中间件自己不执行真正的读写操作,但所有的数据库操作请求都要经过中间件,中间件的性能要求也很高
数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态。例如,向某个测试表写入一条数据,成功的就是主机,失败的就是从机
开源方案
MySQL 官方先是提供了 MySQL Proxy
现在 MySQL 官方推荐 MySQL Router
MySQL Router 的主要功能有读写分离、故障自动切换、负载均衡、连接池等
奇虎 360 公司也开源了自己的数据库中间件 Atlas
Atlas 是基于 MySQL Proxy 实现的
数据库读写分离一般应用于什么场景?能支撑多大的业务规模?
适用场景
读写分离适用单机并发无法支撑并且读的请求更多的情形
在单机数据库情况下,表上加索引一般对查询有优化作用却影响写入速度,读写分离后可以单独对读库进行优化,写库上减少索引,对读写的能力都有提升,且读的提升更多一些。
不适用场景
如果并发写入特别高,单机写入无法支撑,就不适合这种模式
通过缓存技术或者程序优化能够满足要求
缺点
读写分离分散了数据库读写操作的压力,但没有分散存储压力,当数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力会成为系统的瓶颈
数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会下降
数据文件会变得很大,数据库备份和恢复需要耗费很长时间
数据文件越大,极端情况下丢失数据的风险越高(例如,机房火灾导致数据库主备机都发生故障)。
分库分表
单个数据库服务器存储的数据量不能太大,需要控制在一定的范围内。为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上。
业务分库
业务分库指的是按照业务模块将数据分散到不同的数据库服务器
虽然业务分库能够分散存储和访问压力,但同时也带来了新的问题
join 操作问题
业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
事务问题
原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。
成本问题
业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台
业务分表
同一业务的单表数据也会达到单台数据库服务器的处理瓶颈,则需要分表
分表方式
垂直分表
从上往下切就是垂直切分
对应到表的切分就是表记录数相同但包含不同的列
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去
引入新的复杂性
垂直分表引入的复杂性主要体现在表操作的数量要增加
水平分表
从左往右切就是水平切分
对应到表的切分就是表的列相同但包含不同的行数据
水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能
引入新的复杂性
路由
范围路由
选取有序的数据列(例如,整形、时间戳等)作为路由的条件,不同分段分散到不同的数据库表中。以最常见的用户 ID 为例,路由算法可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到数据库 1 的表中,1000000 ~ 1999999 放到数据库 2 的表中,以此类推。
范围路由设计的复杂点
分段大小的选取上,分段太小会导致切分后子表数量过多,增加维护复杂度
分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小
优点
范围路由的优点是可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万,只需要增加新的表就可以了,原有的数据不需要动
缺点
分布不均匀,假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1000 条,而另外一个分段实际存储的数据量有 900 万条
Hash路由
选取某个列(或者某几个列组合也可以)的值进行 Hash 运算,然后根据 Hash 结果分散到不同的数据库表中
复杂点
Hash 路由设计的复杂点主要体现在初始表数量的选取上,表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题
而用了 Hash 路由后,增加子表数量是非常麻烦的,所有数据都要重分布。
优点
表分布比较均匀
缺点
扩充新的表很麻烦,所有数据都要重分布。
配置路由
配置路由就是路由表,用一张独立的表来记录路由信息
优点
配置路由设计简单,使用起来非常灵活,尤其是在扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了
缺点
必须多查询一次,会影响整体性能
而且路由表本身如果太大(例如,几亿条数据),性能同样可能成为瓶颈
join操作
count操作
水平分表后,虽然物理上数据分散到多个表中,但某些业务逻辑上还是会将这些表当作一个表来处理。例如,获取记录总数用于分页或者展示,水平分表前用一个 count() 就能完成的操作,在分表后就没那么简单了
count()相加
具体做法是在业务代码或者数据库中间件中对每个表进行 count() 操作,然后将结果相加。
优点
实现简单
缺点
性能比较低
记录表数
具体做法是新建一张表,假如表名为“记录数表”,包含 table_name、row_count 两个字段,每次插入或者删除子表数据成功后,都更新“记录数表”
优点
这种方式获取表记录数的性能要大大优于 count() 相加的方式,因为只需要一次简单查询就可以获取数据
缺点
缺点是复杂度增加不少,对子表的操作要同步操作“记录数表”,如果有一个业务逻辑遗漏了,数据就会不一致
且针对“记录数表”的操作和针对子表的操作无法放在同一事务中进行处理,异常的情况下会出现操作子表成功了而操作记录数表失败,同样会导致数据不一致。
定时更新count的记录数
order by操作
水平分表后,数据分散到多个子表中,排序操作无法在数据库中完成,只能由业务代码或者数据库中间件分别查询每个子表中的数据,然后汇总进行排序。
什么时候引入分库分表是合适的?是数据库性能不够的时候就开始分库分表么?
应该是这些操作依次尝试
做硬件优化
先做数据库服务器的调优操作,例如增加索引
引入缓存技术,例如Redis,减少数据库压力
程序与数据库表优化,重构,例如根据业务逻辑对程序逻辑做优化,减少不必要的查询;
在这些操作都不能大幅度优化性能的情况下,不能满足将来的发展,再考虑分库分表,也要有预估性
高性能NoSQL
关系型数据库存在以下缺点
关系数据库存储的是行记录,无法存储数据结构
关系数据库的 schema 扩展很不方便
关系数据库在大数据场景下 I/O 较高
关系数据库的全文搜索功能比较弱
因此我们不能盲目地迷信 NoSQL 是银弹,而应该将 NoSQL 作为 SQL 的一个有力补充,NoSQL != No SQL,而是 NoSQL = Not Only SQL。
常见的nosql方案
K-V 存储:解决关系数据库无法存储数据结构的问题
redis
Redis 是 K-V 存储的典型代表,它是一款开源(基于 BSD 许可)的高性能 K-V 缓存和存储系统
Redis 的 Value 是具体的数据结构,包括 string、hash、list、set、sorted set、bitmap 和 hyperloglog,所以常常被称为数据结构服务器
缺点
Redis 的缺点主要体现在并不支持完整的 ACID 事务
Redis 虽然提供事务功能,但 Redis 的事务和关系数据库的事务不可同日而语,Redis 的事务只能保证隔离性和一致性(I 和 C),无法保证原子性和持久性(A 和 D)。
虽然 Redis 并没有严格遵循 ACID 原则,但实际上大部分业务也不需要严格遵循 ACID 原则。
因此我们在设计方案时,需要根据业务特性和要求来确定是否可以用 Redis,而不能因为 Redis 不遵循 ACID 原则就直接放弃。
文档数据库:解决关系数据库强 schema 约束的问题
MongoDB
为了解决关系数据库 schema 带来的问题,文档数据库应运而生。文档数据库最大的特点就是 no-schema,可以存储和读取任意的数据。
目前绝大部分文档数据库存储的数据格式是 JSON(或者 BSON),因为 JSON 数据是自描述的,无须在使用前定义字段,读取一个 JSON 中不存在的字段也不会导致 SQL 那样的语法错误。
优势
新增字段简单
历史数据不会出错
可以很容易存储复杂数据
缺点
文档数据库 no-schema 的特性带来的这些优势也是有代价的,最主要的代价就是不支持事务。
文档数据库另外一个缺点就是无法实现关系数据库的 join 操作
列式数据库:解决关系数据库大数据场景下的 I/O 问题
顾名思义,列式数据库就是按照列来存储数据的数据库,与之对应的传统关系数据库被称为“行式数据库
HBase
优势
业务同时读取多个列时效率高,因为这些列都是按行存储在一起的,一次磁盘操作就能够把一行数据中的各个列都读取到内存中
能够一次性完成对一行中的多个列的写操作,保证了针对行数据写操作的原子性和一致性;否则如果采用列存储,可能会出现某次写操作,有的列成功了,有的列失败了,导致数据不一致。
行式存储的优势是在特定的业务场景下才能体现,如果不存在这样的业务场景,那么行式存储的优势也将不复存在,甚至成为劣势,典型的场景就是海量数据进行统计
缺点
典型的场景是需要频繁地更新多个列。因为列式存储将不同列存储在磁盘上不连续的空间,导致更新多个列时磁盘是随机写操作;而行式存储时同一行多个列都存储在连续的空间,一次磁盘写操作就可以完成,列式存储的随机写效率要远远低于行式存储的写效率。
列式存储高压缩率在更新场景下也会成为劣势,因为更新时需要将存储数据解压后更新,然后再压缩,最后写入磁盘
使用场景
一般将列式存储应用在离线的大数据分析和统计场景中,因为这种场景主要是针对部分列单列进行操作,且数据写入后就无须再更新删除
全文搜索引擎:解决关系数据库的全文搜索性能问题
ES
对比关系型数据优点
全文搜索的条件可以随意排列组合,如果通过索引来满足,则索引的数量会非常多
全文搜索的模糊匹配方式,索引无法满足,只能用 like 查询,而 like 查询是整表扫描,效率非常低
原理
全文搜索引擎的技术原理被称为“倒排索引”(Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,其基本原理是建立单词到文档的索引
之所以被称为“倒排”索引,是和“正排“索引相对的,“正排索引”的基本原理是建立文档到单词的索引。
正排索引适用于根据文档名称来查询文档内容
倒排索引适用于根据关键词来查询文档内容
全文搜索引擎的索引对象是单词和文档
而关系数据库的索引对象是键和行
高性能缓存框架
无法依靠存储提升性能的场景
需要经过复杂运算后得出的数据,存储系统无能为力
读多写少的数据,存储系统有心无力
缓存架构的复杂点
缓存穿透
情况1
存储数据不存在
解决方法
如果查询存储系统的数据没有找到,则直接设置一个默认值(可以是空值,也可以是具体的值)存到缓存中,这样第二次读取缓存时就会获取到默认值,而不会继续访问存储系统。
情况2
缓存数据生成耗费大量时间或者资源
分页缓存的有效期设置为 1 天
通常情况下,用户不会从第 1 页到最后 1 页全部看完,一般用户访问集中在前 10 页,因此第 10 页以后的缓存过期失效的可能性很大
竞争对手每周来爬取数据,爬虫会将所有分类的所有数据全部遍历,从第 1 页到最后 1 页全部都会读取,此时很多分页缓存可能都失效了
解决方法
通常的应对方案要么就是识别爬虫然后禁止访问,但这可能会影响 SEO 和推广;
要么就是做好监控,发现问题后及时处理,因为爬虫不是攻击,不会进行暴力破坏,对系统的影响是逐步的,监控发现问题后有时间进行处理。
缓存雪崩
描述
指当缓存失效(过期)后引起系统性能急剧下降的情况
当缓存过期被清除后,业务系统需要重新生成缓存,因此需要再次访问存储系统,再次进行运算,这个处理步骤耗时几十毫秒甚至上百毫秒
由于旧的缓存已经被清除,新的缓存还未生成,并且处理这些请求的线程都不知道另外有一个线程正在生成缓存,因此所有的请求都会去重新生成缓存,都会去访问存储系统,从而对存储系统造成巨大的性能压力
解决方法
更新锁机制
对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
因此分布式集群的业务系统要实现更新锁机制,需要用到分布式锁,如 ZooKeeper。
后台更新机制
由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存
后台定时机制需要考虑一种特殊的场景,当缓存系统内存不够时,会“踢掉”一些缓存数据,从缓存被“踢掉”到下一次定时更新缓存的这段时间内,业务线程读取缓存返回空值,而业务线程本身又不会去更新缓存,因此业务上看到的现象就是数据丢了
业务线程发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。可能会出现多个业务线程都发送了缓存更新消息,但其实对后台线程没有影响,后台线程收到消息后更新缓存前可以判断缓存是否存在,存在就不执行更新操作。这种方式实现依赖消息队列,复杂度会高一些,但缓存更新更及时,用户体验更好。
缓存热点
虽然缓存系统本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大
解决方法
缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力
缓存副本设计有一个细节需要注意,就是不同的缓存副本不要设置统一的过期时间,否则就会出现所有缓存副本同时生成同时失效的情况,从而引发缓存雪崩效应。正确的做法是设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值
单服务器高性能模式
服务器采取的并发模型
服务器如何管理连接
服务器如何处理请求
最终相关的技术点
I/O模型
阻塞/非阻塞
同步/异步
进程模型
单进程
多进程
多线程
高性能模式
PPC
PPC 是 Process Per Connection 的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的 UNIX 网络服务器所采用的模型
流程
父进程接受连接
父进程“fork”子进程
子进程处理连接的读写请求
子进程关闭连接
优点
模式实现简单,比较适合服务器的连接数没那么多的情况
缺点
fork 代价高
父子进程通信复杂
支持的并发连接数量有限
prefork
PPC 模式中,当连接进来时才 fork 新进程来处理连接请求,由于 fork 进程代价高,用户访问时可能感觉比较慢,prefork 模式的出现就是为了解决这个问题
TPC
TPC 是 Thread Per Connection 的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求
特点
与进程相比,线程更轻量级,创建线程的消耗比进程要少得多
同时多线程是共享进程内存空间的,线程通信相比进程通信更简单
TPC 实际上是解决或者弱化了 PPC fork 代价高的问题和父子进程通信复杂的问题
流程
父进程接受连接
父进程创建子线程
子线程处理连接的读写请求
子线程关闭连接
缺点
创建线程虽然比创建进程代价低,但并不是没有代价,高并发时(例如每秒上万连接)还是有性能问题
无须进程间通信,但是线程间的互斥和共享又引入了复杂度,可能一不小心就导致了死锁问题。
多线程会出现互相影响的情况,某个线程出现异常时,可能导致整个进程退出(例如内存越界)。
prethread
TPC 模式中,当连接进来时才创建新的线程来处理连接请求,虽然创建线程比创建进程要更加轻量级,但还是有一定的代价,而 prethread 模式就是为了解决这个问题
Reactor
特点
Reactor 模式也叫 Dispatcher 模式(在很多开源的系统里面会看到这个名称的类,其实就是实现 Reactor 模式的),更加贴近模式本身的含义,即 I/O 多路复用统一监听事件,收到事件后分配(Dispatch)给某个进程。
Reactor 模式的核心组成部分包括 Reactor 和处理资源池(进程池或线程池),其中 Reactor 负责监听和分配事件,处理资源池负责处理事件
Reactor具体实现方式多变
Reactor 的数量可以变化:可以是一个 Reactor,也可以是多个 Reactor
资源池的数量可以变化:以进程为例,可以是单个进程,也可以是多个进程(线程类似)。
与PPC相比较
PPC 模式最主要的问题就是每个连接都要创建进程(为了描述简洁,这里只以 PPC 和进程为例,实际上换成 TPC 和线程,原理是一样的),连接结束后进程就销毁了,这样做其实是很大的浪费
为了解决这个问题,一个自然而然的想法就是资源复用,即不再单独为每个连接创建进程,而是创建一个进程池,将连接分配给进程,一个进程可以处理多个连接的业务。
引出新的问题
进程如何才能高效地处理多个连接的业务
当一个连接一个进程时,进程可以采用“read -> 业务处理 -> write”的处理流程,如果当前连接没有数据可以读,则进程就阻塞在 read 操作上
如果一个进程处理多个连接,进程阻塞在某个连接的 read 操作上,此时即使其他连接有数据可读,进程也无法去处理,很显然这样是无法做到高性能的。
解决方法
将 read 操作改为非阻塞,然后进程不断地轮询多个连接。
不停轮询连接耗费CPU,如何做到有效读取连接数据
只有当连接上有数据的时候进程才去处理,这就是 I/O 多路复用技术
解决方法
当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epoll、kqueue 等
当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理
经典实现方案
单Reactor单进程/线程
方案详情
Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发
如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应。
Handler 会完成 read-> 业务处理 ->send 的完整业务流程
优点
很简单
没有进程间通信,没有进程竞争,全部都在同一个进程内完成
缺点
只有一个进程,无法发挥多核 CPU 的性能
Handler 在处理某个连接上的业务时,整个进程无法处理其他连接的事件,很容易导致性能瓶颈。
应用场景
单 Reactor 单进程的方案在实践中应用场景不多,只适用于业务处理非常快速的场景,目前比较著名的开源软件中使用单 Reactor 单进程的是 Redis
C 语言编写系统的一般使用单 Reactor 单进程,因为没有必要在进程中再创建线程
而 Java 语言编写的一般使用单 Reactor 单线程,因为 Java 虚拟机是一个进程,虚拟机中有很多线程,业务线程只是其中的一个线程而已
单Reactor多线程
方案详情
主线程中,Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发。
如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件
如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应
Handler 只负责响应事件,不进行业务处理;Handler 通过 read 读取到数据后,会发给 Processor 进行业务处理
Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理;Handler 收到响应后通过 send 将响应结果返回给 client
优点
单 Reator 多线程方案能够充分利用多核多 CPU 的处理能力
缺点
多线程数据共享和访问比较复杂
Reactor 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈。
多Reactor多进程/线程
方案详情
父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程
子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件。
当有新的事件发生时,subReactor 会调用连接对应的 Handler(即第 2 步中创建的 Handler)来进行响应。
Handler 完成 read→业务处理→send 的完整业务流程
优点
父进程和子进程的职责非常明确,父进程只负责接收新连接,子进程负责完成后续的业务处理。
父进程和子进程的交互很简单,父进程只需要把新连接传给子进程,子进程无须返回数据。
子进程之间是互相独立的,无须同步共享之类的处理(这里仅限于网络模型相关的 select、read、send 等无须同步共享,“业务处理”还是有可能需要同步共享的)。
目前著名的开源系统 Nginx 采用的是多 Reactor 多进程,采用多 Reactor 多线程的实现有 Memcache 和 Netty。
具体使用方案
java使用线程,例如Netty
Nginx使用进程
Memcache使用线程
C语言进程和线程都可以
Proactor
Reactor 可以理解为“来了事件我通知你,你来处理”,而 Proactor 可以理解为“来了事件我来处理,处理完了我通知你”。这里的“我”就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写的这些 I/O 事件,“你”就是我们的程序代码
理论上 Proactor 比 Reactor 效率要高一些,异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠,但要实现真正的异步 I/O,操作系统需要做大量的工作
目前 Windows 下通过 IOCP 实现了真正的异步 I/O,而在 Linux 系统下的 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 Reactor 模式为主。所以即使 Boost.Asio 号称实现了 Proactor 模型,其实它在 Windows 下采用 IOCP,而在 Linux 下是用 Reactor 模式(采用 epoll)模拟出来的异步模型。
高性能负载均衡
分类和架构
概念
单服务器处理性能有天花板,需要设计高性能集群来提升系统整体的处理性能,高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的计算能力
高性能集群的复杂性主要体现在需要增加一个任务分配器(即负载均衡器),以及为任务选择一个合适的任务分配算法
分类
DNS负载均衡
DNS 是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡,即北方用户访问北京的机房,南方用户访问深圳的机房
DNS 负载均衡的本质是 DNS 解析同一个域名可以返回不同的 IP 地址。例如,同样是 www.baidu.com,北方用户解析后获取的地址是 61.135.165.224(这是北京机房的 IP),南方用户解析后获取的地址是 14.215.177.38(这是深圳机房的 IP)。
优点
简单、成本低:负载均衡工作交给 DNS 服务器处理,无须自己开发或者维护负载均衡设备。
就近访问,提升访问速度:DNS 解析时可以根据请求来源 IP,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能。
缺点
更新不及时
DNS 缓存的时间比较长,修改 DNS 配置后,由于缓存的原因,还是有很多用户会继续访问修改前的 IP,这样的访问会失败,达不到负载均衡的目的,并且也影响用户正常使用业务
DNS缓存指DNS返回了正确的IP之后,系统就会将这个结果临时储存起来。 并且它会为缓存设定一个失效时间 (例如N小时),在这N小时之内,当你再次访问这个网站时,系统就会直接从你电脑本地的DNS缓存中把结果交还给你,而不必再去询问DNS服务器,变相“加速”了网址的解析
扩展性差
DNS 负载均衡的控制权在域名商那里,无法根据业务特点针对其做更多的定制化功能和扩展特性
分配策略比较简单
DNS 负载均衡支持的算法少;不能区分服务器的差异(不能根据系统与服务的状态来判断负载);也无法感知后端服务器的状态
硬件负载均衡
硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5 和 A10
优点
功能强大:全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡
性能强大:对比一下,软件负载均衡支持到 10 万级并发已经很厉害了,硬件负载均衡可以支持 100 万以上的并发
稳定性高:商用硬件负载均衡,经过了良好的严格测试,经过大规模使用,稳定性高
支持安全防护:硬件均衡设备除具备负载均衡功能外,还具备防火墙、防 DDoS 攻击等安全功能
缺点
价格昂贵
扩展能力差:硬件设备,可以根据业务进行配置,但无法进行扩展和定制
软件负载均衡
软件负载均衡通过负载均衡软件来实现负载均衡功能
常见的有 Nginx 和 LVS,其中 Nginx 是软件的 7 层负载均衡,LVS 是 Linux 内核的 4 层负载均衡。4 层和 7 层的区别就在于协议和灵活性,Nginx 支持 HTTP、E-mail 协议;而 LVS 是 4 层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等。
和硬件的区别
硬件负载均衡性能远远高于软件负载均衡性能
Nginx 的性能是万级,一般的 Linux 服务器上装一个 Nginx 大概能到 5 万 / 秒
LVS 的性能是十万级,据说可达到 80 万 / 秒
而 F5 性能是百万级,从 200 万 / 秒到 800 万 / 秒都有
优点
简单:无论是部署还是维护都比较简单
便宜:只要买个 Linux 服务器,装上软件即可
灵活:4 层和 7 层负载均衡可以根据业务进行选择;也可以根据业务进行比较方便的扩展,例如,可以通过 Nginx 的插件来实现业务的定制化功能。
缺点
性能一般:一个 Nginx 大约能支撑 5 万并发
功能没有硬件负载均衡那么强大
一般不具备防火墙和防 DDoS 攻击等安全功能
典型架构
地理级负载均衡
集群级别负载均衡
机器级别的负载均衡
算法
分类
任务平分类
概念
负载均衡系统将收到的任务平均分配给服务器进行处理,这里的“平均”可以是绝对数量的平均,也可以是比例或者权重上的平均
轮询
负载均衡系统收到请求后,按照顺序轮流分配到服务器上
轮询是最简单的一个策略,无须关注服务器本身的状态
只要服务器在运行,运行状态是不关注的
但如果服务器直接宕机了,或者服务器和负载均衡系统断连了,这时负载均衡系统是能够感知的,也需要做出相应的处理
加权轮询
负载均衡系统根据服务器权重进行任务分配,这里的权重一般是根据硬件配置进行静态配置的,采用动态的方式计算会更加契合业务,但复杂度也会更高
加权轮询是轮询的一种特殊形式,其主要目的就是为了解决不同服务器处理能力有差异的问题
加权轮询解决了轮询算法中无法根据服务器的配置差异进行任务分配的问题,但同样存在无法根据服务器的状态差异进行任务分配的问题
负载均衡类
概念
负载均衡系统根据服务器的负载来进行分配,这里的负载并不一定是通常意义上我们说的“CPU 负载”,而是系统当前的压力,可以用 CPU 负载来衡量,也可以用连接数、I/O 使用率、网卡吞吐量等来衡量系统的压力
负载均衡系统将任务分配给当前负载最低的服务器,这里的负载根据不同的任务类型和业务场景,可以用不同的指标来衡量
LVS 这种 4 层网络负载均衡设备,可以以“连接数”来判断服务器的状态,服务器连接数越大,表明服务器压力越大
Nginx 这种 7 层网络负载系统,可以以“HTTP 请求数”来判断服务器状态(Nginx 内置的负载均衡算法不支持这种方式,需要进行扩展)
如果我们自己开发负载均衡系统,可以根据业务特点来选择指标衡量系统压力。如果是 CPU 密集型,可以以“CPU 负载”来衡量系统压力;如果是 I/O 密集型,可以以“I/O 负载”来衡量系统压力。
负载最低优先的算法解决了轮询算法中无法感知服务器状态的问题
性能最优类
概念
负载均衡系统根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器
负载最低优先类算法是站在服务器的角度来进行分配的,而性能最优优先类算法则是站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器,通过这种方式达到最快响应客户端的目的。
和负载最低优先类算法类似,性能最优优先类算法本质上也是感知了服务器的状态,只是通过响应时间这个外部标准来衡量服务器状态而已
Hash类
概念
负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上。常见的有源地址 Hash、目标地址 Hash、session id hash、用户 ID Hash 等
负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上,这样做的目的主要是为了满足特定的业务需求
源地址 Hash
ID Hash
互联网架构模板
存储层技术
SQL
Mysql
Oracle
中间件
百度DBProxy
淘宝的TDDL
mysql官方的MYSQL Router
360开源的Atlas
Nosql
Memcache
Redis
MongoDB
ES
Hbase
存储平台
资源动态按需动态分配
资源自动化管理
故障自动化处理
小文件存储
特点
除了关系型的业务数据,互联网行业还有很多用于展示的数据。例如,淘宝的商品图片、商品描述;Facebook 的用户图片;新浪微博的一条微博内容等
这些数据具有三个典型特征:一是数据小,一般在 1MB 以下;二是数量巨大,Facebook 在 2013 年每天上传的照片就达到了 3.5 亿张;三是访问量巨大,Facebook 每天的访问量超过 10 亿。
技术框架
HBase
Hadoop
Hypertable
FastDFS
淘宝的 TFS
京东 JFS
Facebook 的 Haystack
大文件存储
分类
一类是业务上的大数据
另一类是海量的日志数据
成熟的技术框架
Hadoop
HBase
Storm
Hive
淘宝的云梯系统
腾讯的 TDW 系统
开发层
开发框架
原则
优选成熟的框架,避免盲目追逐新技术!
web服务器
Java 的有 Tomcat、JBoss、Resin 等
PHP/Python 的用 Nginx
Apache
容器
Docker 为代表
服务层
配置中心
配置中心就是集中管理各个系统的配置
服务中心
服务中心就是为了解决跨系统依赖的“配置”和“调度”问题
服务名字系统
服务名字系统(Service Name System)
DNS,即 Domain Name System
服务总线系统
看到这个翻译,相信你可能立刻联想到计算机的总线
消息队列
消息队列系统基本功能的实现比较简单,但要做到高性能、高可用、消息时序性、消息事务性则比较难
RocketMQ、Kafka、ActiveMQ,rabbitmq
网络层
负载均衡
DNS
Nginx,LVS,F5
CDN
多机房
同城多机房
跨城多机房
跨国多机房
多中心
关键
数据一致性
数据事务性
用户层
用户管理
单点登录
cookie
JSONP
token
授权登录
OAuth 2.0 协议
消息推送
存储云,图片云
平台技术
运维平台
生命周期
配置
主要负责资源的管理。例如,机器管理、IP 地址管理、虚拟机管理等
部署
主要负责将系统发布到线上。例如,包管理、灰度发布管理、回滚等
监控
主要负责收集系统上线运行后的相关数据并进行监控,以便及时发现问题
应急
主要负责系统出故障后的处理。例如,停止程序、下线故障机器、切换 IP 等
核心四要素
标准化
平台化
自动化
可视化
测试平台
核心职责
单元测试
集成测试
接口测试
性能测试
功能
用例管理
资源管理
任务管理
数据管理
数据平台
数据管理
数据采集
从业务系统搜集各类数据。例如,日志、用户行为、业务数据等,将这些数据传送到数据平台。
数据存储
将从业务系统采集的数据存储到数据平台,用于后续数据分析
数据访问
负责对外提供各种协议用于读写数据。例如,SQL、Hive、Key-Value 等读写协议。
数据安全
通常情况下数据平台都是多个业务共享的,部分业务敏感数据需要加以保护,防止被其他业务读取甚至修改,因此需要设计数据安全策略来保护数据
数据分析
数据统计
根据原始数据统计出相关的总览数据。例如,PV、UV、交易额等
数据挖掘
机器学习、深度学习
数据应用
在线业务
离线业务
管理平台
管理平台的核心职责就是权限管理
功能
身份认证
权限控制
0 条评论
下一页