接上一篇《brpc初探》。
什么是RAFT
看内部一个开源项目的时候,一开始我以为他们自己实现了raft协议。但是看了代码之后,发现用的是braft。因为在我们自己bg里一直在提paxos,bg开源的东西很多也是基于paxos。但是实际上paxos是什么我并不清楚,只知道是个一致性协议。关于paxos和raft的关系,可以看braft的文档(这里赞一下braft的文档,写得非常高质量):
RAFT是一种新型易于理解的分布式一致性复制协议,由斯坦福大学的Diego Ongaro和John Ousterhout提出,作为RAMCloud项目中的中心协调组件。Raft是一种Leader-Based的Multi-Paxos变种,相比Paxos、Zab、View Stamped Replication等协议提供了更完整更清晰的协议描述,并提供了清晰的节点增删描述。
Raft作为复制状态机,是分布式系统中最核心最基础的组件,提供命令在多个节点之间有序复制和执行,当多个节点初始状态一致的时候,保证节点之间状态一致。系统只要多数节点存活就可以正常处理,它允许消息的延迟、丢弃和乱序,但是不允许消息的篡改(非拜占庭场景)。
Raft可以解决分布式理论中的CP,即一致性和分区容忍性,并不能解决Available的问题。其中包含分布式系统中一些通常的功能:
- Leader election.
- Replication and recovery.
- Snapshot and log compaction.
- Membership management.
- Fully concurrent replication.
- Fault tolerance.
- Asymmetric network partition tolerance.
- Workaround when quorate peers are dead.
通过RAFT提供的一致性状态机,可以解决复制、修复、节点管理等问题,极大的简化当前分布式系统的设计与实现,让开发者只关注于业务逻辑,将其抽象实现成对应的状态机即可。基于这套框架,可以构建很多分布式应用:
- 分布式锁服务,比如Zookeeper
- 分布式存储系统,比如分布式消息队列、分布式块系统、分布式文件系统、分布式表格系统等
- 高可靠元信息管理,比如各类Master模块的HA
overview: https://github.com/brpc/braft/blob/master/docs/cn/overview.md
Paxos主要包括两个组件:Proposer和Acceptor,其中Proposer主动发起投票,Acceptor被动接收投票,并存储提案值。在实际系统中,每个Paxos Server都包含这两个组件。
关于paxos和raft还有专门的文档:
https://github.com/brpc/braft/blob/master/docs/cn/paxos_protocol.md
https://github.com/brpc/braft/blob/master/docs/cn/raft_protocol.md
在构建分布式存储系统过程中,一般会有Master来实现一些节点加入离开、副本修复、负载均衡以及业务相关的元信息CURD。对于这些Master模块的HA百度做过很多尝试,比如keepalived、QJM等,一直没有比较理想的解决方案。
在2015年中的时候,我们想到用Raft来解决这个问题,Raft的复制状态机能够解决高可用的问题,选主和节点变更也非常方便,不用再依赖ZK。
braft是解决复制状态机问题,brpc是解决模块间RPC通信问题。braft中Raft协议的互通直接使用brpc实现,runtime使用了bthread,因此braft编译需要依赖brpc,从这点来看braft和brpc有一定的绑定关系。
但是从另一个角度来看,braft中核心的是协议状态机比如log、snapshot、configuration这些东西的抽象和实现,协议RPC只是其中一环,做一层transport抽象也可以比较容易的替换为其他的coroutine based protobuf RPC框架,对于非coroutinebased protobuf RPC来讲,braft只能用类似logcabin中pthread同步RPC,这样就丧失了多复制组支持的特性,RPC的回调改造成本就比较高了。
做基础架构工作,第一要做的是时刻关注学术界和企业界的发展,多与同行交流来获取业界的发展动态,不断的提高自己的眼界,有助于做出更好的系统设计。
在大型系统设计的时候需要能够构建清晰的模型,模块怎么划分怎么交互。模型清晰之后就是系统的详细设计,分布式系统中有很多时序相关的东西和问题,不能像单机一样去调试,所以在设计阶段就要把系统中的每个细节都想清楚,能够推演系统的各个流程。思考系统中各种race condition,对于这些race condition首先要量力而行,避免过早设计、过早优化导致项目延期;解决问题过程中,如无必要尽量使用简单的方案,复杂方案的实现也会带来隐患;对于低概率问题或者是高成本问题,即使不解决也需要做到心中有数。
架构改进要数据说话,通过各种工具和日志等分析出系统架构中最棘手的三个问题,然后针对这些问题制定相应的改造方案。这里需要注意的是方案设计不仅仅是提出一个全新的解决方案,还需要考虑如何把系统从当前方案迁移到新的方案,同时确保迁移过程是尽可能的平滑无损。
对于重大版本在开发测试完成之后,需要做几次上线演练,记录并修正演练过程中的非预期问题。这样经过几次迭代之后,系统的问题就会逐步收敛,当收敛到一定阶段之后,如果依然有一些比较大的问题难以修复,这个时候根据人力条件判断是继续在现有条件下规避问题,还是整体重构或者是部分重构来解决问题。
https://baijiahao.baidu.com/s?id=1603689359064649109
关于测试的论述也对我很有启发。
几乎所有的框架、模块、类库, 都会把高性能作为最重要的标签之一(当然, braft也不例外)。但是常常开发者对于性能理解只是停留在吞吐或者QPS的数字表面,性能测试变成了想方设法刷数字的游戏,而不考虑场景是否符合实际应用场景。常见的『提升性能数字』的方法有以下两类:
Batch: 系统主动等request数量达到一定数量或者等一个超时时间, 合成一个request发给后端系统, 取决于batch_size / request_size 的值, 能将"QPS"提升几十~几百倍. (这点后面再详细解释)。
Client不限制的异步发送: 这样能让系统一直处于高负载状态,不存在任何的等待,规避了一些系统调用和线程同步开销。
这些设计虽然能跑出不错的benchmark数据,但是完全偏离了实际的应用场景。以batch为例, batch的问题在于本质上并没有去指导如何提升系统的QPS。在性能测试中,并发度通常很高,会有源源不断的请求进来,所以每个请求并不需要等待多长时间就能满足batch size的条件, 然而在真实场景中,并发度并没有这么高,这样会导致每个请求都必须要『等待』一个设定的值, latency无法达到最优解。而这时候工程师往往会沉浸在优化超时、batch size等调参工作,从而忽略了分析系统瓶颈这类真正有意义的事情。另外硬件性能的逐渐提升,网络和磁盘本身的延迟越来越短, 这套机制无法兼顾低负载下的延迟和高负载的吞吐。
在braft中,我们主要采用了以下几点方法来提高的性能:
- 数据流是全并发的, leader写本地磁盘和向follower复制数据是完全并发的。
- 尽可能的提高局部性,充分发挥不同层面的cache的作用
- 尽可能隔离不同硬件的访问,通过流水线的形式提高吞吐
- 尽可能的降低锁临界区大小, 关键路径上采用lock-free/wait-free算法.
https://github.com/brpc/braft/blob/master/docs/cn/benchmark.md