目录

结论

2PC、3PC和Percolator都是一种实现分布式事务(原子性)的协议。2PC是最基本的协议,3PC和Percolator都是基于2PC进行优化。

本文旨在对比这三种方法,分析他们的区别和优缺点,而不讲述他们的基本原理。适合对他们有基本理解的人阅读。

先说结论,后面是分析:

2PC对协调者有依赖性,2PC的协调者必须保存状态,记录各个参与者的信息,宕机后只能恢复,而不能直接更换。它是有状态的。2PC的第二阶段完全依赖于协调者。

3PC可以缓解2pc过度依赖于协调者的问题。但3PC并没有完全解决,而且性能还更差。

可以通过raft这种一致性协议,来保证2pc协调者的高可用,避免单点问题。这样当部分2PC协调者宕机后,通过raft的leader选举选出新的leader来成为这个事务新的协调者。

percolator解决了对协调者的依赖,并且有很大的性能提升。

percolator的特点在于,因为参与者不依赖于协调者,因此他的协调者可以是无状态的(例如TiDB Server)。协调者宕机之后只要有一个新的协调者就OK。它不依赖于特定的协调者,不需要等待旧的协调者重启。因此不需要raft这种协议来保持协调者的多副本。

所以percolator和2PC相比,除了有性能差距,还有对协调者依赖的差距。

性能提升在于primary key commit成功就可以返回,其它进行异步提交。而2PC必须等全部参与者都commit。受短板效应的影响,受限于每次最慢的那个参与者的完成时间。

percolator把2PC对协调者依赖的部分下沉到了参与者(TiKV,primary key),因此它的协调者变成了无状态的。

对比表格

需要从下面几个维度去对比:

协议 对协调者的依赖性 协调者状态 性能 可用性
Percolator 不依赖 无状态 高性能 参与者依赖raft(或其它高可用机制)
2PC 完全依赖 有状态 中性能 协调者和参与者依赖raft
3PC 部分依赖 有状态 低新能 协调者和参与者依赖raft

2PC和Percolator对比

percolator将第二阶段改成了primary和secondary两种,secondary keys可以异步提交,primary的状态作为整个过程的状态,而primary可以是原子的。

因为在进行prewrite之后,整个事务就可以执行了,至于是否执行成功,percolator将这个状态保存到primary key上,它成功了(原子的),那么其他的secondary key也可以看作是成功了,就可以直接向客户端返回成功。即使后面有secondary key失败了,也可以重试。

传统的2PC要等所有的key在第二阶段都返回成功才能向客户端,因为它没有primary,所以无法保存状态,这导致当第二阶段协调者crash之后,事务就会失败。而percolator等primary key成功了或失败了就可以马上返回,其它keys异步提交,加快了速度。

percolator用primary key巧妙地优化了这个问题。

从下面2PC和3PC的对比可以看到,2PC高度依赖于协调者。

percolator的第二阶段不再依赖于协调者。因为第二阶段的primary key的提交或回滚是原子性的,标志着第二阶段是否成功。如果primary key commit了然后协调者宕机了,那么其它keys lock住了,在其它事务执行的时候检测到这点,然后去检查lock对应的primary key,就可以rollback或者commit。

2PC和3PC对比

这个讲的不错:http://dengchengchao.com/?p=1425 他讲的通知聚会的例子很好。

2PC的问题

如果参与者在2PC期间发生失败:

  • 第一阶段宕机:协调者会发现其中某个参与者超时,则第二阶段会发起回退请求。
  • 第二阶段宕机:协调者会一直重试直到此参与者返回成功。

如果协调者2PC期间宕机:

不管哪个阶段宕机,由于协调者每次在发起请求前会保存状态,因此他可以在恢复后发起询问。

2PC的优点就是简单,但是缺点也很明显:

参与者高度依赖协调者,因此当协调者发生故障的时候,参与者只能一直阻塞直到协调者恢复,因此故障风险大,并且会影响系统的效率

3PC

3PC相对于2PC而言增加了一个缓冲阶段(canCommit),这个阶段不会修改数据,这个缓冲阶段主要就是为了解决2PC对协调者强依赖的问题(变成了更弱的依赖,但依然是依赖,没有从根本上解决问题)。

