分布式系列高并发分布式事务处理解决方案

Posted Java技术汇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式系列高并发分布式事务处理解决方案相关的知识,希望对你有一定的参考价值。

每日一句
       得之坦然,失之淡然,顺其自然,争其必然。
【分布式系列】高并发分布式事务处理解决方案
为了实现分布式事务,下面给大家介绍两个阶段的提交协议。 
阶段一

开始向事务涉及到的全部资源发送提交前信息。此时,事务涉及到的资源还有最后一次机会来异常结束事务。如果任意一个资源决定异常结束事务,则整个事务取消,不会进行资源的更新。否则,事务将正常执行,除非发生灾难性的失败。为了防止会发生灾难性的失败,所有资源的更新都会写入到日志中。这些日志是永久性的,因此,这些日志会幸免遇难并且在失败之后可以重新对所有资源进行更新。

阶段二

只在阶段一没有异常结束的时候才会发生。此时,所有能被定位和单独控制的资源管理器都将开始执行真正的数据更新。 在分布式事务两阶段提交协议中,有一个主事务管理器负责充当分布式事务协调器的角色。事务协调器负责整个事务并使之与网络中的其他事务管理器协同工作。 为了实现分布式事务,必须使用一种协议在分布式事务的各个参与者之间传递事务上下文信息,IIOP便是这种协议。这就要求不同开发商开发的事务参与者必须支持一种标准协议,才能实现分布式的事务。


01

Traditional Distributed Transaction


对于很多传统的分布式数据库来说,通常会使用 Agreement Protocol (譬如通常的 two-phase commit)机制来实现分布式事务,但这个会有一些问题。

首先就是网络开销比较大,一次事务要经过多次网络交互。有时候这些开销可能比实际的事务执行时间还长。同时,为了保证事务的一致性,我们需要对访问的数据进行加锁处理,而这个锁通常只有在事务完成之后才会被释放。虽然我们能做很多优化,譬如将多个并发的请求作为一个 batch 减少网络开销,但这个仍然没有办法减少锁冲突开销。

另外,在分布式事务上面使用锁,也容易引起死锁问题,而处理因为死锁导致的事务终止或者重试,也会增大事务的延时和降低整个系统的吞吐。

02

Deterministic Database

Calvin 并没有采用通用的 2PC 实现,Calvin 的目标是尽量的减少分布式事务的开销,采用的方式很简单,当一个事务需要多个节点一起参与的时候,不同节点在获取锁和开始执行事务之前,就已经在外面确定好了如何执行这个事务。

Calvin 是一个 deterministic database,也就是说,对于需要处理的事务,Calvin 会在全局确定好事务的顺序,并按照这个顺序执行。这些事务的执行顺序,我们可以认为是一个全局的有序 log,并且 Calvin 会通过复制机制来备份到不同的副本上面。所有的副本都是可以并行的顺序执行这些 log 的。这样,即使一个副本当掉,另外副本也仍然能执行并且 commit 事务。

03

Calvin Architecture

在前面说到,Calvin 致力于提供的是一个通用的分布式事务解决方案,所以它可以适配非常多的 storage,只要这些底层的 storage 系统实现了通常的 CRUD 操作。也就是说,我们可以在单机上面使用 RocksDB,然后加上 Calvin,就可以对外提供一个支持分布式事务的数据库了。

Calvin 主要分为三层

Sequencing layer,也就是 sequencer,负责拦截事务并且将它们放到一个全局的事务序列里面。这个序列也就是事务执行的顺序。Sequencer 也同时会处理复制和日志。

Scheduling layer,也就是 scheduler,它使用一种 deterministic locking scheme 的技术来编排事务的执行,在保证 sequencer 指定的顺序下,尽可能的允许多个事务并发的执行。

Storage layer,最终的数据存储层,可拔插,能支持不同的 storage。

上面三层都是可以水平扩展的,Calvin 的整个架构如下:
1
Calvin Architecture
对于 Calvin 来说,只要理解了 sequencer 和 scheduler 是如何设计的,那么其实就能理解 Calvin 是如何工作的了。
2
Sequencer
Sequencer 的主要作用就是接受客户端发过来的事务请求,然后将它们排序,记录到日志。因为这些事务是顺序排好序了,所以只要依次执行,就一定保证整个系统的事务一致性。最简单的做法当然就是用一个节点来处理,但大家知道这铁定不行,首先就是会有单点问题,另外就是随着数据量的膨胀,单个节点铁定承载不了。

