TiDB 新特性漫谈:从 Follower Read 说起

2024-04-08 02:58

本文主要是介绍TiDB 新特性漫谈:从 Follower Read 说起,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者:黄东旭

很久没有写文章了,正好今天有一些闲暇的时间,写写最近的一些 Update。关注 TiDB 的同学,最近可能注意到 TiKV 这边合并了一个不大不小的 PR #5051 ,支持了一个特性叫做 Follower Read,看到这个功能被合并进主干我确实有点百感交集,还发了条朋友圈庆祝,因为我实在很喜欢这个特性,可能有同学不太理解,今天就写一写和这个 PR 相关的一些事情。

大家知道,TiDB 的存储层 TiKV 使用的是 Multi-Raft 的架构:

image

数据在 TiKV 内部按照一个个名为 Region 的逻辑概念切分,每一个 Region 是一个独立的 Raft 复制小组,默认状态下是 3 个副本,多个 Region 自动的动态分裂,合并,移动,在整个集群内部尽可能均匀分布。使用 Raft 主要是为了实现高可用(数据冗余),但是对于 Raft 比较熟悉的朋友一定知道标准的 Raft 是一个有 Strong Leader 的,读写流量都会经过 Leader。细心的朋友这个时候可能发现问题了,虽然 TiKV 能够很均匀的将 Region 分散在各个节点上,但是对于每一个 Region 来说,只有 Leader 副本能够对外提供服务,另外两个 Follower 除了时刻同步数据,准备着 Failover 时候投票切换成 Leader 外,并没有干其他的活。

只有 Region Leader 在干活,其他 Followers 冷眼旁观?

所以有些时候用户会注意到,对于一些热点数据,可能会将这块数据的 Region Leader 所在的机器的资源打满,虽然此时可以强行 Split,然后移动数据到其他机器上,但是这个操作总是滞后的,另外 Follower 的计算资源没有用上也比较可惜。

所以优化就很直接了:能不能在 Follower 上也处理客户端的读请求呢,这样不就分担了 Leader 的压力了吗?这个就是 Follower Read 了。

ReadIndex

对于熟悉 Raft 的同学来说,沿着这个方向往下想,下一个问题一定就是:如何保证在 Follower 上读到最新的数据呢?如果只是无脑的将 Follower 上最近的 Committed Index 上的数据返回给客户端可以吗?答案是不行的(这里留个悬念,后面会再返回来讨论这个问题),原因显而易见,Raft 是一个 Quorum-based 的算法,一条 log 的写入成功,并不需要所有的 peers 都写入成功,只需要多数派同意就够了,所以有可能某个 Follower 上的本地数据还是老数据,这样一来就破坏线性一致性了。

其实在 trivial 的 Raft 实现中,即使所有的 Workload 都走 Leader,也仍然在一些极端场景下会出现上面提到的问题。举个例子,当出现网络隔离,原来的 Leader 被隔离在了少数派这边,多数派那边选举出了新的 Leader,但是老的 Leader 并没有感知,在任期内他可能会给客户端返回老的数据。

但是对于每次读请求都走一次 Quorum Read 虽然能解决问题,但是有点太重了,能不能做得更高效点?根本问题其实就在于老的 Leader 不确定自己是不是最新的 Leader,所以优化也很直接,只要想办法让 Leader 在处理读请求时确认自己是 Leader 就好了,这个就是所谓的 ReadIndex 算法。简单来说,就是在处理读请求的时候记录当前 Leader 的最新 Commit index,然后通过一次给 Quorum 的心跳确保自己仍然是 Leader,确认后返回这条记录就好,这样就能保证不破坏线性一致性。尽管 ReadIndex 仍然需要进行一次多数派的网络通信,但是这些通信只是传输元信息,能极大减少网络 IO,进而提升吞吐。

在 TiKV 这边比标准的 ReadIndex 更进一步,实现了 LeaseRead。其实 LeaseRead 的思想也很好理解,只需要保证 Leader 的租约比重选新的 Leader 的 Election Timeout 短就行,这里就不展开了。

Follower Read

说到今天的主角,Follower Read,如何保证 Follower 上读到最新的数据呢?最土的办法就是将请求转发给 Leader,然后 Leader 返回最新的 Committed 的数据就好了嘛,Follower 当做 Proxy 来用。这个思路没有任何问题,而且实现起来也很简单还安全。但是,很明显这个地方可以优化成:Leader 只要告诉 Follower 当前最新的 Commit Index 就够了,因为无论如何,即使这个 Follower 本地没有这条日志,最终这条日志迟早都会在本地 Apply。