相对于2PC而言,3PC增加了一个缓冲阶段:CanCommit,通过缓冲阶段能够让所有参与者预先准备事务,一般来说,如果事务能够进行到第二阶段,则表示大部分情况下,事务应该都能正常运行。

可以看到,3PC通过增加一个阶段,便减弱了参与者对协调者的依赖,比如在第一个阶段之后小A没有收到X的信息,那么小A便可以不去,因为他并不知道后面的人情况,而在第二个阶段,相当于做了一个约定,当小A收到此消息后,便默认第三阶段是commit,如果此后X宕机,那么小A在等待超时后,除非收到第三阶段回退的消息,否则小A依然会去参加同学聚会,因为一般在第一阶段答应过X之后,其他参与者都会尽力的去提交此次事务。

虽然3PC减弱了对协调者的依赖,但是带来的问题便是极端情况下数据可能不一致,因为3PC第三阶段如果超时,其他参与者便会默认提交,那么如果在某些特殊情况下,使得X发送的是abort消息,那么就会出现数据不一致的问题。

我对2PC和3PC对于协调者依赖的理解

3PC最大的特点在于,第一阶段发生冲突之后不需要rollback。这样在第二阶段宕机的话,参与者可以在超时后继续正常执行事务,而不依赖于协调者了。

如果第一阶段没有发生冲突,就需要在第二阶段发送pre_commit指令,这时会修改数据。如果所有的参与者都收到了pre_commit,那么如果这时协调者宕机了,他们可以在超时后自动执行第三阶段。不依赖与协调者。

如果只有一部分收到了pre_commit,那么这时还需要等待协调者恢复之后才能知道自己是commit还是rollback。因此这种情况依赖于协调者。

综上所述,对于上面三种情况,3PC依赖协调者的情况只有:第一阶段无冲突,第二阶段pre_commit时协调者宕机。

而2PC依赖协调者的情况:整个第一阶段,第二阶段。属于完全依赖。

因此,3PC缓解了对协调者的依赖,但是没有完全解决。

而Percolator是不依赖协调者的。不管是哪个阶段都不依赖。

3PC缓解了对协调者的依赖,但是没有完全解决。

并且3PC的代价是流程更长,性能更差,因此会更容易发生事务冲突。

总结

我认为:

2PC对协调者有依赖性,2PC的协调者必须保存状态,记录各个参与者的信息,宕机后只能恢复,而不能直接更换。它是有状态的。2PC的第二阶段完全依赖于协调者。

3PC可以缓解2pc过度依赖于协调者的问题。但3PC并没有完全解决,而且性能还更差。

可以通过raft这种一致性协议,来保证2pc协调者的高可用,避免单点问题。这样当部分2PC协调者宕机后,通过raft的leader选举选出新的leader来成为这个事务新的协调者。

percolator解决了对协调者的依赖,并且有很大的性能提升。

percolator的特点在于,因为参与者不依赖于协调者,因此他的协调者可以是无状态的(例如TiDB Server)。协调者宕机之后只要有一个新的协调者就OK。它不依赖于特定的协调者,不需要等待旧的协调者重启。因此不需要raft这种协议来保持协调者的多副本。

所以percolator和2PC相比,除了有性能差距,还有对协调者依赖的差距。

性能提升在于primary key commit成功就可以返回,其它进行异步提交。而2PC必须等全部参与者都commit。受短板效应的影响,受限于每次最慢的那个参与者的完成时间。

percolator把2PC对协调者依赖的部分下沉到了参与者(TiKV,primary key),因此它的协调者变成了无状态的。

为什么 percolator 的事务两阶段提交不需要 Coordinator? - hellocode的回答 - 知乎 (PS. 问题是错的)https://www.zhihu.com/question/300050882/answer/518833781

可用性

我认为,Percolator的协调者是无状态的,因此协调者不需要raft维护多副本,可以直接更换一个新的。就像TiDB的TiDB Server一样。

2PC和3PC的协调者都是有状态的,因此需要多副本,如使用raft。如果没有多副本,只能等待它恢复。如果它恢复不了,可能就凉凉了。

而不管是Percolator还是2PC还是3PC,他们的参与者都需要有多副本机制如raft。否则一个参与者宕机了或者数据损坏了,那么系统就不可用了。因此需要raft来支持他们的高可用。

因此可以总结为:

Percolator只有参与者依赖raft(或其它高可用机制),而2PC和3PC的参与者都依赖raft。