加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

事务系统实现模式很简单?你确定没忽视这些差异?

发布时间:2019-02-20 23:13:47 所属栏目:建站 来源:hellocode
导读:本文试图讨论这几个问题: MySQL的redo log和binlog为什么要用XA? MongoDB的oplog是按照什么顺序复制? Raft真的只能串行Apply吗? 数据库的复制和事务是完全独立的两回事? 为什么MySQL不早点做一个Raft插件,直接用Raft实现高可用? 本文旨在阐述Fault-Toler
副标题[/!--empirenews.page--]

本文试图讨论这几个问题:

  • MySQL的redo log和binlog为什么要用XA?
  • MongoDB的oplog是按照什么顺序复制?
  • Raft真的只能串行Apply吗?
  • 数据库的复制和事务是完全独立的两回事?
  • 为什么MySQL不早点做一个Raft插件,直接用Raft实现高可用?

本文旨在阐述Fault-Tolerant Transaction的几种实现模式。虽然乍一看它们可能都是Raft+KVEngine +Concurrency Control,容易被认为是同一类方法,但实际上的差异很大,在讨论时不应该忽视它们之间的差异。

一、基本概念

讨论的Fault-Tolerance,指的是通过网络通信的多个计算机节点,在部分节点发生Stop Failure的情况下,仍然尽力保证可用性;

不讨论具体的Fault-Tolerance方法,默认读者对Raft等算法有基本理解;

也不讨论具体的Concurrency Control方法,默认读者对其有基本的理解;

会涉及到Spanner、TiKV、MongoDB等具体的数据库。

1、基于RSM的Fault-Tolerant KV

Replicated State Machine最早应该是在『Implementing fault-tolerant services using the state machine approach』提出。它是一种很简单实用的实现容错的方法,核心思想是:几个状态机具有相同的初始状态,并且按照同样的顺序执行了同样的命令序列,那么它们的最终状态也是一样的。由于状态一样,那么任意一个状态机宕机,都可以被其他的代替,因此实现了Fault Tolerant。

事务系统实现模式很简单?你确定没忽视这些差异?

这里提到了几个概念,命令、执行顺序、状态机,它们都是抽象概念,对应到具体的应用场景才有实际意义。在KVEngine的场景下,命令就是Put/Get等操作,状态机就是KVEngine本身,而执行序列,则由Replication Log决定。

既然提到了RSM和KV,那么基于RSM的KV也就呼之欲出了。把发到KVEngine的操作先用Raft复制一遍,在Apply的时候扔回到KVEngine执行,于是我们就得到了一个Fault-Tolerant的KVEngine。

看起来很简单,但我在这里显然忽略了很多细节:

  • 串行还是并行Apply:Raft被人诟病的一点是串行Commit、串行Apply,但这并不是Raft的锅;
  • 两条Log:Raft复制需要一个Log,KVEngine也会有一个WAL,会带来IO放大,能不能合并成一个呢?
  • Checkpoint:为了加速Recovery,需要做Checkpoint;
  • 只读操作需要复制吗?
  • 命令可以是复合操作吗:单行的CAS操作可以吗,多行的事务操作可以作为一个命令吗?

2、基于RSM的事务

我们来考虑最后一个问题,RSM中的命令,可以直接是一个事务吗?

既然Raft都是串行Apply了,那么看起来把事务的所有操作作为一个命令扔到状态机执行并没有什么问题。

但问题在于,实际中的事务是交互式的,也就是包含了if-else等逻辑的,并且逻辑还可能依赖了数据库系统外部的状态,所以不能简单地用Write Batch + Snapshot来实现一个事务,还是要有Concurrency Control的逻辑。

事务系统实现模式很简单?你确定没忽视这些差异?

为了解决Concurrency Control的问题,我们在Raft Leader上,实现一个Lock Table和Transaction Manager。拿S2PL方法举例:

  • 读数据之前加读锁,写数据之前加写锁;读操作通过Raft读数据,写操作Buffer在本地;
  • 在用户决定事务提交时,即可释放读锁;通过Raft写一条事务日志,包含所有写操作;
  • 在Raft Apply事务日志时,把写操作应用到KVEngine,并且释放写锁。

这里举的例子是S2PL,但对于其他的并发控制方法也基本通用。例如Snapshot Isolation,事务开始时获得KV的Snapshot,读操作都走Snapshot,写操作获得写锁,数据Buffer在本地,事务提交时检查[begin, end]之间有没有写冲突,没有的话则通过Raft写事务日志,在Apply事务日志之后,把写操作应用到KVEngine,最后释放写锁。

事务系统实现模式很简单?你确定没忽视这些差异?

这种方法接近Spanner的做法,它具有几个特点:

  • 只有Leader需要维护Lock Table、Transaction Manager,事务并发控制基本在Leader节点完成;
  • 从RSM的角度来看,这里的Lock Table起到了命令定序的作用,保证所有State Machine按照同样的顺序执行命令;
  • 加锁操作不走复制协议,解锁操作在复制协议Apply之后完成,锁会在复制的开始到Commit一直持有:也就意味着,复制协议的Commit即是事务的Commit,在Commit之前发生Failover,事务都会Abort;
  • Raft所复制的,即是事务的REDO。

3、基于共享存储的事务

重新看一下上面这个模型,复制协议所做的事情非常简单,和其他模块的耦合也很小,仅仅是维护一个有序的Log,因此,我们可以把它从share-nothing推广到share-storage的模型中。

事务系统实现模式很简单?你确定没忽视这些差异?

也就是说,我们把普通的单机事务引擎,放到一个高可用的存储上,就得到了基本可用的Fault-Tolerant 事务引擎了,连复制协议也不需要实现的。

不过事情显然不会这么简单:

  • 如何实现只读节点,提供读扩展的能力;
  • 计算节点如何更快地Failover;
  • 如何把更多的操作下推到存储节点。

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读