TiDB 目前的 Follower Read 正是如此实现的,当客户端对一个 Follower 发起读请求的时候,这个 Follower 会请求此时 Leader 的 Commit Index,拿到 Leader 的最新的 Commit Index 后,等本地 Apply 到 Leader 最新的 Commit Index 后,然后将这条数据返回给客户端,非常简洁。

但是这个方案可能会引入两个问题:

  1. 因为 TiKV 的异步 Apply 机制,可能会出现一个比较诡异的情况:破坏线性一致性,本质原因是由于 Leader 虽然告诉了 Follower 最新的 Commit Index,但是 Leader 对这条 Log 的 Apply 是异步进行的,在 Follower 那边可能在 Leader Apply 前已经将这条记录 Apply 了,这样在 Follower 上就能读到这条记录,但是在 Leader 上可能过一会才能读取到。
  2. 这种 Follower Read 的实现方式仍然会有一次到 Leader 请求 Commit Index 的 RPC,所以目前的 Follower read 实现在降低延迟上不会有太多的效果。

对于第一点,虽然确实不满足线性一致性了,但是好在是永远返回最新的数据,另外我们也证明了这种情况并不会破坏我们的事务隔离级别(Snapshot Isolation),证明的过程在这里就不展开了,有兴趣的读者可以自己想想。

对于第二个问题,虽然对于延迟来说,不会有太多的提升,但是对于提升读的吞吐,减轻 Leader 的负担还是很有帮助的。总体来说是一个很好的优化。

未来?

如果只是一个简单的性能优化的话,我其实也没有太多兴趣单独为它写一个 Blog,虽然简单,但是 Follower Read 确实是一个对未来很重要的功能。

我们经常被问到的一个问题是:如果我在一个表上跑一个大查询,会不会影响正在进行的 OLTP 事务?虽然我们在 TiKV 里面内置了一个 IO 优先级队列,会优先处理重要的 OLTP 请求,但是仍然还是消耗了 Leader 所在机器的资源,甚至更极端一点的例子,有一个热点小表,读远大于写,尽管对于热数据来说,肯定 Cache 在内存里面了,但是在一些极端热的情况下仍然会出现 CPU 瓶颈,网络 IO 瓶颈。

熟悉 TiDB 架构的朋友一定知道,从一开始调度模块 PD 就是一个独立的组件,目前的调度还仅限于 Region 的分裂、合并、移动,Leader transfer 之类,但是能做的肯定不止于此,TiDB 很快就会做的事情是,针对不同热度的数据,动态采用不同的副本策略。举个例子,如果发现一张小表巨热,PD 可以快速让 TiKV 对这块数据动态创建多个只读副本(大于 3),通过 Follower Read 来分摊 Leader 的压力,当压力下来后,再销毁这些副本,因为 TiKV 中每个 Region 足够小(默认 96MB) 所以 TiDB 做这个事情的时候可以非常灵活和轻量,这个功能和 Kubernetes 结合在云端上能非常有想象力

另外一个很重要的功能也需要 Follower Read 作为基础,就是 Geo-Replication 后的 Local Read。现在 TiDB 即使跨数据中心部署,虽然 TiDB 会将副本分散在各个数据中心,但是对于每块数据仍然是 Leader 提供服务,这就意味着,业务需要尽可能的接近 Leader,所以我们经常会推荐用户将应用程序部署在一个数据中心,然后告诉 PD 将 Leaders 都集中在这个数据中心以加速读写请求,Raft 只用来做跨数据中心高可用。

但是对于部分的读请求,如果能就近读,总是能极大的降低延迟,提升吞吐。但是细心的朋友肯定能注意到,目前这个 Follower Read 对于降低延迟来说,并不明显,因为仍然要去 Leader 那边通信一下。不过仍然是有办法的,还记得上面留给大家的悬念嘛?能不能不问 Leader 就返回本地的 committed log?其实有些情况下是可以的。大家知道 TiDB 是基于 MVCC 的,每条记录都会一个全局唯一单调递增的版本号,下一步 Follower Read 会和数据本身的 MVCC 结合起来,如果客户端这边发起的事务的版本号,本地最新的提交日志中的数据的版本大于这个版本,那么其实是可以安全的直接返回,不会破坏 ACID 的语义。另外对于一些对一致性要求不高的场景,未来直接支持低隔离级别的读,也未尝不可。到那时候,TiDB 的跨数据中心的性能将会又有一个飞跃。

所以,Follower Read 是让上面这些吸引人的特性变为现实的第一步,我们仍然秉承着先做稳再做快的原则,一步步来,有兴趣的朋友自己也可以测试起来,也希望更多小伙伴能参与相关特性的贡献。

这篇关于TiDB 新特性漫谈:从 Follower Read 说起的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/884387

