一、基本概念
讨论的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的逻辑。