分布式系统是指一组独立的计算机,通过网络协同工作的系统,客户端看来就如同单台机器在工作。随着互联网时代数据规模的爆发式增长,传统的单机系统在性能和可用性上已经无法胜任,分布式系统具有扩展性强、可用性高、廉价高效等优点得以广泛应用。
但与单机系统相比,分布式系统在实现上要复杂很多。CAP理论是分布式系统的理论基石,它提出以下3个要素:
Consistency(强一致性):任何客户端都可以访问到同一份最新的数据副本。
Availability(可用性): 系统一直处于可服务状态,每次请求都能获得非错的响应。
Partition-tolenrance(分区可容忍性):单机故障或网络分区,系统仍然可以保证强一致性和可用性。
一个分布式系统最多只能满足其中2个要素。对于分布式系统而言,P显然是必不可少的,那么只能在AP和CP之间权衡。AP系统牺牲强一致性,这在某些业务场景下(如金融类)是不可接受的,CP系统可以满足这类需求,问题的关键在于会牺牲多少可用性。传统的主备强同步模式虽然可以保证一致性,但一旦机器故障或网络分区系统将变得不可用。paxos和raft等一致性算法的提出,弥补了这一缺陷。它们在保证CP的前提下,只要求大多数节点可以正常互联,系统便可以一直处于可用状态,可用性上显著提高。paxos的理论性偏强,开发者需要自己处理很多细节,这也是它有很多变种的原因,相对而言raft更易理解和工程化,一经提出便广受欢迎。
在我们关注的消息中间件领域,金融支付类业务往往对数据的强一致性和高可靠性有严格要求。
在对主流的消息中间件进行调研后,发现它们在应对这种场景时都存在一定的不足:
RabbitMQ:一个请求需要在所有节点上处理2次才能保证一致性,性能不高。
Kafka:主要应用在日志、大数据等方向,少量丢失数据业务可以忍受,但不适合要求数据高可靠性的系统。
RocketMQ:未采用一致性算法,如果配置成异步模式可能丢失数据,同步模式下节点故障或网络分区都会影响可用性。
鉴于以上分析,我们设计开发了基于Raft的强一致高可靠消息中间件CMQ。接下来会介绍raft算法原理细节、如何应用在CMQ中在保证消息可靠不丢失,以及实现过程中在性能方面所作的优化。
节点之间通过RPC通信来完成选举和日志同步,发送方在发送RPC时会携带自身的Term,接收方在处理RPC时有以下两条通用规则:
Raft算法属于强Leader模式,只有Leader可以处理客户端的请求,Leader通过心跳维持自身地位,除非Leader故障或网络异常,否则Leader保持不变。选举阶段的目的就是为了从集群中选出合适的Leader节点。
在选举时可能会出现两个节点的选举定时器同时到期并发起选举,各自得到一半选票导致选举失败,选举失败意味着系统没有Leader,不可服务。如果选举定时器是定值,很可能两者再次同时到期。为了降低冲突的概率,选举超时值采用随机值的方式。此外,选举超时值如果过大会导致Leader故障会很久才会再次选举。选举超时值通常取300ms~600ms之间的随机值。
选举阶段完成后,Leader节点开始接收客户端请求,将请求封装成Entry追加到raft日志文件末尾,之后同步Entry到其他Follower节点。当大多数节点写入成功后,该Entry被标记为committed,raft算法保证了committed的Entry一定不会再被修改。
在日志同步的过程中,可能会出现节点之间日志不一致的问题。例如Follower写日志过慢、Leader切换导致旧Leader上未提交的脏数据等场景下都会发生。在Raft算法中,日志冲突时以Leader的日志为准,Follower删除不匹配部分。
如下图所示,Follower节点与Leader节点的日志都存在不一致问题,其中(a)、(b)节点日志不全,(c)、(d)、(e)、(f)有冲突日志。Leader首先从index=11(最后一条Entry index +1)开始发送AppendEntry RPC,Follower均返回不匹配,Leader收到后不断回退。(a)、(b)在找到第一条匹配的日志后正常同步,(c)、(d)、(e)、(f)在这个过程中会逐步删除不一致的日志,最终所有节点的日志都与Leader一致。成为Leader节点后不会修改和删除已存在的日志,只会追加新的日志。
Raft算法中充分考虑了工程化中集群管理问题,支持动态的添加节点到集群,剔除故障节点等。下面详细描述添加和删除节点流程。
清空D节点上的所有数据,避免有脏数据。Leader将存量的日志通过AppendEntry RPC同步到D,使D的数据跟上其他节点。待D的日志追上后,Leader A创建一条Config Entry,其中集群信息包含ABCD。Leader A将Config Entry同步给B C D,Follower收到后应用,之后所有节点的集群信息都变为ABCD,添加完成。
注:在步骤2过程中,Leader仍在不断接收客户请求生成Entry,所以只要D与A日志相差不大即认为D已追上。
如下图所示,集群中原来包含A B C D,A为Leader,现在剔除节点D。
Leader A创建一条Config Entry,其中集群信息为ABC。A将日志通过AppendEntry RPC同步给节点B C。A B C在应用该日志后集群信息变为ABC,A不再发送AppendEntry给D,D从集群中移除。此时D的集群信息依旧为ABCD,在选举超时到期后,发起选举,为了防止D的干扰,引入额外机制:所有节点在正常接收Leader的AppendEntry时,拒绝其他节点发来的选举请求。
在节点重启时,由于无法得知State Matchine当前ApplyIndex(除非每次应用完日志都持久化ApplyIndex,还要保证是原子操作,代价较大),所以必须清空State Matchine的数据,将ApplyIndex置为0,,从头开始应用日志,代价太大,可以通过定期创建快照的方式解决该问题。如下图所示:
Raft具有很强的容错性,只要大多数节点正常互联,即可保证系统的一致性和可用性,下面是一些常见的异常情况,以及他们的影响及处理:
可以看到异常情况对系统的影响很小,即使是Leader故障也可以在极短的时间内恢复,任何情况下系统都一直保持强一致性,为此牺牲了部分可用性(大多数节点故障时,概率极低)。不过,Leader故障时新的Leader可能会包含旧Leader未提交或已提交但尚未通知客户端的日志。由于算法规定成为Leader后不允许删除日志,所以这部分日志会被新Leader同步并提交,但由于连接信息丢失,客户端无法得知该情况。当发起重试后会出现重复数据,需要有幂等性保证。此外,raft的核心算法都是围绕Leader展开,网络分区时可能出现伪Leader问题,也需要特殊考虑。
我们用State Matchine统一表示业务模块,其通过ApplyIndex维护已应用的日志index。以下为Raft与状态机交互的流程:
快照管理与业务紧密相关,不同系统快照制作的成本差异很大,CMQ中快照的内容十分轻量,一次快照的耗时在毫秒级,平均5min创建一次,各节点独立完成。实现上内存中维护了一份动态的快照,制作快照时首先拷贝出动态快照的副本,之后处理流继续更新动态快照,用拷贝出的副本创建快照文件,不影响实际的处理流。快照具体内容包括:
Raft算法具备强一致、高可靠、高可用等优点, 消息中间件通常分为高可靠版本和高性能版本两种。腾讯云CMQ是一款金融级的高可靠分布式消息中间件,通过raft保证了消息的可靠不丢失。同时在性能和可用性方面相比竞品都有显著提高。
保时捷中国召回17278辆进口Taycan系列电动汽车,可能出现制动液泄漏问题
华为正式发布园区网络“光进铜退”先锋行动 将投入5000万支持新老伙伴
特斯拉首次进入省级政府采购体系 官方回应:满足入围要求 后续是否采购看用户选择
苹果后端代码暗示iPhone 16全系配备相同芯片 但外媒认为会有区别
特斯拉股价周三再涨15.13美元 市值在近7个交易日增加2000亿美元
青云QingCloud EHPC 打造即买即用的全流程SaaS化超算服务
蚂蚁链发布BTN:可将区块链网络吞吐量提升186% 带宽成本降低80%
蚂蚁自研数据库OceanBase宣布开源 300万行核心代码向社区开放