引言
EPaxos(Egalitarian Paxos)作为工业界备受瞩目的下一代分布式一致性算法,具有广阔的应用前景。但纵观业内,至今仍未出现一个EPaxos的工程实现,甚至都没看到一篇能把EPaxos讲的通俗一点的文章。EPaxos算法理论虽好,但由于其实在晦涩难懂,工程实现上也有很多挑战,实际应用落地尚未成熟。
本文旨在通俗易懂地介绍EPaxos算法,由浅入深、一步一步的让只有Paxos或Raft等分布式一致性算法基础的同学都能轻易看懂EPaxos,真正将晦涩难懂的EPaxos,变的平易近人,带入千万家。
Paxos的问题
一切还要从Paxos说起。Paxos是分布式一致性算法的鼻祖,在2F+1个副本中可以容忍F个副本同时失效。
Paxos正常情况下达成一次决议需要两个阶段:Prepare阶段和Accept阶段。
Prepare阶段各副本竞争提议权,Accept阶段竞争到提议权的副本发起提议,让议案在各副本间达成一致。
使用Paxos对一系列值达成一致的流程如图1所示。三个副本,以不同颜色标识,A、B、C、D是它们提议的值。它们竞争每个Instance,提议自己的值:
Paxos独立的决定每个Instance的值。针对每个Instance,运行完整的Paxos两阶段流程,决定该Instance的值。
Paxos达成一次决议至少需要两次网络来回,在并发情况下可能需要更多的网络来回,极端情况下甚至可能形成活锁,效率低下。为了解决这些问题,Multi-Paxos应运而生。
Multi-Paxos在各副本中选举一个Leader,提议由Leader发起,没有竞争,解决了活锁问题。提议都由Leader发起的情况下,Prepare阶段可以跳过,将两阶段变为一阶段,提高效率。
使用Multi-Paxos对一系列值达成一致的流程如图2所示。三个副本,以不同颜色标识,首先进行Leader选举,绿色副本被选为Leader,然后连续提议A、B、C、D四个值:
Multi-Paxos首先选举Leader,Leader选出来后Instance的提议权都归Leader,无需竞争Instance的提议权,因此可以省略Prepare阶段,只需要一阶段。Leader的存在提高了达成决议的效率,但同时也成为了性能和可用性的瓶颈。
Leader需要处理比其它副本更多的消息,各副本负载不均衡,资源利用率不高。Leader宕机后系统不可用,直到新Leader被选举出来,才能恢复服务,降低了可用性。
Basic Paxos每个副本都能提议,可用性高,但因为竞争冲突导致效率低下;Multi-Paxos选举Leader避免冲突,提高效率,但同时又引入了Leader瓶颈,降低了可用性。效率和可用性能否兼顾?EPaxos正是为了解决此问题而提出。不同于Multi-Paxos引入Leader来避免冲突,EPaxos采用另一种思路,它直面冲突,试图解决冲突问题。
EPaxos的解决方案
EPaxos是一个Leaderless的一致性算法,任意副本均可发起提议,通常情况下,达成一次决议需要一次或两次网络来回。
EPaxos无Leader选举开销,一个副本不可用可立即访问其他副本,具有更高的可用性。各副本负载均衡,无Leader瓶颈,具有更高的吞吐量。客户端可选择最近的副本提供服务,在跨AZ跨地域场景下具有更小的延迟。
不同于Paxos,事先对所有Instance编号,然后再独立对每个Instance的值一一达成一致。EPaxos可并发的处理多个Instance,不事先对Instance编号,而是在运行时动态决定各Instance之间的顺序。
EPaxos不仅对每个Instance的值达成一致,还对Instance之间的相对顺序达成一致。EPaxos将不同Instance之间的相对顺序也作为一致性问题,在各个副本之间达成一致,因此各个副本可并发地在不同的Instance中发起提议,在这些Instance的值和相对顺序都达成一致后,各副本再对它们按照相对顺序重新排序,形成一致的顺序。
使用EPaxos对一系列值达成一致的流程如图3所示:三个副本,以不同颜色标识,各副本有自己的Instance空间,在各自的Instance中提议自己的值,A、B、C、D是它们提议的值。每个Instance不仅对值达成一致,还对与其它Instance之间的相对顺序达成一致。
EPaxos的Instance空间是二维的,每个副本拥有二维Instance空间中的一行,无需竞争Instance的提议权,各副本可并发的在各自的Instance空间中发起提议,同时维护Instance之间的相对顺序,对Instance的值和相对顺序都达成一致。最后各副本各自按照相对顺序对Instance进行确定性的重新排序,即对一系列值达成一致。
EPaxos引入依赖(deps)的概念,作为Instance的一个属性,以表示Instance之间的相对顺序。A ← B即B依赖A,表示A在B之前。每个Instance都有自己的依赖集合,EPaxos维护Instance之间的依赖,并让依赖集合与值一起在各副本之间达成一致,最后各副本按照依赖对Instance重新排序,得到一致的值序列。图3中的案例,最后各副本达成一致的一系列值为:A ← B ← C ← D。
将EPaxos的Instance看作图上的结点,Instance的依赖集合看作结点的出边,Instance的值和依赖集合达成决议后,图的结点和边就在各副本之间达成一致,因此各副本会看到到相同的图。
EPaxos对Instance重新排序的过程,类似于对图进行确定性的拓扑排序。但需要注意的是EPaxos的Instance之间的依赖可能形成环,即图中可能有环路,因此不完全是拓扑排序。
为了处理循环依赖,EPaxos对Instance重排序的算法需要先寻找图的强连通分量,环路都包含在了强连通分量中,所有强连通分量构成一个有向无环图(DAG),然后对强连通分量进行确定性的拓扑排序。
EPaxos对Instance重新排序的流程如图4所示,其中由背景色圈起来的是强连通分量:
寻找图的强连通分量一般使用Tarjan算法,它是一个递归算法,实际压测发现递归实现很容易爆栈,也给工程应用带来了一定的挑战。
不同强连通分量中的Instance按照确定性的拓扑顺序排序,同一强连通分量中的Instance是并发提议的,理论上可按任意确定性规则排序。EPaxos给出了一种方案,为每个Instance维护了一个seq序列号,seq的大小近似反映了Instance提议的顺序,期望全局唯一递增,同一强连通分量里面的Instance按照seq大小排序。实现的时候测试发现seq可能重复,EPaxos论文并未考虑到这一点,后续文章会更详细的介绍此问题与解决方案。
当有Instance达成决议,并且其依赖的所有Instance也都达成决议后,就可以开始一次排序过程。实际上,随着新的Instance不断的运行,旧的Instance可能依赖新的Instance,新的Instance又可能依赖更新的Instance,导致依赖链不断延伸,没有终结,排序过程一直无法进行,形成活锁。这也是EPaxos工程应用的一大挑战。
因为Instance排序算法是确定性的,各副本基于一致的依赖关系图对Instance重新排序后,得到一致的Instance序列,即对一系列值达成一致。
总结
EPaxos通过引入动态顺序,同时兼顾了效率和可用性,融合了Basic Paxos和Multi-Paxos的优点,具有广阔的应用前景。本文粗浅的介绍了EPaxos的基本概念和直观理解,希望能让读者对EPaxos有个整体印象。
思考
最后留下几个思考题,感兴趣的同学可以思考思考:
EPaxos的Instance没有事先编号,那Instance如何标识?
EPaxos如何确定Instance的依赖集合,又如何让依赖集合达成一致?
EPaxos的Instance之间的依赖为什么会形成环,什么情况下会形成循环依赖?