TiKV 源码解析系列 - Lease Read

本系列文章主要面向 TiKV 社区开发者,重点介绍 TiKV 的系统架构,源码结构,流程解析。目的是使得开发者阅读以后,能对 TiKV 项目有一个初步了解,更好的参与进入 TiKV 的开发中。本文是本系列文章的第五章节。做者:唐刘 git

Raft log read

TiKV 是一个要保证线性一致性的分布式 KV 系统,所谓线性一致性,一个简单的例子就是在 t1 的时间咱们写入了一个值,那么在 t1 以后,咱们的读必定能读到这个值,不可能读到 t1 以前的值。github

由于 Raft 原本就是一个为了实现分布式环境下面线性一致性的算法,因此咱们能够经过 Raft 很是方便的实现线性 read,也就是将任何的读请求走一次 Raft log,等这个 log 提交以后,在 apply 的时候从状态机里面读取值,咱们就必定可以保证这个读取到的值是知足线性要求的。算法

固然,你们知道,由于每次 read 都须要走 Raft 流程,因此性能是很是的低效的,因此你们一般都不会使用。安全

咱们知道,在 Raft 里面,节点有三个状态,leader,candidate 和 follower,任何 Raft 的写入操做都必须通过 leader,只有 leader 将对应的 raft log 复制到 majority 的节点上面,咱们才会认为这一次写入是成功的。因此咱们能够认为,若是当前 leader 能肯定必定是 leader,那么咱们就能够直接在这个 leader 上面读取数据,由于对于 leader 来讲,若是确认一个 log 已经提交到了大多数节点,在 t1 的时候 apply 写入到状态机,那么在 t1 以后后面的 read 就必定能读取到这个新写入的数据。服务器

那么如何确认 leader 在处理此次 read 的时候必定是 leader 呢?在 Raft 论文里面,提到了两种方法。网络

ReadIndex Read

第一种就是 ReadIndex,当 leader 要处理一个读请求的时候:架构

  1. 将当前本身的 commit index 记录到一个 local 变量 ReadIndex 里面。app

  2. 向其余节点发起一次 heartbeat,若是大多数节点返回了对应的 heartbeat response,那么 leader 就可以肯定如今本身仍然是 leader。分布式

  3. Leader 等待本身的状态机执行,直到 apply index 超过了 ReadIndex,这样就可以安全的提供 linearizable read 了。性能

  4. Leader 执行 read 请求,将结果返回给 client。

能够看到,不一样于最开始的经过 Raft log 的 read,ReadIndex read 使用了 heartbeat 的方式来让 leader 确认本身是 leader,省去了 Raft log 那一套流程。虽然仍然会有网络开销,但 heartbeat 原本就很小,因此性能仍是很是好的。

但这里,须要注意,实现 ReadIndex 的时候有一个 corner case,在 etcd 和 TiKV 最初实现的时候,咱们都没有注意到。也就是 leader 刚经过选举成为 leader 的时候,这时候的 commit index 并不可以保证是当前整个系统最新的 commit index,因此 Raft 要求当 leader 选举成功以后,首先提交一个 no-op 的 entry,保证 leader 的 commit index 成为最新的。

因此,若是在 no-op 的 entry 还没提交成功以前,leader 是不可以处理 ReadIndex 的。但以前 etcd 和 TiKV 的实现都没有注意到这个状况,也就是有 bug。解决的方法也很简单,由于 leader 在选举成功以后,term 必定会增长,在处理 ReadIndex 的时候,若是当前最新的 commit log 的 term 还没到新的 term,就会一直等待跟新的 term 一致,也就是 no-op entry 提交以后,才能够对外处理 ReadIndex。

使用 ReadIndex,咱们也能够很是方便的提供 follower read 的功能,follower 收到 read 请求以后,直接给 leader 发送一个获取 ReadIndex 的命令,leader 仍然走一遍以前的流程,而后将 ReadIndex 返回给 follower,follower 等到当前的状态机的 apply index 超过 ReadIndex 以后,就能够 read 而后将结果返回给 client 了。

Lease Read

虽然 ReadIndex 比原来的 Raft log read 快了不少,但毕竟仍是有 Heartbeat 的开销,因此咱们能够考虑作更进一步的优化。

在 Raft 论文里面,提到了一种经过 clock + heartbeat 的 lease read 优化方法。也就是 leader 发送 heartbeat 的时候,会首先记录一个时间点 start,当系统大部分节点都回复了 heartbeat response,那么咱们就能够认为 leader 的 lease 有效期能够到 start + election timeout / clock drift bound 这个时间点。

为何可以这么认为呢?主要是在于 Raft 的选举机制,由于 follower 会在至少 election timeout 的时间以后,才会从新发生选举,因此下一个 leader 选出来的时间必定能够保证大于 start + election timeout / clock drift bound

虽然采用 lease 的作法很高效,但仍然会面临风险问题,也就是咱们有了一个预设的前提,各个服务器的 CPU clock 的时间是准的,即便有偏差,也会在一个很是小的 bound 范围里面,若是各个服务器之间 clock 走的频率不同,有些太快,有些太慢,这套 lease 机制就可能出问题。

TiKV 使用了 lease read 机制,主要是咱们以为在大多数状况下面 CPU 时钟都是正确的,固然这里会有隐患,因此咱们也仍然提供了 ReadIndex 的方案。

TiKV 的 lease read 实如今原理上面跟 Raft 论文上面的同样,但实现细节上面有些差异,咱们并无经过 heartbeat 来更新 lease,而是经过写操做。对于任何的写入操做,都会走一次 Raft log,因此咱们在 propose 此次 write 请求的时候,记录下当前的时间戳 start,而后等到对应的请求 apply 以后,咱们就能够续约 leader 的 lease。固然实际实现还有不少细节须要考虑的,譬如:

  • 咱们使用的 monotonic raw clock,而 不是 monotonic clock,由于 monotonic clock 虽然不会出现 time jump back 的状况,但它的速率仍然会受到 NTP 等的影响。

  • 咱们默认的 election timeout 是 10s,而咱们会用 9s 的一个固定 max time 值来续约 lease,这样一个是为了处理 clock drift bound 的问题,而另外一个则是为了保证在滚动升级 TiKV 的时候,若是用户调整了 election timeout,lease read 仍然是正确的。由于有了 max lease time,用户的 election timeout 只能设置的比这个值大,也就是 election timeout 只能调大,这样的好处在于滚动升级的时候即便出现了 leader 脑裂,咱们也必定可以保证下一个 leader 选举出来的时候,老的 leader lease 已通过期了。

固然,使用 Raft log 来更新 lease 还有一个问题,就是若是用户长时间没有写入操做,这时候来的读取操做由于早就已经没有 lease 了,因此只能强制走一次上面的 ReadIndex 机制来 read,但上面已经说了,这套机制性能也是有保证的。至于为何咱们不在 heartbeat 那边更新 lease,缘由就是咱们 TiKV 的 Raft 代码想跟 etcd 保持一致,但 etcd 没这个需求,因此咱们就作到了外面。

小结

在 TiKV 里面,从最开始的 Raft log read,到后面的 Lease Read,咱们一步一步的在保证线性一致性的状况下面改进着性能。后面,咱们会引入更多的一致性测试 case 来验证整个系统的安全性,固然,也会持续的提高性能。

源码地址:https://github.com/pingcap/tikv

相关文章
相关标签/搜索