MULTI-RAFT-GROUP kv 小记

参考资料:https://www.sofastack.tech/blog/sofa-jraft-rheakv-multi-raft-group/

Single-Raft模式


既然我们有multi-raft模式,那也就是说还有对应的single-raft模式。这里说的Single-raft模式其实是最早RaftServer实现的模式,一个节点上面只启动一个RaftServer实例,它的正常server state无非两种Leader或者Follower(假设不考虑投票选举阶段)。

对应一个单节点单RaftServer实例而言,它在本地节点上以及服务自身会存有以下一些必要的信息:

  • StateMachine,内部状态机。
  • RaftLog,持久化在本年底的transaction log。
  • 还有Snapshot数据

其中RaftLog和Snapshot的数据是落在本地节点盘之上的。

下图是RaftServer内部服务的一个简要模型图:
MULTI-RAFT-GROUP kv 小记
上面括号中的F为Follower,SM为StateMachine的意思。对Lead/Follower RaftServer之间交互通信感兴趣的同学可阅读笔者之前写的相关文章:Apache Ratis的Ratis Server主从同步机制

那么问题来了,在multi-raft模式下,上图中这个结构会变成什么样呢?

Multi-raft改进


Apache Ratis社区在JIRA RATIS-91: Add multi-raft support实现了这个功能。在这个功能下,它主要做了这么几个事情:

  • 新增接口允许client端向service端添加新的raft group,一个新的raft group可理解为一组新的Leader/Follower RaftServer服务。
  • 分离出RaftServerProxy和RaftServer服务,一个节点可以启动多个RaftServer实例,按照raft group id区别开,RaftServer有其独立的StateMachine以及其对应存储RaftLog的位置。
  • RaftClient进行请求通信时需要带上raft group id,以此让server Proxy知道请求应定向到哪个具体的RaftServer中去。

新的mult-raft RaftServer结构类似如下:

MULTI-RAFT-GROUP kv 小记
对比上图和上上图,同样是3个节点,single-raft模式只能构造出1组RaftServer服务,而multi-raft可以构造出多组以上,这取决于用户实际使用场景怎么使用设置,也不是越多越好。因为一个节点所包含的RaftServer多了,自然它所能允许处理的最大的请求量上限也自然提高了。

MULTI-RAFT-GROUP

通过对 Raft 协议的描述我们知道:用户在对一组 Raft 系统进行更新操作时必须先经过 Leader,再由 Leader 同步给大多数 Follower。而在实际运用中,一组 Raft 的 Leader 往往存在单点的流量瓶颈,流量高便无法承载,同时每个节点都是全量数据,所以会受到节点的存储限制而导致容量瓶颈,无法扩展。

MULTI-RAFT-GROUP 正是通过把整个数据从横向做切分,分为多个 Region 来解决磁盘瓶颈,然后每个 Region 都对应有独立的 Leader 和一个或多个 Follower 的 Raft 组进行横向扩展,此时系统便有多个写入的节点,从而分担写入压力,图如下:

MULTI-RAFT-GROUP kv 小记

此时磁盘及 I/O 瓶颈解决了,那多个 Raft Group 是如何协作的呢,我们接着往下看。

选举及复制

RheaKV 主要由 3 个角色组成:PlacementDriver(以下成为 PD) 、Store、Region。由于 RheaKV 支持多组 Raft,所以比单组场景多出一个 PD 角色,用来调度以及收集每个 Store 及 Region 的基础信息。

MULTI-RAFT-GROUP kv 小记

PlacementDriver

PD 负责整个集群的管理调度、Region ID 生成等。此组件非必须的,如果不使用 PD,设置 PlacementDriverOptions 的 fake 属性为 true 即可。PD 一般通过 Region 的心跳返回信息进行对 Region 调度,Region 处理完后,PD 则会在下一个心跳返回中收到 Region 的变更信息来更新路由及状态表。

Store

