这篇文章转载TiDB大牛 唐刘 的博客:https://mp.weixin.qq.com/s?__biz=MzI3NDIxNTQyOQ==&mid=2247484544&idx=1&sn=7d8e412ecc5aaeb3f9b7cf391bdcf398&chksm=eb1623eadc61aafcefcfbdf36b388a5f96d3009d21641eb6ac67c57317d6c397ddeb58fc7d06&scene=21#wechat_redirect

由于工作原因,最近在学习TiDB中TiKV相关的知识,觉得写得挺好的,并转载留存。

在分布式领域,为了保证数据的一致性,通常都会使用 Paxos 或者 Raft 来实现。但 Paxos 以其复杂难懂著称,相反 Raft 则是非常简单易懂,所以现在很多新兴的数据库都采用 Raft 作为其底层一致性算法,包括我们的 TiKV。

当然,Raft 虽然简单,但如果单纯的按照 Paper 的方式去实现,性能是不够的。所以还需要做很多的优化措施。本文假定用户已经熟悉并了解过 Raft 算法,所以对 Raft 不会做过多说明。(还不熟悉 Raft,点这里:)TiKV 源码解析系列——如何使用 Raft)

Simple Request Flow

这里首先介绍一下一次简单的 Raft 流程:

1. Leader 收到 client 发送的 request。

2. Leader 将 request append 到自己的 log。

3. Leader 将对应的 log entry 发送给其他的 follower。

4. Leader 等待 follower 的结果,如果大多数节点提交了这个 log,则 apply。

5. Leader 将结果返回给 client。

6. Leader 继续处理下一次 request。

可以看到,上面的流程是一个典型的顺序操作,如果真的按照这样的方式来写,那性能是完全不行的。

Batch and Pipeline

首先可以做的就是 batch,大家知道,在很多情况下面,使用 batch 能明显提升性能,譬如对于 RocksDB 的写入来说,我们通常不会每次写入一个值,而是会用一个 WriteBatch 缓存一批修改,然后在整个写入。 对于 Raft 来说,Leader 可以一次收集多个 requests,然后一批发送给 Follower。当然,我们也需要有一个最大发送 size 来限制每次最多可以发送多少数据。

如果只是用 batch,Leader  还是需要等待 Follower 返回才能继续后面的流程,我们这里还可以使用 Pipeline 来进行加速。大家知道,Leader 会维护一个 NextIndex 的变量来表示下一个给 Follower 发送的 log 位置,通常情况下面,只要 Leader 跟 Follower 建立起了连接,我们都会认为网络是稳定互通的。所以当 Leader 给 Follower 发送了一批 log 之后,它可以直接更新 NextIndex,并且立刻发送后面的 log,不需要等待 Follower 的返回。如果网络出现了错误,或者 Follower 返回一些错误,Leader 就需要重新调整 NextIndex,然后重新发送 log 了。

Append Log Parallelly

对于上面提到的一次 request 简易 Raft 流程来说,我们可以将 2 和 3 并行处理,也就是 Leader 可以先并行的将 log 发送给 Followers,然后再将 log append。为什么可以这么做,主要是因为在 Raft 里面,如果一个 log 被大多数的节点 append,我们就可以认为这个 log 是被 committed 了,所以即使 Leader 再给 Follower 发送 log 之后,自己 append log 失败 panic 了,只要 `N / 2 + 1` 个 Follower 能接收到这个 log 并成功 append,我们仍然可以认为这个 log 是被 committed 了,被 committed 的 log 后续就一定能被成功 apply。

那为什么我们要这么做呢?主要是因为 append log 会涉及到落盘,有开销,所以我们完全可以在 Leader 落盘的同时让 Follower 也尽快的收到 log 并 append。

这里我们还需要注意,虽然 Leader 能在 append log 之前给 Follower 发 log,但是 Follower 却不能在 append log 之前告诉 Leader 已经成功 append 这个 log。如果 Follower 提前告诉 Leader 说已经成功 append,但实际后面 append log 的时候失败了,Leader 仍然会认为这个 log 是被 committed 了,这样系统就有丢失数据的风险了。

Asynchronous Apply

上面提到,当一个 log 被大部分节点 append 之后,我们就可以认为这个 log 被 committed 了,被 committed 的 log 在什么时候被 apply 都不会再影响数据的一致性。所以当一个 log 被 committed 之后,我们可以用另一个线程去异步的 apply 这个 log。

所以整个 Raft 流程就可以变成:

1. Leader 接受一个 client 发送的 request。

2. Leader 将对应的 log 发送给其他 follower 并本地 append。

3. Leader 继续接受其他 client 的 requests,持续进行步骤 2。

4. Leader 发现 log 已经被 committed,在另一个线程 apply。

5. Leader 异步 apply log 之后,返回结果给对应的 client。

使用 asychronous apply 的好处在于我们现在可以完全的并行处理 append log 和 apply log,虽然对于一个 client 来说,它的一次 request 仍然要走完完整的 Raft 流程,但对于多个 clients 来说,整体的并发和吞吐量是上去了。

Now Doing…

→ST Snapshot

在 Raft 里面,如果 Follower 落后 Leader 太多,Leader 就可能会给 Follower 直接发送 snapshot。在 TiKV,PD 也有时候会直接将一个 Raft Group 里面的一些副本调度到其他机器上面。上面这些都会涉及到 Snapshot 的处理。

