事务系统实现模式很简单?你确定没忽视这些差异?
本文试图讨论这几个问题:
本文旨在阐述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。 看起来很简单,但我在这里显然忽略了很多细节:
2、基于RSM的事务 我们来考虑最后一个问题,RSM中的命令,可以直接是一个事务吗? 既然Raft都是串行Apply了,那么看起来把事务的所有操作作为一个命令扔到状态机执行并没有什么问题。 但问题在于,实际中的事务是交互式的,也就是包含了if-else等逻辑的,并且逻辑还可能依赖了数据库系统外部的状态,所以不能简单地用Write Batch + Snapshot来实现一个事务,还是要有Concurrency Control的逻辑。 为了解决Concurrency Control的问题,我们在Raft Leader上,实现一个Lock Table和Transaction Manager。拿S2PL方法举例:
这里举的例子是S2PL,但对于其他的并发控制方法也基本通用。例如Snapshot Isolation,事务开始时获得KV的Snapshot,读操作都走Snapshot,写操作获得写锁,数据Buffer在本地,事务提交时检查[begin, end]之间有没有写冲突,没有的话则通过Raft写事务日志,在Apply事务日志之后,把写操作应用到KVEngine,最后释放写锁。 这种方法接近Spanner的做法,它具有几个特点:
3、基于共享存储的事务 重新看一下上面这个模型,复制协议所做的事情非常简单,和其他模块的耦合也很小,仅仅是维护一个有序的Log,因此,我们可以把它从share-nothing推广到share-storage的模型中。 也就是说,我们把普通的单机事务引擎,放到一个高可用的存储上,就得到了基本可用的Fault-Tolerant 事务引擎了,连复制协议也不需要实现的。 不过事情显然不会这么简单:
4、基于高可用KV的事务 (编辑:ASP站长网) |