(2022.06.27 Mon)
存储高可用的方案本质都是将数据复制到多个存储设备,通过数据冗余实现高可用。其复杂性体现在如何应对复制延迟和中断导致的数据不一致问题。高可用存储方案,需要注意如下问题:
- 数据如何复制
- 各个节点的职责
- 如何应对复制延迟
- 如何应对复制中断
常用的高可用存储架构有主备、主从、主主、集群、分区。下面分别介绍。
主备模式primary-replica
主备模式是最常见的存储高可用方案,几乎所有存储系统都提供了主备复制的功能,MySQL、Redis、MongoDB。
主备框图如下
主备方案的设计如下:
- 主机存储数据,通过复制通道将数据复制到备机
- 正常情况下,client无论读写操作,都发送给主机,备机不提供服务
- 主机故障,客户端不会自动将请求发给备机,整个系统处于不可用状态,不能读写,但数据没有丢失
- 若主机恢复,不管人工还是自动,client继续访问主机,主机继续将数据发给备机
- 若主机不恢复,则需要人工操作,将备机升级为主机,然后让客户端访问新主机,原来的备机升级为主机,同时为了保证主备架构,人工增加新的机器作为备机
- 若主机不可恢复,成功写入主机但没有复制到备机的数据丢失,需要人工排查和恢复,同时需要应对数据永久丢失的风险
- 主备间数据有延迟,由于备机不对外提供读写服务,因此对业务无影响。如果延迟多,且主机故障,则可能丢失较多数据。一般做法是做复制延迟的监控措施,当延迟的数据量较大时及时报警,由人工干预处理。
简单来说,备机只起到备份的作用,不承担业务读写。
主备复制的优缺点
优点是简单
- 客户端不需要感知备机的存在,也无需知道主备机的身份在灾难恢复后发生转变
- 主备双方只需要金牛星数据复制,无需进行状态判断和主备倒换这类复杂的操作
缺点
- 备机仅仅只为备份,并没有提供读写操作,硬件成本有浪费
- 故障后需要人工干预,无法自动恢复。
内部的后台管理系统使用主备复制架构比较多,比如学生管理系统、员工管理系统、假期管理系统等。数据变更频率低,即使丢失也可以通过人工的方式补全。
(2022.06.28 Tues)
主从复制master-slave/primary-secondary
不同于主备复制,这里的"从"可理解为帮"主"机器承担任务,也就是"读"的操作。具体来说就是主机负责读写操作,从机只负责读操作。
框架如图
细节如下:
- 主机存储数据,通过复制通道复制到从机
- 客户端写操作发给主机,读操作发给主机和从机,具体的读取对象根据业务特点选择
- 主机故障的情况下写操作不可用,但读操作依然可使用,如论坛的写操作(发帖)不可用,但是读操作(看帖)可以用
- 主机故障恢复,客户端继续发写申请给主机
- 如果主机不能恢复,需要人工将备机升级为主机,执行读写操作;为保持主从架构,增加新的机器作为从机
- 主机不能恢复,成功写入主机但没有复制到从机的数据面临丢失风险,要人工排查,需要考虑这些数据如果永远丢失的风险
- 如果数据延迟大,会导致刚写入的数据无法在从机读出的情况
- 如果延迟多,恰好主机损坏,会导致数据丢失,故复制延迟需要注意。可对复制延迟做监控,延迟较大需要报警和人工干预。
对比主备操作的优缺点:
- 主机故障,读操作不受影响
- 从机提供读操作,发挥了硬件性能
- 主从比主备复杂,体现在客户需要感知主从关系,将不同的操作发给不同主机
综上,业务中读多写少的业务使用主从复制的架构比较多。论坛、新闻等读业务远大于写业务的需求。
主备倒换和主从倒换
设计关键:
- 主备间状态转换
- 状态转换渠道:是否互联互通还是通过第三方
- 检测内容:机器掉电、进程存在、响应时长等
- 倒换决策
- 倒换条件:主备/从机转换的条件判断,是下面若干条件之一,机器掉电、主机上进程不存在、主机响应时间过久比如3秒、短时间内多次重启,或者其他条件
- 倒换后策略:考虑a)备机升级为主机且主机修复后,是否需要再次转换,或者b)主机修复后自动成为新的备机
- 自动程度:自动、半自动、人工
- 数据冲突
故障之后可能存在数据冲突,如旧主机新增数据可能还没有复制到旧的备机,发生转换,旧备机成为主机,但没有新加入旧主机的数据
解决方案
三种方案的区别在于状态传递渠道不同,分别是互连式、中介式、模拟式。
互连式
主备机直接建立状态传递的渠道
主备之间除了数据复制通道,多了一条状态转译通道,实现方式如下:
- 网络连接,比如开新端口,或者非网络连接,如串口线连接
- 主机发送状态给备机,或备机到主机来获取状态信息
- 与数据复制共享一条通道,或独立一条通道
- 状态传输通道可一条可多条,可以是不同类型通道的混合,比如端口加串口
客户端也需要做调整
- 为了不影响客户端的访问,主、备机之间共享一个对客户端来说唯一的地址,比如虚拟ip,主机绑定这个虚拟ip
- 客户端记录主备机的地址,哪个能访问就访问哪个,备机也许可以收到客户端的操作请求,但会以不提供服务的原因直接拒绝
这种直连的转换形式简单,缺点是状态转换通道本身有故障,则备机会认为主机故障从而将自身升级为主机,如果恰好主机没故障,则变成了两个主机。通过增加状态转换通道的方式可降低故障概率,但无法降为0,甚至可能从不同的转换通道收到不同甚至矛盾的信息。
中介式
在主备之间引入第三方中介,主备都连中介,并通过中介传递状态信息,架构如图
中介式架构有如下特点
- 连接管理更简单
不在需要建立和管理状态连接通道,只需连接中介,降低了管理复杂度。 - 状态决策简单
主备机无需通过判断连接通道的状态信息做决策,只需要传递信息给中介并根据如下规则判断- 主、备机的初始状态都是备机,且只要与中介断开连接就降为备机,可能出现双备机情况
- 主机与中介断开连接,中介通知备机该信息,备机升级为主机
- 网络中断导致主机与中介断开连接,主机降为备机,网络恢复后,旧的主机以新的备机身份向中介上报状态
- 若掉电重启或进程重启,旧主机变为备机,与中介恢复连接,发现有了主机,则保持备机状态不变
- 主备机与中介连接都正常,按其他情况决定是否转换,比如主机响应时间超过3秒,则进行转换,主机降为备机,备机升为主机
这种架构的成本是中介机器本身就是高可用的机器,如果中介崩溃,则整个系统面临崩溃的局面。
mongoDB的replica set采取的就是中介式架构。
中介式架构中的中介可以通过ZooKeeper来实现,这个开源项目实现了统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等,且它本身也是高可用系统。
模拟式
主备机之间不传递任何状态数据,而备机模拟成一个客户端,向主机发起模拟读写操作,根据读写操作的响应情况来判断主机的状态。
架构如图
模拟式的架构实现简单,无需状态传递通道的建立和管理。不过缺点在于备机获得的状态信息只有应用层的响应信息,没有互连式的多样化信息,如CPU负载,IO负载,吞吐量、响应时间等的信息,用于做决策的信息很有限。
主主复制
两台主机都是主机,互相备份数据给对方,客户端任一挑选一个机器做读写操作,架构如图
业务上需要考虑的风险是主机1在故障后无法恢复,写入该主机但没有复制的给主机2的数据会丢失,需人工排查和恢复,有的数据可能永远丢失。另一个需要考虑的点是写入1的机器从2中因复制延迟无法瞬时读取。
主主模式无需倒换和区分不同的角色。看起来简单,但架构不简单,表现为要保证数据能够双向复制,但很多数据可能无法双向复制,比如
- auto increment的用户id,在1号主机创建,为200,则此时2号主机创建id则要跳过200
- 主机1的库存是300,连续卖掉两件,库存298,主机2的库存卖掉1件,变为299,此时主机1数据复制到2,2的库存变为298
主主模式对数据的设计有要求,一般适用于临时性、可丢失、可覆盖的数据场景,如用户登录产生的session(可重新登录生成),用户行为日志(可丢),论坛草稿(可丢).
(2022.06.29 Wed)
数据分区
这里的分区指的是按一定的规则在地理上的分去,每个分区存储一部分数据,规避地理级别的故障造成的巨大影响。设计分区需要考虑如下方面。
数据量
数据量过大,则分区规则越复杂,考虑的情况越多。
分区规则
地理位置决定不同的分区规则,比如按大洲分区,国家城市时区等等分区。
大洲分区面向不同大洲提供服务,国家等以此类推。
复制规则
为应对数据的灾难情况,需要数据的复制,有下面三种情况
- 集中式
不同分区的数据存在一个中心数据中心上。结构简单,分区之间无直接通信;扩展容易;成本较高,需要建立一个独立的备份中心。
*互备式
每个分区备份另外一个分区的数据。设计比较复杂,每个分去都要承担数据存储和承担备份功能,相互之间要关联和影响;扩展麻烦;成本低,直接使用已有的设备。 - 独立式
每个分区有独立的备份中心,注意保证分区和备份之间的地理距离,保证电力或天气等因素对两处地点的影响最小。设计简单,分区不影响;扩展容易;成本高。
数据集群
集群是多台及其组合在一起形成统一的系统,数量至少是3台。根据机器承担的不同角色划分,集群分为两类,数据集中集群、数据分散集群。
数据集中集群
所谓数据集中,指的是集群结构为一主多备/从,数据只能在主机中写,读操作可以主也可以从备/从,根据主备、主从架构灵活应变。
复杂度提高,相比于一主一备/从架构,主要体现在:
- 主机复制数据给备机
一主一备/从的架构中,只有一条复制通道,而数据集群中有多条这样的通道。多通道增大主机复制的压力,需考虑降低主机复制压力、降低主机复制给正常读写带来的压力
多通道可能导致多个备机之间数据不一致,可能需要对备机之间的数据一致性进行检查和修正,ZooKeeper在重新选举Leader后会进入恢复状态 - 备机检测主机状态的方式
多台备机需要对主机状态进行判断,而不同的备机判断的结果可能是不同的,处理这些不同的判断较为棘手 - 主机故障后如何决定新主机
备机之间需要协调
目前集中式集群ZooKeeper为典型,通过ZAB协议来解决上述问题,但ZAB协议复杂,实现难度高。
数据分散集群
分散集群指多个服务器组成的集群中每台服务器负责存储一部分数据,每个服务器又会备份一部分数据。
分配数据到不同的服务器上的算法需要考虑如下方面:
- 均衡性
保证数据分区基本是均衡的,不能存在多于几倍的情况 - 容错性
当部分服务故障时,需要将原来分配给故障服务器的数据分区分配给其他服务器 - 可伸缩性
扩充新的服务器之后,需要能够自动将部分数据迁移到新服务器,并保证扩容性的均衡性
分区集群中每台服务器都可以处理读写请求,不存在集中集群中负责写主机那样的角色。在分区集群中,需要有一台来处理数据分配算法,这个机器可以是独立的一台,也可以是集群选举出来的。如果是选举得到,这台机器一般被称为主机,不过这个主机和数据集中集群中的主机在职责上并不相同。
Hadoop的实现是独立的服务器负责数据分区的分配,叫做Namenode。其他节点称为datanode。
HDFS采用master/slave(primary/secondary)架构。一个HDFS集群由一个namenode和多个datanode组成,namenode是中心服务器,负责管理文件系统的名字空间,及客户端对文件的访问。datanode一般是一个节点一个,负责管理所在节点的存储。用户在HDFS上能够以文件的形式存储数据。一个文件被分成多个数据块,这些块存储在一组datanode上。namenode执行文件系统的名字空间操作,如打开、关闭、重命名文件或目录,也负责确定数据块到具体datanode节点的映射。datanode负责处理文件系统客户端的读写请求。在namenode的统一调度下进行数据块的创建、删除和复制。
ElasticSearch集群选举一台服务器做数据分区的分配,称为master node。
数据集中集群,client只能将数据写到主机;数据分散集群,client可以向集群中任意服务器读写数据。这种差异决定了应用场景。集中式适合数据量不大,集群机器数量不多的场景,如ZooKeeper集群,机器一般5台;分散式集群,因其良好的可扩展性,事业业务数据量大,集群机器庞大的业务场景,如Hadoop集群,HBase集群。
分布式事务算法
有的业务场景需要事务来保证数据一致性,采用集群方案,数据可能分布在不同的集群节点上,节点间只能靠消息进行通信,因此分布式事务实现起来只能依赖消息通知,而消息有可能丢失,这带来了复杂性。
下面分别介绍事务算法中的二阶段提交(two-phase commit protocol, 2PC)和三阶段提交(3PC)。
2PC
2PC由两阶段组成,分别是Commit请求阶段和Commit提交阶段。
该算法有如下假设:
- 分布式系统中,存在一个节点作为协调者(coordinator),其他节点作为参与者(cohorts),且节点之间可以进行网络通信
- 所有节点采用预写式日志,日志被写入后即保持在可靠的存储设备上,即便节点损坏,也不会导致日志数据丢失
- 所有节点不会永久性损坏,即使损坏,仍然可以恢复
算法基本说明:
- 第一阶段-提交请求阶段,或投票阶段,各参与者投票是否要继续接下来的提交操作
- 协调者向所有参与者发送Query to commit消息,询问是否可以执行提交事务,并开始等待各参与者回应
- 参与者执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志,返回yes给消息协调者;如果参与者执行失败,则返回no给协调者
- 第二阶段-提交执行阶段
成功
当协调者从所有参与者获得的相应消息都是yes时
- 协调者向所有参与者翻出Commit的请求
- 参与者完成Commit操作,并释放在整个事务期间占用的资源
- 参与者向协调者发送ACK消息
- 协调者收到所有参与者反馈的ACK消息,完成事务
失败-任一参与者在第一阶段发回No,或协调者第一阶段的询问超时无法获得所有 - 协调者向所有参与者发出Rollback的请求
- 参与者利用之前写入的Undo信息执行回滚,释放事务期间占用的资源
- 参与者向协调者发送ACK消息
- 协调者收到所有参与者反馈的ACK消息,取消事务
2PC的缺点
- 同步阻塞:两方互相等待对方消息,等待过程中节点处于阻塞状态,不能做其他事,若节点响应消息慢,则整个系统全部被拖慢,导致难以支持高并发的场景
- 状态不一致:协调者在发出Commit后,某参与者并未收到这个消息,其他参与者收到,则收到消息的参与者提交事务,未收到者超时后回滚事务,导致事务状态不一致。协调者会发送ROLLBACK给参与者,但该条信息同样存在丢失问题
- 单点故障:如果协调者故障,则参与者一直阻塞
3PC
三阶段提交算法是针对二阶段提交算法在的"单点故障"而提出的解决方案。通过二阶段提交算法中的第一阶段和第二阶段之间插入一个新的阶段"准备阶段",当协调者故障后,参与者可以通过超时提交来避免一直阻塞。
具体算法:
第一阶段(提交判断阶段)
- 协调者向参与者发送canCommit消息,询问参与者是否可以提交事务
- 参与者收到canCommit消息后,判别自己是否可以提交该事务,如果可以执行就返yes,不可以则返回no
- 如果协调者收到人一个no或参与者超时,则事务终止,同时会通知参与者终止事务,如果在超时时间内收到所有yes,则进入第二阶段
第二阶段(准备提交阶段)
- 协调者发送preCommit消息给所有参与者,告诉参与者准备提交
- 参与者收到preCommit消息后,执行事务操作,将undo和redi信息记录到事务日志中,然后返回ACK消息
第三阶段(提交执行阶段)
- 协调者在接收到所有ACK消息后会发送doCommit,告诉参与者正式提交;否则会给参与者终止消息,事务回滚
- 参与者收到doCommit消息后提交事务,然后返回haveCommitted消息
- 如果参与者收到一个preCommit消息并返回ACK,但等待doCommit消息超时(例如协调者崩溃或超时),参与者则会在超时后继续提交事务
3PC避免了单点故障导致系统阻塞问题,也存在数据不一致问题
分布式一致算法
placeholder
Reference
1 从零开始学架构-照着做,你也能成为架构师,李运华著,电子工业出版社