sofastack / sofa-jraft

A production-grade java implementation of RAFT consensus algorithm.
https://www.sofastack.tech/projects/sofa-jraft/
Apache License 2.0
3.57k stars 1.14k forks source link

结合NWR实现Flexible raft,用于自定义Quorum的大小 #975

Open shihuili1218 opened 1 year ago

shihuili1218 commented 1 year ago

简介

RAFT

SOFAJRaft 是一个基于 RAFT 一致性算法的生产级高性能 Java 实现,JRaft的运行过程分为两个阶段:

在原始的RAFT算法中,Leader选举、日志复制,都要求获得多数派(即:⌊N / 2⌋ + 1)成员的支持。即:某个成员获得多数派成员的支持则晋升为Leader;某个日志复制到多数派则达成共识。

NWR

NWR模型,常用于动态调整一致性强度。具体描述如下:

满足W+R>N的情况下,读Quorum和写Quorum一定存在交集,这个相交的成员一定拥有最新的数据,那么这个分布式系统一定是满足强一致性的。

目标

在一些场景中,我们期望能够动态调整Quorum的数量,使其更贴合自己的业务场景。例如:一个5成员组成的集群,在一个写多读少的场景中,则期望把多数派(3)调整为2,降低达成共识的条件,使得写请求能尽快的完成。同时,为了继续满足RAFT的正确性,将写Quorum调整为2需要付出代价是读Quorum要调整为4,这样才能满足W+R>N。

基于这个设计,用户甚至可以将Quorum调整到一个极端情况。例如:W=1,R=N,此时写效率最高,当然这需要付出的代价是不允许有成员宕机。

RAFT结合NWR

Raft中Leader选举存在两个功能:

这正好对应了NWR模型中的读场景,而处理事务日志对应NWR模型中的写场景。

NWR的计算方式

因为JRaft是支持成员变更的,N是动态变化的,因此我们所配置的W或R不能是一个整数,可以考虑为(0, 1]范围内的小数。再通过计算获得W和R的具体值。 例如:N=5,w=0.4,计算公式为:W=⌈N * w⌉,R=N - W + 1。得到以下表格

N W R
5 2 4
6 3 4
7 3 5
8 4 5
9 4 6
10 4 7
11 5 7
funky-eyes commented 1 year ago

这个功能感觉不好吧,如果用户可能稀里糊涂的配了w,就跟kafka副本集一样,5台机器配了topic是2副本,此时挂了2个broker,必定有部分分区不可用,毕竟这个项目叫jraft,就是应该基于raft的原理基础上去实现,跟nwr模型结合反倒感觉违背了raft的共识,这还能叫raft吗?

shihuili1218 commented 1 year ago

这个功能会提供开关,给用户多一种选择。选择使用自定义Quorum,一定是在生产场景多次考量,且熟悉配置w或r的造成影响的前提下使用此功能。

这个功能必须要满足W+R>N的场景,在满足这个条件的前提下,无论用户怎么折腾,数据正确性是一定能保证的。当然你说的情况(可用性的降低)是使用此功能需要付出的代价,这是用户需要结合生产场景,在性能和可用性之间的取舍。

funky-eyes commented 1 year ago

写请求更快的响应,说明是减少了写时日志同步的应答数吗?假设出现了网络分区怎么解决? req->leader -> follower->leader -> rep,这个时候在中间follower相当于没有超过半数了,加上网络分区,leader节点达标了写入了,假设是5台,2 3这样分区,2写入了这个数据,然后3台立马恢复了网络,这个时候会发生什么?或者2台机器正常,3台的盘写满了,或者盘出现了问题

shihuili1218 commented 1 year ago

写请求更快的响应,说明是减少了写时日志同步的应答数吗? -- 是的 假设是5台(写quorum为2),2\3这样分区,2写入了这个数据,然后3台立马恢复了网络,这个时候会发生什么? -- 3台恢复过来,集群是不能提供服务的,因为读quorum需要4个节点存活,这就是需要付出的代价:可用性降低、读请求延迟

funky-eyes commented 1 year ago

我的意思是,那3台挂了或者网络断开了,leader的日志可能都没发过去,我举个极端例子 2台 /3台网络分区后,前两台写入了数据,后三台没有,然后集群一并宕机,此时后三台恢复启动,选出新leader,再启动前2台,此时数据不就丢了吗?因为提交的数据没有大于1/2了,前面2台client发请求到对应leader,leader都给client确切已写入的应答后挂了,这不就不一致了?还是说是我理解的有问题呢?

shihuili1218 commented 1 year ago

我整理以下你说的场景啊,集群{A, B, C, D, E},Leader为A,W=2,R=4。 分区后的集群为{A, B}、{C, D, E}。

  1. {A, B}是可以处理写请求的,Leader A将日志写入A和B,达成共识。
  2. {A, B}、{C, D, E}都宕机
  3. {C, D, E}恢复,注意此时,是不足选出Leader的,因为R=4。
funky-eyes commented 1 year ago

明白了,是靠R进行限制达到一致性,这样就是降低了可用性了,按raft原本设计,5节点挂2个是还可以正常提供服务,而现在挂的次数>=w(写成员)就会导致服务不可用,不过对99.9%的情况而言,不太可能出现一下挂那么多节点,我认为这个功能是有比较好的价值,就是用到这块功能时,使用者用户需要明确知道其中的原理,否则可能会造成类似我上面存在的误解。 然后这个功能貌似对小于等于3节点其实是无影响的。这个任务是要做summercode的项目,还是开放到社区?

shihuili1218 commented 1 year ago

作为summercode的项目呢

erdengk commented 1 year ago

非常有意思的提议,我看到它作为ospp的项目之一,我准备申请它。

1294566108 commented 1 year ago

同样在OSPP关注到了,希望能继续参与JRaft的代码贡献