写点什么

聊聊 TiDB 里面如何实现读写分离

  • 2024-01-19
    北京
  • 本文字数:3134 字

    阅读完需:约 10 分钟

作者: 数据源的 TiDB 学习之路原文来源:https://tidb.net/blog/5d80603f


聊这个话题之前,让我们先来回顾一下 TiDB 的整体架构。如下图所示,TiDB 的整体架构包括三大模块:PD Cluster、TiDB Cluster 以及 Storage Cluster。PD Cluster 是整个集群的大脑,主要负责集群元数据存储、生成全局唯一 ID、全局时间戳 TSO、负载均衡调度。TiDB Server 是计算引擎层,主要负责接收客户端传递的 SQL 语句进行解析、编译和执行,将 SQL 语句转为对底层存储引擎的键值对处理,同时它里面还包括缓存功能、垃圾回收、在线 DDL 能力。Storage Cluster 是存储引擎层,它包括行存引擎 TiKV 以及列存引擎 TiFlash,其中 TiKV 主要面向 OLTP 交易型场景,而 TiFlash 则主要适用于大数据量的分析型场景。



由于 TiDB 是一个存算分离的架构,针对读写分离的实现,有两种思路。


一种是计算引擎的读写分离。由于每个 TiDB Server 是完全对等的,都能够接收客户端 SQL 请求并执行,当前在数据库端并没有一种方式可以指定哪些 TiDB Server 只处理写请求哪些 TiDB Server 只处理读请求。因此需要依赖上层应用来实现读写流量转发,比如把写流量分配给某几个 TiDB Server 而把读流量转发给其余的 TiDB Server,通过这种方式来实现读和写在计算资源上的分离。如果 TiDB 在后续版本中能够增加一层 PROXY 代理,倒是可以通过代理层来实现计算资源的读写分离。


另一种是存储引擎的读写分离。存储引擎的读写分离可以分为 2 类场景:



1.只有 TiKV 存储引擎。当只有 TiKV 存储时,底层的数据是以 Region 为单位构成的 Multi raft 组。每个 Raft 组由至少 3 个 Region 副本构成,一个副本为 Leader 其余为 Follower。默认情况下,每个 Raft 组中只有 Leader 副本可以接收读写请求,其余副本只能被动接收 Leader 同步过来的 Raft 日志,不直接对外服务。这意味着,在默认情况下,只有 1/3 的 Region 副本(Leader 副本)是对外提供读写服务的,这些 Leader 副本在 PD 的调度下均匀分布在不同的 TiKV 节点。


针对 TiKV 的读写分离,TiDB 的主要思路是让 Follower 副本承担部分或全部读操作,即 Follower read,参考 Follower Read | PingCAP 文档中心。如果相同的 TiKV 节点上既有 Leader 也有 Follower,这种方式做不到物理上的读写分离但能降低热点 Region 的负载压力。如果要实现物理上的读写分离,可以借助 Placement Rules 功能将 Leader 副本和 Follower 副本放置在不同的物理节点上实现只读存储节点,参考 ****只读存储节点最佳实践 | PingCAP 文档中心


2. 既有 TiKV 也有 TiFlash 存储引擎。目前所有的 TiDB 版本中 TiFlash 均提供只读服务,业务无法直连 TiFlash 进行写操作。TiFlash 的数据只能作为 Raft Learner 角色异步从 TiKV 传输而来。TiDB 目前支持 SQL 引擎层根据优化器评估自动选择读 TiKV 还是 TiFlash,也支持通过配置指定读取 TiKV 还是 TiFlash。在 HTAP 实际业务场景中 TiKV 和 TiFlash 一般会选择分开部署,因此通过这种方式也可以实现读写分离,即写操作和部分读操作在 TiKV 节点,部分读操作在 TiFlash 节点,参考 使用 TiDB 读取 TiFlash | PingCAP 文档中心


下面我们便用实际的示例来说明实现 TiDB 中读写分离的几种方式。


一. 跟随者读(Follower Read)


默认情况下,TiKV 中只有每个 Region 的 Leader 副本接受读写请求,这样可能会引发 Leader 的读写热点造成性能瓶颈。TiDB 从 3.x 版本中引入变量 tidb_replica_read,允许在 SESSION 或 GLOBAL 级别设置数据读取方式。这个变量允许的值包括:


(1)leader。当设置为 leader 或空时,TiDB 将所有读操作都发给 Leader 副本(默认行为)。


(2)follower。TiDB 将所有读操作发给 Follower 副本。