在现在的实现中,一个 Snapshot 流程是这样的:

1. Leader scan 一个 region 的所有数据,生成一个 snapshot file。

2. Leader 发送 snapshot file 给 Follower。

3. Follower 接受到 snapshot file,读取,并且分批次的写入到 RocksDB。

如果一个节点上面同时有多个 Raft Group 的 Follower 在处理 snapshot file,RocksDB 的写入压力会非常的大,然后极易引起 RocksDB 因为 compaction 处理不过来导致的整体写入 slow 或者 stall。

幸运的是,RocksDB 提供了[SST]机制,我们可以直接生成一个 SST 的 snapshot file,然后 Follower 通过 injest 接口直接将 SST file load 进入 RocksDB。

→Asynchronous  Lease Read

在之前的 [Lease Read] TiKV 源码解析系列 - Lease Read 文章中,我提到过 TiKV 使用 ReadIndex 和 Lease Read 优化了 Raft Read 操作,但这两个操作现在仍然是在 Raft 自己线程里面处理的,也就是跟 Raft 的 append log 流程在一个线程。无论 append log 写入 RocksDB 有多么的快,这个流程仍然会 delay Lease Read 操作。

所以现阶段我们正在做的一个比较大的优化就是在另一个线程异步实现 Lease Read。也就是我们会将 Leader Lease 的判断移到另一个线程异步进行,Raft 这边的线程会定期的通过消息去更新 Lease,这样我们就能保证 Raft 的 write 流程不会影响到 read。

延展阅读:

TiKV 源码解析系列 - Lease Read

TiKV 源码解析系列 - PD Scheduler

TiKV 源码解析系列——Placement Driver

TiKV 源码解析系列——multi-raft 设计与实现

TiKV 源码解析系列——如何使用 Raft

TiKV 源码解析系列 - Raft 的优化相关推荐

  1. TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析

    作者:屈鹏 本文为 TiKV 源码解析系列的第二篇,按照计划首先将为大家介绍 TiKV 依赖的周边库 raft-rs .raft-rs 是 Raft 算法的 Rust 语言实现.Raft 是分布式领域 ...

  2. TiKV 源码解析系列 ——Placement Driver

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

  3. prometheus变量_TiKV 源码解析系列文章(四)Prometheus(下)

    本文为 TiKV 源码解析系列的第四篇,接上篇继续为大家介绍 rust-prometheus.上篇主要介绍了基础知识以及最基本的几个指标的内部工作机制,本篇会进一步介绍更多高级功能的实现原理. 与上篇 ...

  4. Redux 源码解析系列(一) -- Redux的实现思想

    文章来源: IMweb前端社区 黄qiong(imweb.io) IMweb团队正在招聘啦,简历发至jayccchen@tencent.com Redux 其实是用来帮我们管理状态的一个框架,它暴露给 ...

  5. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  6. openGauss数据库源码解析系列文章--openGauss简介(一)

    openGauss数据库是华为深度融合在数据库领域多年经验,结合企业级场景要求推出的新一代企业级开源数据库.此前,Gauss松鼠会已经发布了openGauss数据库核心技术系列文章,介绍了openGa ...

  7. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

  8. Mybatis3 源码解析系列

    简介 Mybatis作为一个优秀的Java持久化框架,在我们的日常工作中相信都会用到,本次源码解析系列,就开始探索下Mybatis 总结 在MyBatis的学习中,首先通读了<MyBatis3源 ...

  9. openGauss数据库源码解析系列文章——openGauss开发快速入门(二)

    在上一篇openGauss数据库源码解析系列文章--openGauss开发快速入门(上)中,我们介绍了openGauss的安装部署方法,本篇将具体介绍openGauss基本使用. 二. openGau ...

最新文章

  1. [C] [编程题]连通块(DFS解决)
  2. Java矩阵计算之EJML
  3. Meteor:发布与订阅
  4. 增量加载(Incremental Loads)
  5. iOS自定义View 控件自动计算size能力
  6. 29 个你必须知道的 Linux 命令
  7. JNI与底层调用-1
  8. [EULAR文摘] 在总人群中监测ACPA能否预测早期关节炎
  9. Maven3.0.5代理nexus
  10. Unity3d官方测试插件学习-单元测试,集成测试
  11. 《纽约时报》:乔布斯最后的日子 与家人相伴
  12. 飞腾腾锐 D2000 荣获数字中国“十大硬核科技”奖
  13. 为BIG网站建立历史记录页面
  14. 二级域名设置解析方法
  15. php音乐云盘,百度云盘音乐、文件、图片外链的三个方法总结
  16. 使用JDT ASTParser解析Java语句序列
  17. 使用ngrok 二 -- 微信公众号笔记---本地调试微信接口
  18. tophat以及cufflinks的使用
  19. 什么是Web应用防火墙
  20. matlab中图像显示函数

热门文章

  1. SQL*Plus命令
  2. kafka exporter v0.3.0 发布: Prometheus官方推荐,欢迎试用
  3. SUSE12系统安装及LVM设置详解
  4. 农业部部署农业大数据发展工作 评:对农业现代化很重要
  5. Understand Lambda Expressions in 3 minutes(翻译)
  6. 8大排序算法图文讲解转
  7. Linux下Chromium使用flash的办法
  8. silverlight之datagrid的一个问题
  9. log 的 debug()、 error()、 info()方法的区别
  10. 什么是商业智能(BI),以及其与数据分析的区别?