我们知道TiDB是一个典型的存算分离架构,它的核心模块主要包括计算引擎层TiDB Server、存储引擎层TiKV以及调度引擎层Placement Driver(简称PD)。在前面的系列文章中,我们重点介绍了TiDB Server以及TiKV的相关内容,本篇我们再一起了解一下PD模块。
TiDB作为一个分布式数据库,各个模块均具有高可用的特性。TiDB Server是一个无状态的计算引擎,可以任意扩展,本身不存储数据;TiKV是以Region为单位基于Raft组进行复制,一般至少为三个副本。PD模块至少由三个节点组成,它通过集成ETCD支持自动故障转移,因此它没有单点故障的风险;同时,PD中的数据也是基于ETCD内部的Raft机制保证了数据的多副本一致性,因此不用担心单点数据丢失的问题。在多个PD节点中,有一个PD节点为Leader节点。

了解了PD的高可用特性后,我们重点要了解PD这个模块到底有哪些能力。PD的能力可以概括为以下几点:
(1)整个集群TiKV元数据的存储。一个集群中包含多个TiKV节点,每个TiKV节点上又包含多个Region,每个TiKV节点也只知道自己节点上的Region状态,而不会知道其他节点上的Region状态。而PD则保存了整个集群的Region信息,包括Leader的位置、Region与TiKV的对应关系等。因此,当SQL语句要访问对应的Region时,必须要和PD交互以获取Region的位置。
(2)分配全局ID和事务ID。比如表ID、索引ID、事务ID等。
(3)生成全局时间戳TSO。事务的开始时间、结束时间,均是由PD来分配。
(4)收集集群信息进行调度。PD会及时收集各个TiKV节点及Region信息,以便了解当前的负载情况,如果发现负载不均,可以通过迁移Region来平衡负载。
(5)提供label,支持高可用。PD可以给Region打上相应的标签,从而可以将不同的Region设置不同的分布策略,比如放置在不同的数据中心从而可以实现多数据中心灾备部署方案。
(6)提供TiDB Dashboard。TiDB的Dashboard也是运行在PD节点上。
下面我们深入介绍几个PD的重要功能。
一. PD的路由功能
在了解TiDB中的DML/DDL执行流程
(qq.com) 一文中,我们知道在读取过程中Executor会将执行的请求发给TiKV Client,如果TiKV Client本地缓存中有Region位置信息则直接从缓存中读取并发送到对应的TiKV节点,如果没有缓存信息则需要向PD请求Region位置信息。缓存可以减少每次都需要和PD交互产生的网络消耗,当缓存的信息与实际的位置信息不一致时,TiKV Client会重新向PD请求最新的数据并更新本地缓存,这在TiDB中称为“Back Off”,过多的Back Off会影响查询的性能。

二. PD的TSO分配流程
由PD生成的TSO是一个64位整型数值,它由物理时间+逻辑时间组成。物理时间是机器的Unix时间,精确到毫秒;逻辑时间用于将1毫秒划分为262144个时间单位。

关于TSO的分配过程如上图所示。首先,由TiDB Server中的TSO请求者(比如一个事务)发起请求TSO,这个请求不是直接发给PD节点,而是交由PD Client来处理。PD Client会立即给TSO请求者返回一个tsFuture对象,它标识PD Client什么时候请求了TSO。当TSO请求者接收到tsFuture返回对象时,开始进行SQL语句的解析编译流程(由于SQL语句的解析编译过程是不需要TSO的,因此这个过程可以立即执行而不用等待TSO返回),与此同时,PD Client也正在异步的与PD Leader交互并获取PD Leader分配的TSO。如果语句的解析编译先完成,则TSO请求者会先调用tsFuture.wait等待PD Client返回分配TSO;如果PD
Client先完成获取分配TSO,则会等待TSO请求者解析编译完成并发出tsFuture.wait。当两边异步进行的动作均完成后,也就意味着TSO请求者获得TSO成功。
注意,由于集群中通常会包含多个TiDB
Server,每个TiDB Server同时又会有多个并发会话,如果每个TSO请求者向PD Client请求TSO时PD Client都要和PD交互,即使是异步请求分配TSO,那么性能也一定是会有影响的。因此,PD Client中做了批处理的优化,比如将5毫秒内的TSO请求合并为一个批次统一向PD发送,一次获取一个批量的TSO。
在PD组中,为了保证Leader故障切换时TSO能够连续递增,PD节点必须要将当前分配的TSO持久化而且要复制到Follower节点。但如果PD每次都是分配一个TSO就持久化一个TSO的方式,那么势必会带来大量的网络及磁盘IO压力。为了解决这个问题,PD针对TSO分配引入了“时间窗口”的优化,具体来说就是PD每次分配一个时间窗口(比如3秒)的TSO,如700~703三秒钟的TSO。通过这个优化,磁盘IO优化为3秒钟产生一次。不过,这个优化会带来TSO空洞的可能,因为假如当前消费到702时刻的TSO,但Leader故障切换到TSO范围会变为704~706,这就导致702~704之间的TSO空洞,不过这对业务也不会有任何的影响。

三. PD的调度功能
PD调度功能的第一步是要收集信息,信息的收集是每个TiKV通过心跳周期性的向PD汇报各自的状态实现。心跳信息分为两类:Store心跳和Region 心跳。Store心跳是TiKV自己的信息,包括当前的容量、剩余空间以及读写流量等,这样就知道当前哪个TiKV比较忙。Region心跳是每个Region向PD汇报自己的信息,包括副本的分布状态、数据量以及读写流量等,这样就知道哪个Region比较忙,同时也知道Region在TiKV上的分布是否均匀。

收集完信息后,PD调度的第二步是生成调度(operator)。生成调度的关注点包括以下部分:
(1)Region均衡。包括两种,一种是Leader均衡(Leader均衡意味着每个节点的读写是否均衡),另一种是Region均衡(主要是存储的均衡,避免个别TiKV数据量太大)。
(2)热点Region。是否存在某些Region有读写热点,针对读写热点的Region,可以将其打散到不同节点,分摊热点压力。
(3)Region合并。当对表进行drop、delete或truncate之类操作后,Region需要进行相应的合并,以减少PD维护的压力。
PD调度的最后一步就是执行调度,即将前面生成的operator下发给Region,让Region进行相应的动作,以实现Region的均衡、打散或合并等操作。
四. PD的Label功能
假设一个集群有三个数据中心DC1~DC3,每个数据中心有两个机架Rack,每个机架中有两个节点。默认情况下,PD只能保证同一个TiKV节点中不会有同一个Region的两个副本,但无法保证一个Region的不同副本分布在不同的数据中心或不同的机架上。如果想实现三个数据中心中有一个数据中心故障后集群仍然可用,那么就必须要保证每个Region的每个副本要分别放置于不同的DC中(如下面的Region3),这就是Label标签的作用。

Label的主要目的就是为了让每个TiKV感知到集群的拓扑结构。Label的配置需要在两个组件上完成,分别是TiKV和PD。TiKV实例上需要配置server.labels,包含三个级别:zone、rack和host,这样就将TiKV和拓扑结构关联起来。PD上需要配置location-labels,表示PD知道集群有几层隔离级别,还要配置isolation-level,表示选取哪种隔离方式,如果设置为“zone”,表示副本必须按zone分布放置。





