聊这个话题之前,让我们先来回顾一下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是一个存算分离的架构,针对读写分离的实现,有两种思路。
一种是计算引擎的读写分离。由于每个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_read为learner来将只读业务转发到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则更适合均衡一部分读负载的场景。