架构实战营 1 期模块 7 作业——业务异地多活架构
业务异地多活架构作业
王者荣耀商城异地多活架构设计
作业要求
分析王者荣耀商城的业务特点,设计其异地多活架构;
按照模块 7 第 5 课的方法来设计异地多活架构。
作业答案
1. 业务分级
此步骤可以忽略,因为已经分析过了。
2. 数据分类
库存
王者荣耀的商城是虚拟物品商城,没有生产、制造的周期,边际成本极低:
普通物品,供应量可以是无限的,相当于没有库存,不存在库存数据的一致性;
限量发行的物品,供应量是有限的,此时对库存数量存在强强一致性要求,无法分区部署,进行单点部署或者应用“库存拆分 + 实时转异步的异地扣库存技巧”;
购买物品
用户点券余额:全局强一致性
用户属性(已经购买的物品情况)
不可重复购买的物品(英雄、皮肤),全局强一致性
可无限购买的物品(鲜花、改名卡),最终一致性
订单:
根据用户已经拥有物品的情况,决定是否可以下单。相当于可以利用算法生成:自变量是用户已经拥有的物品,因变量是订单是否有效。
对于包含限量物品的订单,不可丢失、不可重复(防止重复购买)
3. 数据同步
库存
普通物品
无需同步,相当于是用算法生成的:无限供应
限量发行的物品
全局一致性,不论采用单点部署还是库存拆分的技巧,都不需要(因为没有办法)进行同步。
购买物品
用户点券余额
全局一致性,采用数据库同步余额,充值只能在归属地充值;
用户属性(已经购买的物品情况)
由于某些种类的物品不能重复购买,这种物品的拥有情况决定了是否可以下单,所以需要保持全局一致性,采用数据库同步。
订单
订单一旦生成,不可丢失,采用数据库同步。
4. 异常处理
库存
如果采用单点部署,库存服务挂掉,提示用户稍后再试。
购买物品
用户购买完某些物品,没有同步到异地机房,提示用户稍后再试,或者进行二次读取,去另外一个机房读取。
5. 王者荣耀商城异地多活架构示意图
业务异地多活架构笔记
本模块的讨论的是在保证数据可靠的前提下,提高系统可用性的机制。详见“可用与可靠”,数据可靠的本质就是保存多份,手段是数据复制(复制手段有两种:状态转移和操作转移);状态转移会影响分布式系统的可用性,操作转移可以使用 Quorum 机制提升可用性;在操作转移的过程中,必须使用分布式共识算法(Paxos/Raft)来保证数据的一致性。
注意,本质是数据冗余保存:“鸡蛋篮子法则”——同一种鸡蛋放在多个篮子里(主从或主备),不是不同的鸡蛋放在不同的篮子里(分片),所以 Memcached 这样的分区架构的系统不在此次讨论额范畴内。
可用与可靠
整理自《周志明的软件架构课》。
可靠
通过增加数据副本来保证可靠性,比如用多块磁盘保存同一份数据,强调数据不会丢失。数据丢失的概率等于多块磁盘同时损坏概率的乘积,磁盘越多,数据越可靠。即可靠场景下,系统整体的可靠性与每个节点的可靠性之间是“与”的关系。
可用
解决在分布式系统中,动态数据如何在不可靠的网络通讯条件下,依然能在各个节点之间正确复制的问题。即复制数据,使其可靠的过程要保证系统的可用性。
数据同步——状态转移
系统里的每个节点都反馈成功的完成磁盘写入后,数据的变化才能宣告成功,比如 MySQL 的主从复制。虽然能保证数据是一致的,但任何一个节点(即可用场景下,系统整体的可用性与每个节点可用性之间是“或”的关系)因为任何原因没有响应都会阻塞这个过程,导致系统不可用。相当于节点越多,成本越高,可用性相比单个节点确没有提高。此时可用与可靠出现了矛盾。
以同步为代表的数据复制方法,叫做状态转移(State Transfer),是一种牺牲可用性的可靠性保障手段。
数据同步——操作转移
为了缓解系统高可用和高可靠之间的矛盾,分布式系统里主流的数据复制方法,是以操作转移(Operation Transfer)为基础的。想要改变数据的状态,除了直接将目标状态赋予它以外,还可以通过某种操作,把源状态转移为目标状态。
这里的操作往往就是日志记录操作,比如 Etcd 中就使用了 WAL 日志(就是和 MySQL 上使用的 WAL 是同一种性质的日志,虽然格式不同,但是目的是一样的,比如 crash safe、顺序写等)。
使用确定的操作,促使状态之间产生确定的转移结果的计算模型,就是状态机(State Machine)。要让多台机器的最终状态一致,只要确保它们的初始状态和接收到的操作指令都是完全一致的。还是拿 MySQL 做对比,虽然 MySQL 里没有状态机的概念,但是 MySQL 在提交的时候,也会有 Change Buffer 和 redolog 做配合。
这里操作指令就是一连串的、在系统内传播的消息。“消息”就此登场,它还会在下面的 FLP 不可能原理中扮演重要角色。
消息的传播与处理期间,允许系统内部状态存在不一致的情况,但是此期间的状态不能被外部观察到;但是消息序列执行完成的时候,所有节点的最终状态是一致的。这种模型,就是状态复制机(State Machine Replication)。且最终状态采用“少数服从多数”的原则。这样就可以容忍少数节点失联,使得增加机器数量可以用来提升系统整体的可用性,即 Quorum 机制。
高可用架构三大核心原理
FLP 不可能原理
又称 FLP 定理或者 FLP 结论,指出了什么情况下不存在分布式算法。
It is impossible to have a deterministic protocol that solves consensus in a message-passing asynchronous system in which at most one process may fail by crashing. 在一个“完全异步”的分布式系统里,如果至少有一台机器可能会出问题,那么就“不存在”非随机的共识算法。
1985 年三位科学家 Fisher,Lynch 和 Patterson 发表论文"Impossibility of Distributed Consensus with One Faulty Process"。
1996 年的论文"Unreliable failure detectors for reliable distributed systems"证明了如果分布式系统存在一个能让你最终做出准确判断的不准确时钟,那么系统存在共识算法。
1996 年的论文"The weakest failure detector for solving consensus"证明了这个不准确时钟是共识算法存在的充要条件。
从结论可以看出,想要共识算法不存在,需要同时存在两个现象:
一个是机器出问题;
另一个是完全异步。
也就是说:只要我们能让任何一个条件失效,就存在共识算法。
很显然我们无法保证机器不出问题,所以重点要放在怎么让系统不是完全异步。幸运的是完全异步这个条件非常容易去除,只需要给每台机器增加一个时钟来判断发送的消息是否丢失。
所以常见的共识算法,比如 Paxos 和 Raft,里面一定会判断消息是否超时。所以虽然理论上分布式系统不存在共识算法,但是现实中很容易绕开这些理论约束,实现正确的共识算法。
FLP 的不可能三角
Liveness: 又称 Termination(终止性),指系统中非故障节点(即正常运行/活着的节点)最终(即有限的时间内)都要确定或者是输出唯一一个结果,对于不能正常运转的机器没有任何要求;且结论只能生成一次,一旦做出就无法更改。这一条的重点是描述单个节点,每个节点最终能产生一个确定的结果。
Safety:又称 Agreement(一致性)。如果说上一条是描述单个几点,那么这一条就是描述多个节点输出的结果是完全相同的。Termination 和 Agreement 合起来就是说:所有(注意:不是大多数!!)活着的节点不仅每个节点能输出一个确定的结果,而且这些结果还是一样的。
上面要求所有节点而不是大多数节点给出一样的结果,并不是要求数据在复制的时候,所有节点都要响应成功,而是只要大多数节点响应成功就可以了。剩余没有响应的节点,在后续的日志复制 RPC 的一致性检查中,会被强制被领导者的数据更新覆盖。
Fault Tolerance:协议必须在节点故障的时候同样有效。
每条边的含义如下:
SL 系统: 指所有活着的节点在算法结束的时候,输出一样的结果,但是不允许节点失败;
SF 系统:指为了在有节点故障的时候能达成一致,系统得出确定结论需要无限等待。
LF 系统:指在有节点故障的时候在有限的时间内无法达成一致。
共识算法
在《分布式金融架构课》专栏中,使用 Termination(终止性)、Agreement(一致性)和 validity(有效性)定义了共识,即存在共识必须具备这三个特性(充分必要条件?),其中有效性的定义如下:
validity(有效性),制定了共识算法必须有意义。比如不论机器初始状态时什么,最后都无脑的输出 0,这个共识算法是没有意义的。
有效性可以理解为一个数学上的约束,类似于屏蔽“平凡解”的意思。
更实际、更通俗、更简单的结论是:共识表示在多台机器之间,能同步一个内容不断增加的文件。
一个例子如下:
系统由 A、B、C 三台机器组成,初始值分别为 1、0、0。
系统运行一段时间后,A 给 B 和 C 发了两个不同的消息,A 输出结果 0.
2.1、 B 经过一定的时间,收到了消息,也输出结果 0;
2.2、C 收到消息后,没有向外面发送任何消息,也没有输出任何结果
2.2.1、如果 C 在正常运行,则 C 不满足终止性。那么 ABC 组成的分布式系统没有达成共识;
2.2.2、如果 C 死机了,那它就处于非运行状态。由于共识的定义对于非运行的机器没有任何要求,所以说 ABC 组成的分布式系统达成了共识;
FLP 不可能三角和共识的比较
华仔和任老师分别给出了 FLP 不可能三角和共识两个知识点,看起来应该是有某种联系。这里只记录自己的思考。
FLP 不可能三角中有一个 Fault Tolerance;而分布式共识算法对于非运行的的机器没有任何要求,所以分布式共识算法本身就是 Fault Tolerance 的;
分布式共识算法要求 Termination/Liveness 和 Agreement/Safety 同时具备;
综上,分布式共识算法同时具备了 FLP 不可能三角的三个角;
所以,如果“FLP 不可能三角”是说 Fault Tolerance、Termination/Liveness、Agreement/Safety 不可能同时具备,就等同于在说分布式共识算法不可能存在。
回过头再看分布式共识算法不存在的两个前提:
一个是机器出问题;
另一个是完全异步。
这里的异步是在描述消息的发送方式,有如下三个特点:
指给其它机器发送一条消息后,不一定可以收到反馈:有可能是消息直接丢失了;或者是没有丢失,但是反馈前死机了。
收到消息的时间间隔也没有任何假设:可能几分钟就收到、或者很久才收到,或者永远收不到;
消息的接收也是异步的:接收顺序是完全随机的,并不是先到先得。
此外对消息系统还有几个假设,但不是关键:
不会丢失任何消息;
消息只会被处理一次。
消息系统里没有相关消息时,会返回空消息。分布式系统中的节点也可以根据空消息的情况来改变自己的状态。
机器肯定会出问题,所以取消异步这个前提,分布式共识算法就存在了。如何取消异步这个前提?改变上述异步三个特点的任何一个就可以了,比如设置超时时间,即对收到消息的时间做出假设,比如 Paxos 和 Raft 里面一定会判断消息是否超时。
共识算法之 Raft
算法过程描述
Raft 复制的是日志项,日志项里包含了指令,所以日志项的复制就是一种操作转移(Operation Transfer)型的数据复制操作。
领导者收到客户的请求后,进入第一个阶段,通过日志复制 RPC 消息,将日志项复制到集群其它节点上;
其它节点响应领导者:
从下面可以看出,是在一定的时间内响应领导者,这就打破了完全异步这个前提条件,使得分布性共识的存在称为可能。
2.1、如果领导者收到大多数的“复制成功”的响应后,它将日志项应用到它的状态机(相当于提交事务),并返回成功给客户端。
注意,这里正是前面多次提到的“大多数”而不是“所有的”节点的响应,这样提高了分布式系统的可用性。但这并不意味着违背了共识定义中的 Safety/ Agreement(一致性),因为数据在后续还要被修复。
此外,此时只有领导者提交了日志内容,其它节点何时提交呢?还是通过消息——领导者的日志复制 RPC 消息或心跳消息,包含了当前最大的,将会被提交的日志项索引值,此时跟随者就可以知道哪些日志可以提交了。
上面日志提交的过程,可以理解为一个优化后的二阶段提交,将两阶段优化成了一阶段,即只有领导者自己提交就可以了。
此外,日志提交前,相当于状态是不可见的。符合“操作转移”小节的描述。那么,跟随者没有提交的时候,它收到查询请求不就不一致了么?答案是只有领导者才可以接收请求并做出应答。所以跟随者相当没有 MySQL 主备架构中的“备”而不是主从架构中的“从”。
2.2、如果领导者没有接收到大多数的“复制成功”响应,那么就返回错误给客户端。
Raft 具备 Termination/Liveness(终止性)
从上面的描述可以看出,Raft 具备了 Termination/Liveness(终止性),那么 Safety/ Agreement(一致性)如何保证?参见下面的说明。
Raft 具备 Safety/ Agreement(一致性)
在通过操作转移(Operation Transfer)进行数据复制的时候,为了提高系统的可用性,只要大多数跟随者节点响应“复制成功”后,就认为成功了,还存在状态不一致的跟随者,所以必须在有限的时间内修复不一致。
这里涉及到日志的格式,如下(引自《分布式协议与算法实战》)。
所谓日志就是 Raft 集群中的数据,或者说副本数据是以日志的形式存在的,日志有日志项组成。
日志项是一种数据格式,它主要包含用户指定的数据,也就是指令(Command),还包含一些附加信息,比如索引值、任期编号。
Raft 中一切以领导者为准,跟随者恢复后,也必须以领导者为准,更新自己的日志:
领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的日志是不一致的。
领导者强制跟随者更新覆盖不一致的日志项,实现日志的一致。
领导者从来不会覆盖或者删除自己的日志。
Raft 实现了线性一致性也即是线性化存储
参见模块六作业,线性一致性有如下的两个重要特点:
线性一致性是多个程序的环境下,调整不同程序操作的开始和结束时间,使得这些操作之间没有任何时间上的重叠;
如果两个操作之间没有时间上的重叠,那么两个操作之间的先后顺序不能发生改变。
Raft 多个事务并行操作的情况如下(想不起来来自于哪里了……):
上图中,这个 Raft 组由 5 个节点组成,T1 到 T5 是先后发生的 5 个事务操作。
T1 将 X 置为 1,5 个节点都 Append 成功,Leader 也应用到本地,且返回客户端提交成功。
T2 执行时,大多数跟随者都成功,所以 T2 也成功;
T3 执行时,没有得到超半数的响应,这时 Leader 必须等待一个明确的失败信号(比如通讯超时),才能结束这次操作(符合 Termination/Liveness)。T3 会阻塞后续事务(比如 T4)的进行,这样就符合了线性一致性的特点,使得操作没有任何时间上的重叠,同时 T3 和 T4 之间的顺序不可以发生改变,即 Raft 的投票是顺序的。
T5 也会被阻塞,虽然 T5 和 T3 之间没有冲突(它们操作的数据项不是同一个)。这是设计上的一种权衡,虽然性能有影响,但是节点机比对日志会非常简单,只要找到一条日志是一致的,那么这条日志之前的所有日志就都是一致的。
另外,跟随者上事务的提交也必须是顺序的,比如先收到 T3 的日志,后收到 T2 的日志,也不会 Append,因为这样会使日志出现空洞。
由上可见,日志项中的索引值,就相当于一个全局时钟。
且写日志是一个只能追加的过程,这呼应了共识的定义:共识表示在多台机器之间,能同步一个内容不断增加的文件。
Raft 实现了全序广播
全序广播的学名叫 Total Order Broadcast。在分布式环境下要满足如下两个条件:
所有机器的消息都不能丢失。如果一台机器上出现了某个消息,那么这个消息一定能在所有其它机器上找到。
所有机器记录的消息顺序完全一致。
参见上一小节,由于 Raft 实现了线性一致性,日志项的提交是按照其索引值顺序进行的,所以 Raft 实现了全序广播。
共识算法之 Paxos
图片来自《周志明的软件架构课》,按照本文的叙事逻辑,做了重新理解与调整。
Paxos 算法包括“准备(Prepare)”和“批准(Accept)”两个阶段。
第一阶段“准备”(Prepare)就相当于抢占锁的过程。如果某个提案节点准备发起提案,必须先向所有的决策节点广播一个许可申请(称为 Prepare 请求)。提案节点的 Prepare 请求中会附带一个全局唯一的数字 n 作为提案 ID,决策节点收到后,会给提案节点两个承诺和一个应答。
其中,两个承诺是指:承诺不会再接受提案 ID 小于或等于 n 的 Prepare 请求;承诺不会再接受提案 ID 小于 n 的 Accept 请求。
一个应答是指:在不违背以前作出的承诺的前提下,回复已经批准过的提案中 ID 最大的那个提案所设定的值和提案 ID,如果该值从来没有被任何提案设定过,则返回空值。如果违反此前做出的承诺,也就是说收到的提案 ID 并不是决策节点收到过的最大的,那就可以直接不理会这个 Prepare 请求。
当提案节点收到了多数派决策节点的应答(称为 Promise 应答)后,可以开始第二阶段“批准”(Accept)过程。这时有两种可能的结果:
如果提案节点发现所有响应的决策节点此前都没有批准过这个值(即为空),就说明它是第一个设置值的节点,可以随意地决定要设定的值;并将自己选定的值与提案 ID,构成一个二元组 (id, value),再次广播给全部的决策节点(称为 Accept 请求)。
如果提案节点发现响应的决策节点中,已经有至少一个节点的应答中包含有值了,那它就不能够随意取值了,必须无条件地从应答中找出提案 ID 最大的那个值并接受,构成一个二元组 (id, maxAcceptValue),然后再次广播给全部的决策节点(称为 Accept 请求)。
有了较为简单的 Raft 算法,下面来看比较复杂的 Paxos 算法。这样好像更好理解。下图中的数字对应图下描述的序号。
首先,Paxos 是典型的基于操作转移模型而非状态转移模型来设计的算法,所以这里的“设置值”不要类比成程序中变量的赋值操作,而应该类比成日志记录操作。因此,把 Paxos 算法中的“提案”等同于 Raft 中的“日志项”了;
Raft 中每个“日志项”都有一个索引值,相当于全局时钟,这个索引值直接来自主节点;相应地,Paxos 中 Prepare 阶段中构造的“提案”也有一个 ID,<u>这个 ID 就相当于 Raft“日志项”中的索引值</u>,只是这个 ID 最后的值是多少,是由决策者共同决定的;
Raft“日志项”中有一条指令,来对状态就行修改;相应地,在 Paxos 中 Accept 阶段,提案节点会根据 Prepare 阶段大多数决策者的返回值,来决定“提案”中某个状态的值;
Raft“日志项索引值”是单调递增的,这一点由当前主节点来保证;Paxos 中“提案 ID”也是单调递增的,因为决策者如果收到一个提案 ID 小于自己已经 Accept 过的所有 ID 的提案,会将其忽略;
Raft 是一阶段提交,主节点收到大多数跟随者的应答后,就直接提交了;Paxos 是两阶段提交,形成共识决议后,将形成的决议发送给所有记录节点进行学习。
综上,Raft 确实是 Paxos 的简化版,主要体现在:
使用主节点简化提案 ID(就是日志项索引值)的形成;
一阶段提交,跟随者提交依赖于日志复制 RPC 消息或心跳消息。
按照 FLP 的不可能三角,Paxos 算法由于活锁的存在,属于 SF 系统,在异常情况下,表决过程会无限循环下去。
共识算法之 Redis 哨兵集群
判断主库客观下线
过程描述
汇总自《Redis 核心技术与实战》
任何一个实例只要自身判断主库“主观下线(即主库对哨兵的 PING 命令响应超时)”后,就会给其它实例发送
is-master-down-by-addr
命令。其它哨兵实例会根据自己和主库的连接情况,做出 Y 说 N 的响应。
一个哨兵获得了仲裁所需的赞成票数后,就可以标记主库为“客观下线”。这个所需的赞成票数是通过哨兵配置文件中的 quorum 配置项设定的,例如超过哨兵集群一半的数量。
过程分析
这时哨兵集群中并没有 leader 和 follower 之分,所以是一个类似 Paxos 的过程。
提案内容是关于主库是否下线,各个哨兵要就这个提案达成共识。
提案似乎并没有 ID(没有看过 Redis 的源码)。
超过 quorum 个哨兵同意后,则达成共识。
哨兵选主
也是一个类似于 Paxos 的过程;
不过相比判断主节点客观下线,提案有了类似 ID 的概念——选举是一轮一轮的,每一轮就相当于有一个 ID;
每个哨兵每一轮只有一张选票,给某个哨兵投一票以后,不会在给其它节点投票;相当于 Prepare 阶段决策者对提案者的两个承诺之一:不会再接受提案 ID 小于 n 的 Accept 请求。
任何一个想成为 Leader 的哨兵,要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值
共识算法之 Redis 主从同步
Redis 的主从同步并不是一个分布式共识的过程,因为 Redis 的主从同步采用了异步复制的技术:主库先执行命令,在把这些命令丢到 replication buffer 和 repl_backlog_buffer 中去异步发给从库去执行(这两个缓冲区就像两个队列一样)。所以在生换主时,即使客户端订阅了哨兵的新主库切换事件,在切换完成之前,如果原主库是假死,就会发生数据丢失(因为原主库变为从库时会被新主库的数据覆盖)。
共识算法之 MySQL 主备/主从复制
从数据同步的角度来说,从库和备库在概念上其实差不多。下面会混用两个概念。
不考虑 InnoDB Cluster 的情况
MySQL 主备/主从复制可以把主节点上执行的事务看作一个提案,然后发送到从机进行执行的过程。不同的是:
Raft 算法的数据同步使用了操作转移的方式,每个日志项都有一个索引值;
MySQL 主备/主从复制使用的是状态转移的方式,在 MySQL 5.6 版本引入了 GTID(Global Transaction Identifier,也就是全局事务 ID),我觉得可以理解为 Paxos 算法中的提案 ID 或者 Raft 算法中的日志项索引。
结论:mysql 主备/主从复制并没有达到分布式环境下的线性一致性,因为主备间事务的顺序不会得到保证;虽然半同步复制也有类似于 quorum 的机制,要求一定数量的从机回复复制成功。
为了加快主备复制的进度,减少延迟,需要提高备库的并行复制能力,MySQL 5.7.22 的并行复制策略提供了如下几种策略,由参数 binlog-transaction-dependency-tracking 来控制:
COMMIT_ORDER,表示的就是前面介绍的,根据同时进入 prepare 和 commit 来判断是否可以并行的策略。
这个参数对应没有锁冲突的事务:可能没有读写冲突、写写冲突,或者根本操作的是不同的数据。侧重点在于同一时间进入 prepare 和 commit 状态,有可能对应了同一批数据,但是由于隔离级别,比如可重复读时,只读事务不会受到写事务的影响,相当于<u>时间上是并行</u>的。
WRITESET,表示的是对于事务涉及更新的每一行,计算出这一行的 hash 值,组成集合 writeset。如果两个事务没有操作相同的行,也就是说它们的 writeset 没有交集,就可以并行。
这个参数对应操作不同数据的并行事务,这些事务可以是同一时间进行的,也可以是有先后次序的。侧重点在于没有操作同一批数据,相当于<u>空间上是并行</u>的。
WRITESET_SESSION,是在 WRITESET 的基础上多了一个约束,即在主库上同一个线程先后执行的两个事务,在备库执行的时候,要保证相同的先后顺序。
这个参数不仅强调了空间上是并行的,还进一步要求保证时间上的执行顺序。但是仍然没有在主备主从间做到了前缀一致性,此时只有在同一个线程执行的事务才保证了顺序性,不同线程执行的事务的顺序仍然没有得到保证。
虽然没有达到分布式共识,但是引入了 GTID,使得 MySQL 的复制里 Paxos 近了一步:参见上面对 Paxos 的描述,在第一个阶段“准备阶段”,如果决策者收到的提案 ID 并不是决策节点收到过的最大的,那就可以直接不理会这个 Prepare 请求
;MySQL 对 GTID 的处理也非常类似,如下面的 2.a 所描述的
在 GTID 模式下,每个事务都会跟一个 GTID 一一对应。这个 GTID 有两种生成方式,而使用哪种方式取决于 session 变量 gtid_next 的值。
如果 gtid_next=automatic,代表使用默认值。这时,MySQL 就会把 server_uuid:gno 分配给这个事务。
a. 记录 binlog 的时候,先记录一行 SET
b. 把这个 GTID 加入本实例的 GTID 集合。
如果 gtid_next 是一个指定的 GTID 的值,比如通过 set gtid_next='current_gtid’指定为 current_gtid(这个语句是在 MySQL 客户端中执行的语句),那么就有两种可能:
a. 如果 current_gtid 已经存在于实例的 GTID 集合中,接下来执行的这个事务会直接被系统忽略;
b. 如果 current_gtid 没有存在于实例的 GTID 集合中,就将这个 current_gtid 分配给接下来要执行的事务,也就是说系统不需要给这个事务生成新的 GTID,因此 gno 也不用加 1。
InnoDB Cluster 的情况
MGR 技术
此项技术是 MySQL 在 5.7 版本推出的一种基于状态机的数据同步机制,且没有采用复制技术(状态转移),而是采用 GCS(Group Communication System)协议的日志同步技术。
MGR 本身就是一种类似 Paxos 算法的协议。
MGR 有两种模式:
单主(Single Primary)模式,可以自动进行 Failover 切换,比如最复杂的选主操作。只有一个节点可以写入。
多主(Multi Primary)模式,每个节点都可以写入。如果多个节点存在变更同一行的冲突,MySQL 会自动回滚其中的一个事务,自动保证数据在多个节点之间的完整性和一致性。
MGR 的限制
仅支持 InnoDB 表,并且每张表一定要有一个主键;
目前 MGR 集群最多只支持 9 个节点;
网络不稳定时会影响集群的性能。
多主模式下的冲突检测
多主模式要求每个事务在本节点提交时,还要去验证其他节点是否有同样的记录也正在被修改,如果有的话,其中一个事务要被回滚。
为了避免冲突,做好的做法时每个节点写各自的数据库,这样集群的写入性能就能线性提升了。(有点像分库)
如果要实现一个完整的数据库高可用解决方案,就需要更高一层级的 InnoDB Cluster 完成。
CAP 定理
CAP 由三个性质组成:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。其中,分区容错性指的是网络出现了问题,把原本通过网络连接在一起的机器分成了几个独立的部分,也叫作脑裂。
如果放弃分区容错性,那么就是单机数据库,分布式系统不可能放弃 P,所有在会发生分区的前提下,需要在一致性和可用性中二选一,而不是三个性质排列组合在 AP、CP 和 CA 中三选一(CA 就相当于单机数据库)。
此外,CAP 中的 C 即一致性指的是可线性化(Linearizability)
CAP 不可能三角:
一致性:每次读取操作都会读取到最新写入的数据,或者返回错误。
可用性:每次请求都会得到非错请求,但不保证返回最新的数据。
这里的可用性,就是“可用与可靠”小节说的可用。
分区容忍性:系统在发生分区的时候继续提供服务。
Paxos 在部分节点故障的时候可以继续工作,那是符合 CP 还是 AP 约束?
它仍然是 CP 系统。因为:
1、Paxos 使用了 Quorum 机制来提高系统的可用性,在少数节点故障的情况下,仍然可以达成共识;
2、当故障节点占多数时,系统就会停止工作。
复制延迟与 PACELC 猜想
(理论上)CAP 是忽略网络延迟的,但实际工程中,一定是存在延迟的。所以在工程落地时,有一个 PACELC 猜想:Partition -> Availability, Consistency, Else -> Latency, Consistency,即
描述粒度
在业务系统中,CAP 关注的是单个数据,不是整个系统,系统数据可以有的符合 CP,有的符合 AP。
BASE 理论
BASE 是 CAP 中 AP 方案的延伸,考虑了网络时延和系统恢复正常后的状态。
BASE/CAP/FLP 落地
思考题
互联网业务基本都不允许停机,而银行却允许停机暂停服务,为什么会出现这种差异?
因为银行业务对一致性的要求很高,是一个 CP 的系统,所以为了保证正确性,允许暂停服务。
如何用 FMEA 方法排除架构隐患
FEMA 是一个适用面很广、分析和思考架构可用性的方法。
FMEA(Failure mode and effects analysis,故障模式与影响分析)又 称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等, 专栏采用“故障模式与影响分析”。
Failure:假设系统某些组件或者模块出现故障。
Mode: 故障发生的方式、可能性。
Effect :故障的影响。
Analysis:分析系统的可能反应,以及如何改进。
比如
FMEA 什么阶段应用
在架构设计做完,对方案进行检查的时候用。
应用步骤
给出初始的架构设计图,一般是系统架构图和部署架构图;
假设架构中某个 Role 发生故障,然后分析此故障对系统功能造成的影响;
根据分析结果,判断架构是否需要进行优化。并不是所有问题都一定要修改,没有完美的架构。
客户端和前端需要用应用 FMEA 来优化架构么?
不需要。因为客户端和前端整体上是一个程序,某个模块失效的时候,整体就退出了。
FEMA 分析维度或模板
FMEA 落地技巧
抓住核心:优先针对核心场景进行分析。
分工合作:可以安排给初级架构师或者高级开发人员,锻炼架构思维。
适可而止:严重程度为高的必须解决,严重称为为中的做好检测和告警。
思考题
FMEA 评估会带来一定的工作量,那么是否会降低架构设计的效率?
肯定会增加架构设计的工作量,但是整体来看,收益成本比足够高就可以。
业务级灾备架构设计
虽然是讲灾备架构,但是已经投入了这么高的成本,所以,可以顺带手的做到多活。
灾备和多活的一个区别是,灾备架构里一般只有一个主节点或者主机房,可以执行完整的业务(读/写),其余节点不行;而多活架构,每个机房可以执行的业务是完整的,仅就数据库说,一定是都存在主节点的。
所谓业务级就是不仅包括存储、中间件,还包括业务层。比如同城双中心架构:
全链路条带华设计
这里的业务级灾备设计,在机房之间的延时较小时(比如小于 10ms)可以做多活。还有一个类似的概念,叫全链路条带化设计。
条带化是存储的一项技术,将磁盘条带化后,可以把连续的数据分割成相同大小的数据块,即把每段数据分别写入不同磁盘上的方法。
把条带化思想应用到数据库设计上,就是分布式数据库。分布式数据库为了减少跨分片的多表查询,会做单元化的部署。要求库表可以挑选出一个统一的分片键,即大部分表都能根据这个分片键打散数据,这样当后续业务进行数据访问时,可以在一个分片中完成单元化的闭环操作,不用涉及跨分片的访问。
而全链路条带化设计在数据库的基础上,还需要将上层的服务也作为条带的一部分
那么,当系统中存在不可以条带化改造的系统或服务时,这个系统或服务只能单点部署,例子参见"业务通用型异地多活".
同城多中心
同城双中心
机房间网络延时 < 2ms;
相距 50 公里以上;
光纤互联;
因为延时低,双中心可以当做一个逻辑机房;可以应对机房级别的灾难。
因为可以当做一个逻辑机房,所以可以同城双中心部署一个集群,也就相当于是多个 IDC 都可以“接客”,这才叫“单活”:
但是为了预防脑裂,一般用多条光纤通路。不过上图还是无法应对机房级别灾难,因为当 IDC-1 故障时,IDC-2 仍然无法工作(这是是由一个哨兵,无法选举主节点),解决办法是在第三个地方部署几台哨兵节点。
同城三中心
很少使用,以为想不同城双中心,可靠性没有增加多少,成本却增加很多。
跨城多中心
架构图和同城双中心一样,只是两个机房现在位于不同的城市。由于距离远了,所以可以额外做
用户分区(两个城市距离很远时才做用户分区:因为距离远了,用户就近访问才有意义)
异地多活
根据城市之间的距离,可以细分为两种:
近邻城市,机房延时 < 10ms,可以当做同一逻辑机房;可以做异地多活,但不做用户分区。
远端城市,机房延时 > 30ms,不能当做同一逻辑机房;可以做异地多活架构、分区架构。
当存在分片架构的集群时
此时即对应“全链路条带化设计”。华仔给出了一个 OceanBase 官方推荐架构,非常符合条带化的思想:
2 近(10ms)1 远(30ms)
可以应对城市级别故障,最高可靠性
成本最高
思考题
OceanBase 官方推荐 2 近 1 远的高可用架构,那么如果你的业务不用 OceanBase,是否也可以优先用这个架构方案来实现异地多活?
这个方案的成本较高,首先看业务的量级,如果量级达到了,是可以用这个思想的,比如使用全链路条带化设计,自行对数据库做分库分表。
异地多活架构的三种模式
业务定制型异地多活
按照业务的优先级,优先保证核心业务的异地多活;
基于核心业务的流程和数据特点,做定制化的设计;
对基础设施无要求。异地多活,所以需要部署在远距离的两个城市;
不通用;难扩展,随着业务的变化也需要变化。
重点是要分析数据的特点,采用合适的数据同步策略。其中,数据是否可以重复,可以影响到架构的设计。如果可以重复,那么可以在业务层实现数据同步,否则还是用数据库的同步机制进行同步。比如阿里游戏异地多活架构:
上图左侧其实没有做到真正的多活,因为主库只有一个,当其失败时,就无法执行写操作了,而"活"指实时提供读/写服务的意思。
上图左侧当机房距离较远,时延较大时,会影响系统性能。
上图右侧,因为可以重复生成全局唯一数据,所以可以在业务层进行同步,但是应该在业务层进行数据对账。
业务通用型异地多活
通过配套服务来支持异地多活,相当于把业务定制型异地多活中比较稳定的组件固话、沉淀下来,形成了中间件。
对硬件基础设施无要求;
业务基本无需改造,只需判断业务是否支持 BASE 就可以了
支持就可以异地多活
不支持就单点部署。所以这个架构存在全局单点业务(需要强一致性的业务),例如库存等
配套服务复杂
当机房举例较远时,RTO 会比较大。
业务通用型异地多活架构相当于“条带化设计 + 全局单点业务”,比如淘宝的单元化架构
上图中,全局单点业务只有一份,如果将全局单点(即不可拆分的业务)做冗余,每个单元复制一份,减少异地延迟问题,就得到了蚂蚁的 LDC 架构
上面单点业务做复制时,类似于数据库的主从结构,比如 CZone 就是读多写少的不可拆分业务,是从;GZone 是主节点。
异地多活架构中的容灾设计
异地多活中的“活”是指每个机房都可以提供全量服务,有可能用户做了分区,对这些用户在对应的 set 中是可以使用全部服务的。
如果做了用户分区,就需要对这个分区的数据和服务做灾备,比如蚂蚁 LDC 架构中,
每个 IDC 内每个 RZone 都有备份节点,比如 RZ01A 和 RZ01B 互为备份;
当它们都挂掉,数据库没有挂掉,可以快速重建(使用建站平台);
此外,如果 IDC1 挂掉,则在 IDC2 新建 RZone,使用 IDC2 的数据库。此时数据会有丢失,所以业务要衡量是否可以接受。数据有丢失,说明底层数据库采用的还是 MySQL 某种形式的主备复制,可能是异步复制(所以才会丢失数据),类似于下面架构中的异步复制部分。
单元化架构的核心配套服务
流量调度:负责将用户请求流量分配到对应的 RZone,例如蚂蚁的 Spanner。
配置中心:负责 Zone 的配置和容灾切换,例如 RZone 的业务范围,Zone 访问哪些数据库等。
建站平台:快速新建一个完整的 Zone,目前基本都是基于容器技术来实现。
发布平台:基于流量调度,完成 Zone 的蓝绿发布、灰度发布等。
存储通用型异地多活
即采用本身已经支持分布式一致性的存储系统,架构天然支持异地多活。
需要分布式一致性的存储系统支持,可选的不多,比如 ZooKeeper、etcd、OceanBase;
对机房部署有强要求,如果要实现异地多活,只能采用近邻部署(这样才不会因为延时过大而影响可用性和性能)
比如 OceanBase,就是一个分布式数据库
分布式数据库架构如下:
OceanBase 进行“三地五中心”部署
标准的三地五中心部署如下:
思考题
三种异地多活架构模式可以排列组合么?
不可以,三种异地多活架构是自下而上包含的,即存储通用性异地多活支持业务通用性异地多活,业务通用性异地多活支持业务定制型异地多活。
但是这几种方案成本又有很大差异,不可以不顾业务情况满目使用。
业务定制型异地多活架构设计
1 个原理——CAP
异地多活本质是 CAP 中的 AP 方案,落地时使用 BASE,BASE 是 AP 方案的延伸。
虽然整体上是 AP 方案,但 CAP 关注的粒度是数据而不是系统,需要根据不同业务的数据特点来设计异地多活。有时在某个局部(比如同城双中心这样可以当做一个逻辑机房的情况)可以做到 CP,同时使用 quorum 提高可用性。即在不同的层级上可以做到侧重有所不同。
在没有分区的时候,可以同时做到 AP..
做好补救工作,需要为分区恢复后做准备,包括人工修复数据。
3 大原则
只保证核心业务。不同业务数据的特性不同,无法全部做到异地多活。
只能做到最终一致性。高可用的最底层是基于数据同步的,在同步完成之前,数据是不一致的,需要业务可以容忍。如果无法容忍则无法做到异地多活。
只能保证绝大部分用户。不要为了 0.01%的用户,而影响了 99.9%的用户!
保证 95%用户业务不受影响的异地多活方案可行么?如果这个确实是最优的,那也是可行的!
4 个步骤
业务分级,按某个维度进行优先级排序,优先保证 TO3 的业务异地多活。
根据如下三个维度进行分级:
访问量
核心场景
对收入的影响
此外,还要看实现异地多活的成本,如果投入产出比太低,还是要遵循合适原则。
数据分类,分析 TOP3 中每个业务的关键数据特点,将数据分类。
可以考察数据的如下特点:
数据的修改量:即数据被修改的数量和频率
一致性:对一致性的要求是强一致性(比如余额、库存)还是最终一致性(比如动态、兴趣)
唯一性:数据对唯一性的要求,是全局唯一的(比如用户 ID),还是可以重复的(昵称)。如果全局唯一且不可重复,每次生成即可,都不需要同步。
可丢实性:数据是否可丢失,比如余额是不可丢失的,而密码是可以丢失的
可恢复性:数据是否可以恢复,比如:用户恢复(发微博)、系统提供恢复(密码找回)、内部恢复(编辑和运行重发)
数据同步
多管齐下,“不择手段”,不要局限于存储系统同步,还可以有:
使用消息队列:当数据具有全局唯一性的时候,而且数据能够实现幂等的,可以使用这个方法,就是 A 机房的数据覆盖了 B 机房的数据也没有关系。所以新建账号可以用消息队列进行同步,而余额不可以,因为账号是唯一且可以做到幂等,余额数据不能实现幂等操作。
二次读取:如果在某个机房读取不到,去另外一个机房再去读取一次。
回源读取:如果数据上有源头的标识,那么就可以去源头读取一次而不是先读取本地的。
还可以重新生成,比如 session 数据,重新登录一次就可以了。
华仔给了几个例子,以加深理解
异常处理,对异常情况,落地手工处理,可以是技术手段,也可以是非技术手段。
业务上做到兼容,比如可以接受有损状况;或者数据无法获取时,提示用户稍后再试
事后补偿:经济补偿
人工修正:人工订正数据,手工最终一致性;此时日志是重要的依靠手段。
TOP3 的数据如果互相冲突,那就减少业务的范围,比如只做 TOP2.
5 大技巧
消息队列同步:作为数据库复制主通道之外的辅助通道,适合全局唯一且幂等的数据,因为可以覆盖;不适合余额之类的数据,因为数据修改无法做到幂等性。
如果消息队列复制的数据先写入数据库,数据库复制的时候会出错,这时需要设置从库为跳过这些错误,比如设置 skip-slave-errors.
库存拆分:把库存拆分到不同的机房,当本地库存为 0 时,异地扣库存。能这么做的前提是因为企业可以知道并容忍库存的不一致,用户的余额是不可以的,因为这有为用户的认知。
事务合并,如下:
实时改异步
业务上适当容忍,比如可以透支
思考题
异地多活这么强大,是否可以强制要求所有业务都异地多活?
不可以,还是要遵循架构三原则。而且有的业务无法适用 AP,也不能做异地多活。
评论