Calvin 的 sequencer 是能独立水平扩展的,每个 sequencer 会使用一个 10ms 的 epoch 来批量收集一批 client 的请求, 将它们作为一个 batch,然后 sequencer 将这个 batch 持久化到 storage,并通过复制算法将其备份到其他的副本。 Sequencer 支持通常的 Master/Slave 异步复制,以及 Paxos 的复制。虽然异步复制性能好很多,不过为了保证数据安全,采用 Paxos 是一个更好的方案。

当一个 batch 被复制到足够的副本以后,这个 batch 对应的 GUID 会被存储到一个 Paxos 的 MetaLog 上面,为了更好的提高吞吐,Calvin 会将多个 GUID 作为 batch 存放到 MetaLog 里面。

也就是说,Calvin 在 sequencer 这层的处理其实就是将事务做 batch,通过 Paxos 将 batch 复制到不同的副本,然后在一个中心 Paxos 里面保存对应的 meta 信息。因为 MetaLog 里面保存的事务是顺序的,所以外面只需要读取 MetaLog,然后找到对应的事务数据,就能够顺序处理了。

如果真的如我上面这么猜测,Calvin 在 sequencer 这层其实还是有一个处理 MetaLog 瓶颈 Paxos。

当 transaction batch 复制成功之后,sequencer 就将这个 batch 发给所有的 scheduler,并带上 sequencer 的 Node ID,以及上面提到的 epoch number。

3
Scheduler

Calvin 通过 Sequencer 来保证事务的全局有序,如果完全顺序依次执行事务,一定可以保证事务的一致性,但这样性能会很慢。但如果并发执行,有可能会遇到不同事务操作相同数据的并发问题。

Calvin 在 Scheduler 这层使用了 deterministic lock scheme 机制保证事务能够被安全的并发执行。这个机制大概是这样的:

在所有的 scheduler 上面有一个总的 Lock manager

各个节点自己的 scheduler 只负责 lock 自己本地的数据

类似严格的 two phase locking,但加入了一些确定限制

所有的事务一定要拿到 lock 之后才能开始执行

所有在事务里面的 lock 顺序也是跟全局事务顺序一致的,也就是说,如果两个事务 A 和 B 需要独占一个 lock,A 事务在 B 事务的前面,那么 A 一定比 B 先拿到 lock

使用一个单独的线程来顺序处理 lock 请求

Lock manager 必须按照全局事务顺序来授权 lock

按照这个机制,如果我没有猜错,Lock manager 应该也是一个单点。

当一个事务拿到所有的 lock 之后,scheduler 就要开始执行这个事务了,会做如下几个步骤处理:

Read/Write 分析,scheduler 会分析这次事务 read 和 write 需要处理的数据在哪里,那些在本地,那些在其他节点,其他节点的数据叫做 participant,对于需要 write 的节点,叫做 active participant,而对于 read 的节点,叫做 passive participant。

4
执行 local read

执行 remote reads。scheduler 会将 read 请求发给其他 participants 去执行。

Collect remote read results

执行事务,并且 apply local write,对于其他节点的 write,其他的 scheduler 会自己 apply。


【分布式系列】高并发分布式事务处理解决方案


【分布式系列】高并发分布式事务处理解决方案


更多Java技术交流


每晚 8:20


“腾讯课堂”       搜索      “ 图灵学院“ 


免费Java公开课中给大家讲解记得关注哦!



【分布式系列】高并发分布式事务处理解决方案


往期推荐












【分布式系列】高并发分布式事务处理解决方案

以上是关于分布式系列高并发分布式事务处理解决方案的主要内容,如果未能解决你的问题,请参考以下文章

分布式事务两阶段提交协议三阶提交协议

关于分布式事务两阶段提交协议三阶提交协议

关于分布式事务两阶段提交协议三阶提交协议

关于分布式事务两阶段提交协议三阶提交协议

关于分布式事务两阶段提交协议三阶提交协议(转)

[高级]关于分布式事务两阶段提交协议三阶提交协议