(3)leader-and-follower。选择任意副本执行读操作,读会在 Leader 和 Follower 间均衡。


(4)prefer-leader。优先选择 Leader 进行读,当 Leader 处理明显变慢时选择可用的 Follower 执行读操作。


(5)closest-replicas。优先选择同一个可用区的副本读(leader 和 follower 均可),如果同一可用区没有副本,则从 Leader 读。


(6)closest-adaptive。基于请求的预估返回结果量决定读哪个副本。


(7)learner。优先选择 learner 副本读,如果没有 learner 副本则报错。


二. 只读存储节点


只读存储节点实际上也利用了 Follower Read 的功能,它通过设置 tidb_replica_read=learner 来把读请求转发到 learner 副本上面。在此之前,我们需要先配置只读节点,具体来说就是把部分 TiKV 节点上的副本设置为 learner 副本。


要达到此目的,首先需要选择将哪个 TiKV 节点作为只读节点,然后使用 tiup cluster edit-config 添加以下配置指定只读节点



下一步我们需要利用 Placement-rules 功能将匹配 $mode=readonly 这个 label 的 TiKV 节点配置为 learner 角色,从而让 PD 能够调度将此节点上的副本设置为 learner 副本。具体操作步骤就是先使用 config placement-rules rule-bundle get pd –out=rules.json 导出规则并修改增加以下规则,然后再使用 config placement-rules rule-bundle set pd –in=rules.json 导入规则。



到此我们已经设置好只读节点并通过调度将此节点上的副本设置为 learner 副本,下面我们需要做的就是利用 follower read 功能将读请求转发到 learner 副本。我们先通过模拟 sysbench 中 oltp_read_only 业务来观察当前业务负载情况。




如上图所示,由于集群中有两个节点上面存放了角色为 voter 的 3 个副本,Leader 相对均匀的被分配到这两个节点,因为 CPU 负载主要在这两个节点上。而另外一个节点是 Learner 副本,默认不接收读请求,因此 CPU 负载几乎为零。


现在,我们通过设置 tidb_replica_readlearner 来将只读业务转发到 learner 副本上。




上述两个截图中第一张代表设置全局 tidb_replica_read 值为 learner,第二张图是在一个新的 session 中查看 tidb_replica_read 已经修改为 learner。设置之后查看各节点的 Leader 及 CPU 负载情况,我们发现 Leader 未发生任何的变化,但 CPU 负载很明显从原来的两个节点切换到只读节点。




三. 读取 TiKV 还是 TiFlash


如果集群中添加了 TiFlash,TiDB Server 的引擎可以根据 SQL 语句的执行计划成本评估来选择查询 TiKV 还是 TiFlash,在大多数情况下,我们不需要去手动干预,只需要从执行计划中检查走的哪个存储引擎即可,这可以通过 explain <SQL> 来实现。



在读写分离的场景下,我们可能需要强制指定读请求都转发到 TiFlash,这就需要通过配置变量的方式来实现,这在 TiDB 中称为 Engine 隔离。Engine 隔离有两个隔离级别:实例 (instance) 级别会话 (session) 级别


(1)实例级别。需要在具体的 TiDB Server 配置文件中添加以下配置并重启实例。


[isolation-read]engines = ["tiflash"]
复制代码


比如通过 mysql 连接到某节点 TiDB Server 后查看语句的执行计划走 cop[tikv],如下图所示:



在 tidb.toml 中添加以上配置后并单独重启这个 TiDB Server 节点后,重新使用 mysql 客户端连接相同节点,再次查看执行计划如下图所示,可以看出执行计划已经发生了变化,证明通过修改实例配置文件并重启实例的方式可以将读转发到 TiFlash 节点。



(2)会话级别。通过 set @@session.tidb_isolation_read_engines = “tiflash”;


仍然以上述 SQL 为例,在 session 中默认从 TiKV 读取数据,当修改 session 中的变量后可以查看到执行计划修改为从 TiFlash 读取。



本文简单描述了在 TiDB 中实现读写分离的几种方法,包括 Follower Read、配置只读节点及通过 TiFlash 存储引擎来分离读请求。不过从上述描述中也可以看出,如果要真正做到物理上的读写分离,最适合的方式就是增加一个 learner 副本的只读节点,Follower Read 和 TiFlash 则更适合均衡一部分读负载的场景。


发布于: 刚刚阅读数: 4
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
聊聊TiDB里面如何实现读写分离_实践案例_TiDB 社区干货传送门_InfoQ写作社区