在前面《TiDB架构》一节中介绍过,TiDB 由三个组件组成,分别是 TiDB Server、TiKV Server 和 PD Server。
TiKV 将关系模型中的表格数据转换成 Key-Value 的形式存储在磁盘中,TiDB 则负责完成客户端 SQL 请求的解析和执行。这两个组件已经完成了数据的存储和读写查询,基本实现了一个单机的数据库系统。
但是,TiDB 系统是在大数据场景下诞生的,它的设计初衷是处理分布式的数据库,并具有高可用性和无限的水平扩展能力。那么,这种分布式集群里事务的调度和数据的容灾是如何实现的?
这是由另一个组件 PD Server(后面以 PD 来简称这个组件)来完成的。PD 自身是一个集群, 由多个 Server 组成,有一套选举的策略,由一个 Leader 对外提供服务。
当 PD 崩溃时,系统会重新选举其他节点作为 Leader 来提供服务,无须担心 PD 节点的失效。
下面介绍 PD 作为全局中心总控节点是如何来管理分布式数据库系统的。
既然要管理整个分布式数据库系统,就必须收集足够的信息,例如,节点状态、Region 的副本数及位置信息、Raft Group 的信息等。
PD 可从 TiKV 的心跳信息中获取这些相关的信息,这里有两类心跳信息,一类是 TiKV 节点与 PD 之间的心跳包,另一类是 Raft Group 里的 Region Leader 上报的心跳包。
PD 通过 TiKV 节点上报的心跳包来检测每个节点是否存活,以及是否有新的节点加入,PD 对新加入的节点生成全局 ID。同时此心跳包中还包含节点的元数据信息,主要包括:
Region Leader 上报的心跳信息则包括了这个 Region 的元数据信息,主要包括:
PD 通过这两种心跳信息获取这个集群的信息,就可以对集群进行调度了。
例如,获取 Region 的位置信息后,TiDB Server 在收到 Client 请求时,解析 SQL 语句,找出 Key 的范围,会向 PD 请求获取 Key 所在 Region 的节点,再与具体 TiKV 节点交互式地读取数据。
在 TiKV 中,每个 Raft Group 的 Leader 会定期检查 Region 所占用的空间是否超过某个域值。例如,Region 默认的域值为 64MB,如果一个 Region 超过了 64MB,就需要对 Region 进行拆分。
首先,Leader 会向 PD 发送一个分裂的请求,PD 收到请求信息后,会生产一个新的 RegionlD,并返回给 Leader;
然后,Leader 将此信息写入 raft log 中,TiKV 根据日志信息对 Region 进行分裂,一个 Region 分裂成两个,其中一个继承原 Region 的所有元信息,另一个的元信息则由 PD 生成,如 RegionlD 等;
最后分裂成功后,TiKV 会告诉 PD 这两个 Region 的相关信息,由 PD 来更新 Region 的元信息,包括分裂后的 Region 的位置、副本等。
在 TiKV 中,数据的读取和写入都是通过 Leader 进行的,所有的计算负载都在 Leader 上。如果多个 Raft Group 的 Leader 都在同一个节点上,则要对这些 Leader 进行移动,使其均匀分布在不同的节点上。
同时在实际应用中,通常会出现热点访问的数据,即这种数据被频繁访问,这也会造成当前节点负荷过重。
PD 根据数据的写入和读取速度,检测哪些 Region 是访问热点,然后将这些热点 Region 分散在不同节点上,防止出现一个节点被频繁访问,而其他节点处于空闲状态的情况。
另外,PD 根据节点的总磁盘容量和可用磁盘容量来调度 Region 的存放位置,保证每个节点占用的存储空间大致相等。
同时,根据心跳信息判断当前节点是否失效,如果在一定时间内都没有收到此节点的心跳包,则认为此节点已经下线,这时就需要将此节点上的 Region 都调度到其他节点上。
PD 通过不断地收集节点和 Leader 的心跳信息,获取整个集群的状态,并根据这些信息对集群的操作进行调度。
每次 PD 收到 Region Leader 发来的心跳包,都会检测是否需要对此 Region 进行操作,如 Region 分裂、Region 移动等。
PD 将需要进行的操作通过心跳包的回复信息返回给 Region Leader。这些操作由 Region Leader 根据当前状态来决定是否执行此操作,执行的结果通过后续的心跳包信息发送给 PD,PD 再来更新整个集群的状态。