相关文章

五大特性引领创新! 深度操作系统 deepin 25 Preview预览版发布

《五大特性引领创新!深度操作系统deepin25Preview预览版发布》今日,深度操作系统正式推出deepin25Preview版本,该版本集成了五大核心特性:磐石系统、全新DDE、Tr... 深度操作系统今日发布了 deepin 25 Preview,新版本囊括五大特性:磐石系统、全新 DDE、Tree

ActiveMQ—消息特性(延迟和定时消息投递)

ActiveMQ消息特性:延迟和定时消息投递(Delay and Schedule Message Delivery) 转自:http://blog.csdn.net/kimmking/article/details/8443872 有时候我们不希望消息马上被broker投递出去,而是想要消息60秒以后发给消费者,或者我们想让消息没隔一定时间投递一次,一共投递指定的次数。。。 类似

PostgreSQL核心功能特性与使用领域及场景分析

PostgreSQL有什么优点? 开源和免费 PostgreSQL是一个开源的数据库管理系统,可以免费使用和修改。这降低了企业的成本,并为开发者提供了一个活跃的社区和丰富的资源。 高度兼容 PostgreSQL支持多种操作系统(如Linux、Windows、macOS等)和编程语言(如C、C++、Java、Python、Ruby等),并提供了多种接口(如JDBC、ODBC、ADO.NET等

详解Tomcat 7的七大新特性和新增功能(1)

http://developer.51cto.com/art/201009/228537.htm http://tomcat.apache.org/tomcat-7.0-doc/index.html  Apache发布首个Tomcat 7版本已经发布了有一段时间了,Tomcat 7引入了许多新功能,并对现有功能进行了增强。很多文章列出了Tomcat 7的新功能,但大多数并没有详细解释它们

如何掌握面向对象编程的四大特性、Lambda 表达式及 I/O 流:全面指南

这里写目录标题 OOP语言的四大特性lambda输入/输出流(I/O流) OOP语言的四大特性 面向对象编程(OOP)是一种编程范式,它通过使用“对象”来组织代码。OOP 的四大特性是封装、继承、多态和抽象。这些特性帮助程序员更好地管理复杂的代码,使程序更易于理解和维护。 类-》实体的抽象类型 实体(属性,行为) -》 ADT(abstract data type) 属性-》成

vue 父组件调用子组件的方法报错,“TypeError: Cannot read property ‘subDialogRef‘ of undefined“

vue 父组件调用子组件的方法报错,“TypeError: Cannot read property ‘subDialogRef’ of undefined” 最近用vue做的一个界面,引入了一个子组件,在父组件中调用子组件的方法时,报错提示: [Vue warn]: Error in v-on handler: “TypeError: Cannot read property ‘methods

《C++标准库》读书笔记/第一天(C++新特性(1))

C++11新特性(1) 以auto完成类型自动推导 auto i=42; //以auto声明的变量,其类型会根据其初值被自动推倒出来,因此一定需要一个初始化操作; static auto a=0.19;//可以用额外限定符修饰 vector<string> v;  auto pos=v.begin();//如果类型很长或类型表达式复杂 auto很有用; auto l=[] (int

12C 新特性,MOVE DATAFILE 在线移动 包括system, 附带改名 NID ,cdb_data_files视图坏了

ALTER DATABASE MOVE DATAFILE  可以改名 可以move file,全部一个命令。 resue 可以重用,keep好像不生效!!! system照移动不误-------- SQL> select file_name, status, online_status from dba_data_files where tablespace_name='SYSTEM'

漫谈设计模式 [12]:模板方法模式

引导性开场 菜鸟:老大,我最近在做一个项目,遇到了点麻烦。我们有很多相似的操作流程,但每个流程的细节又有些不同。我写了很多重复的代码,感觉很乱。你有啥好办法吗? 老鸟:嗯,听起来你遇到了典型的代码复用和维护问题。你有没有听说过“模板方法模式”? 菜鸟:模板方法模式?没听过。这是什么? 老鸟:简单来说,模板方法模式让你在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中。这样,你可

漫谈设计模式 [9]:外观模式

引导性开场 菜鸟:老鸟,我最近在做一个项目,感觉代码越来越复杂,我都快看不懂了。尤其是有好几个子系统,它们之间的调用关系让我头疼。 老鸟:复杂的代码确实让人头疼。你有没有考虑过使用设计模式来简化你的代码结构? 菜鸟:设计模式?我听说过一些,但不太了解。你觉得我应该用哪个模式呢? 老鸟:听起来你的问题可能适合用**外观模式(Facade Pattern)**来解决。我们可以一起探讨一下。