通常一个 Node 负责一个 Store,Store 可以被看作是 Region 的容器,里面存储着多个分片数据。Store 会向 PD 主动上报 StoreHeartbeatRequest 心跳,心跳交由 PD 的 handleStoreHeartbeat 处理,里面包含该 Store 的基本信息,比如,包含多少 Region,有哪些 Region 的 Leader 在该 Store 等。

Region

Region 是数据存储、搬迁的最小单元,对应的是 Store 里某个实际的数据区间。每个 Region 会有多个副本,每个副本存储在不同的 Store,一起组成一个Raft Group。Region 中的 Leader 会向 PD 主动上报 RegionHeartbeatRequest 心跳,交由 PD 的 handleRegionHeartbeat 处理,而 PD 是通过 Region的Epoch 感知 Region 是否有变化。

RegionRouteTable 路由表组件

MULTI-RAFT-GROUP 的多 Region 是通过 RegionRouteTable 路由表组件进行管理的,可通过 addOrUpdateRegion、removeRegion 进行添加、更新、移除 Region,也包括 Region 的拆分。目前暂时还未实现 Region 的聚合,后面会考虑实现。

分区逻辑与算法 Shard

MULTI-RAFT-GROUP kv 小记

“让每组 Raft 负责一部分数据。”

数据分区或者分片算法通常就是 Range 和 Hash,RheaKV 是通过 Range 进行数据分片的,分成一个个 Raft Group,也称为 Region。这里为何要设计成 Range 呢?原因是 Range 切分是按照对 Key 进行字节排序后再做每段每段切分,像类似 scan 等操作对相近 key 的查询会尽可能集中在某个 Region,这个是 Hash 无法支持的,就算遇到单个 Region 的拆分也会更好处理一些,只用修改部分元数据,不会涉及到大范围的数据挪动。

当然 Range 也会有一个问题那就是,可能会存在某个 Region 被频繁操作成为热点 Region。不过也有一些优化方案,比如 PD 调度热点 Region 到更空闲的机器上,或者提供 Follower 分担读的压力等。

Region 和 RegionEpoch 结构如下:

class Region {
        long              id;            // region id
    // Region key range [startKey, endKey)
        byte[]            startKey;      // inclusive
        byte[]            endKey;        // exclusive
        RegionEpoch       regionEpoch;   // region term
        List<Peer>        peers;         // all peers in the region
}
class RegionEpoch {
     // Conf change version, auto increment when add or remove peer
     long              confVer;
     // Region version, auto increment when split or merge
     long              version;
}
class Peer {
      long              id;
      long              storeId;
      Endpoint          endpoint;
}

Region.id:为 Region 的唯一标识,通过 PD 全局唯一分配。

Region.startKey、Region.endKey:这个表示的是 Region 的 key 的区间范围 [startKey, endKey),特别值得注意的是针对最开始 Region 的 startKey,和最后 Region 的 endKey 都为空。

Region.regionEpoch:当 Region 添加和删除 Peer,或者 split 等,此时 regionEpoch 就会发生变化,其中 confVer 会在配置修改后递增,version 则是每次有 split 、merge(还未实现)等操作时递增。

Region.peers:peers 则指的是当前 Region 所包含的节点信息,Peer.id 也是由 PD 全局分配的,Peer.storeId 代表的是 Peer 当前所处的 Store。

Split / Merge

什么时候 Region 会拆分?

前面我们有讲过,PD 会在 Region 的 heartBeat 里面对 Region 进行调度,当某个 Region 里的 keys 数量超过预设阀值,我们即可对该 Region 进行拆分,Store 的状态机 KVStoreStateMachine 即收到拆分消息进行拆分处理。

什么时候需要对 Region 进行合并?

既然数据过多需要进行拆分,那 Region 进行合并那就肯定是 2 个或者多个连续的 Region 数据量明显小于绝大多数 Region 容量则我们可以对其进行合并。

给TA打赏
共{{data.count}}人
人已打赏
科学人

Making a Scalable and Fault-Tolerant Database System: Partitioning and Replication

2021-4-2 15:31:00

科学人论文速递

TiKV 的 multi-raft 设计与实现

2021-4-11 15:25:26

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索