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

在分布式领域,为了保证数据的一致性,通常都会使用 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。

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

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。

总结

虽然外面有声音说 Raft 性能不好,但既然我们选择了 Raft,所以就需要对它持续的进行优化。而且现阶段看起来,成果还是很不错的。相比于 RC1,最近发布的 RC2 无论在读写性能上面,性能都有了极大的提升。但我们知道,后面还有很多困难和挑战在等着我们,同时我们也急需在性能优化上面有经验的大牛过来帮助我们一起改进。

如果你对我们做的东西感兴趣,想让 Raft 快的飞起,欢迎联系我们:邮箱:info@pingcap.com,当然,你也可以添加本期作者唐刘的个人微信:siddontang

延展阅读:

TiKV 源码解析系列 - Lease Read

TiKV 源码解析系列 - PD Scheduler

TiKV 源码解析系列——Placement Driver

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

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

TiKV 源码解析系列 - Raft 的优化的更多相关文章

  1. TiKV 源码解析系列文章(三)Prometheus(上)

    本文为 TiKV 源码解析系列的第三篇,继续为大家介绍 TiKV 依赖的周边库 rust-prometheus,本篇主要介绍基础知识以及最基本的几个指标的内部工作机制,下篇会介绍一些高级功能的实现原理 ...

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

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

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

    https://zhuanlan.zhihu.com/p/24809131?refer=newsql

  4. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. Cwinux源码解析系列

      Cwinux源码解析系列

  8. 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析

    通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...

  9. Android源码解析系列

    转载请标明出处:一片枫叶的专栏 知乎上看了一篇非常不错的博文:有没有必要阅读Android源码 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入Android体系,决定学习an ...

随机推荐

  1. JS组件系列——表格组件神器:bootstrap table(三:终结篇,最后的干货福利)

    前言:前面介绍了两篇关于bootstrap table的基础用法,这章我们继续来看看它比较常用的一些功能,来个终结篇吧,毛爷爷告诉我们做事要有始有终~~bootstrap table这东西要想所有功能 ...

  2. 二、Java基础--02

    作为本人首篇黑马技术博客有必要交代一下背景.个人理解博客的用作在于于己在于交流,于他在于学习,在交流学习中共同成长.下面进入正题.本文主要是介绍在做黑马入门测试时的一些问题(这个应该不是泄露题库吧). ...

  3. Windows环境下Redis

    Redis 是一个高性能的key-value数据库, 使用内存作为主存储,数据访问速度非常快,当然它也提供了两种机制支持数据持久化存储.比较遗憾的是,Redis项目不直接支持Windows,Windo ...

  4. Protocol Buffer详解

    1.Protocol Buffer 概念 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 ...

  5. 力控ADO组件数据源设置

    1.mysql的ODBC驱动如何下载及安装 地址:http://dev.mysql.com/downloads/connector/odbc/5.1.html Mysql跟力控ado进行交互 第一步: ...

  6. LeetCode解题报告:LRU Cache

    LRU Cache Design and implement a data structure for Least Recently Used (LRU) cache. It should suppo ...

  7. Android开发(19)---常见dialog对话框的运用

    Dialog是android开发过程中最常用到的组件之一,Dialog的创立办法有两种: 一是直接new一个Dialog目标,然后调用Dialog目标的show和dismiss办法来操控对话框的显现和 ...

  8. Windows核心编程:第12章 纤程

    Github https://github.com/gongluck/Windows-Core-Program.git //第12章 纤程.cpp: 定义应用程序的入口点. // #include & ...

  9. HDU 4818 RP problem (高斯消元, 2013年长春区域赛F题)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4818 深深地补一个坑~~~ 现场赛坑在这题了,TAT.... 今天把代码改了下,过掉了,TAT 很明显 ...

  10. Java 类引入 学习记录规整

    之前觉得声明一个类,再把另一个包内的声明数值用第一个类打印出来就可以了(加入引入包类) 结果发现是不对的 看了看demo  得出正确结果    ImportTest 被运行 引入下面的Import类 ...