集群模式
注意:文章中提到的部分特性暂时还未实现。
整体架构
|
|
上面给出来 HoraeDB 集群化方案的整体架构图,对于其中的一些名词做出解释:
HoraeMeta Cluster
:集群的元数据中心,负责集群的整体调度;Shard(L)/Shard(F)
: Leader Shard 和 Follower Shard,每一个 Shard 由多张 Table 组成;HoraeDB
:一个 HoraeDB 实例, 包含多个 Shard;WAL Service
:WAL 服务,在集群方案中,用于存储实时写入的数据;Object Storage
:对象存储服务,用于存储从 memtable 生成的 SST 文件;
根据架构图,读者应该对于集群化方案有一个初步的认知 —— 存储计算分离。正因如此,HoraeDB 实例本身不存储任何数据,从而使得计算存储弹性扩缩容、服务高可用和负载均衡等等有用的分布式特性比较容易实现。
Shard
Shard 是一个重要的概念,是集群调度的基本单元,一个 Shard 包含多张表。
这里值得说明的是,集群调度的基本单元并非是 Table,而是采用了 Shard,主要原因在于考虑到 HoraeDB 集群如果需要支持至少百万级别的 Table 的话,而部分组件,直接处理起这个量级的 Table 可能会存在一些性能问题,例如对于 WAL 服务,如果按照 Table 去单独管理数据的话,一个重要的性能问题就在于重启后恢复新写入数据的时候,按照 Table 级别去做数据恢复的话,将会非常耗时,但按照 Shard 去管理数据的话,这个情况会改善很多,因为具体的 WAL 服务实现可以按照 Shard 这个信息,将 Table 的数据进行合理组织,从而获得比较好的数据局部性,在重启恢复的时候,就可以通过 Shard 来进行快速的数据恢复。
此外 Shard 具有 Leader 和 Follower 两种 Role,也就是图中的 Shard(L)
和 Shard(F)
,Leader 负责读写,Follower 只有读权限,Follower 的引入其实是为了解决 HA 的问题,保证 Leader 在 crash 了之后,能快速的恢复相关表的读写服务,为了做到这一点,Follower 需要从 WAL 不停地同步最新的数据(由 Leader 写入)。
一个 HoraeDB 实例会拥有多个 Shard,这些 Shard 可以是 Leader 也是 Follower,以交织的方式存在:
|
|
上面提到集群的基本调度单元是 Shard,因此对于 Shard 存在这一些基本操作,通过这些操作可以完成一些负责的调度逻辑:
- 向 Shard 新增/删除 Table:在建表/删表的时候使用;
- Shard 的打开/关闭:可以用来将一个 Shard 迁移到另外一个 HoraeDB 实例上面;
- Shard 分裂/合并:可以用来完成扩容和缩容;
- Shard 角色切换:将一个 Shard 从 Leader 切换成 Follower,或者从 Follower 切换成 Leader;
HoraeMeta
HoraeMeta 主要负责集群的元数据管理和集群调度,其实现是通过内置一个 ETCD 来保证分布式一致性的。
元数据主要都是围绕 Table 来构建的,包括一些建表信息(如 Table ID),Table 所属集群(HoraeMeta 是可以支持多个 HoraeDB 集群的),Table 与 Shard 的映射关系等等。
HoraeMeta 的主要工作还是负责集群的调度,需要完成:
- 接收来自 HoraeDB 实例的心跳,根据心跳来检测 HoraeDB 实例的存活状态;
- 负责 Shard 的具体分配以及调度,会将 Shard 按照一定的算法(负载均衡)分配到具体的实例上面去;
- 参与 Table 的创建,将 Table 分配到合适的 Shard(也就会分配到具体的实例);
- 如果有新的 HoraeDB 实例加入到集群中,可以进行扩容操作;
- 如果有检测到 HoraeDB 实例的下线状况,可以完成自动的故障恢复;
路由
为了避免转发请求的开销,客户端与 HoraeDB 实例之间的通信是点对点的,也就是说,客户端在发送任何特定的写入/查询请求之前,应该从服务器获取路由信息。
实际上,路由信息是由 HoraeMeta 决定的,但是客户端只允许通过 HoraeDB 实例而不是 HoraeMeta 来访问它,以避免由 HoraeMeta 引起的潜在性能瓶颈。
WAL Service & Object Storage
在集群方案中,WAL Service
和 Object Storage
都是作为独立的服务而存在的,并且是具备容灾能力的分布式系统。目前 WAL Service
的实现,在集群化方案中主要包括两种实现:Kafka
和 OBKV
(通过其提供的 Table API 访问 OceanBase
);而 Object Storage
的实现比较多,基本覆盖了主流的对象存储服务。
这两个系统的相似之处在于,它们是作为计算存储分离架构中的的存储层;而它们的区别也很明显,WAL Service
具备较高的实时写入性能(高吞吐、低延时),负责实时的新增数据写入,而 Object Storage
具备低成本的存储代价和较为高效的查询吞吐,负责后台整理好的、长期存储的数据读写。
这两个服务组件的引入,使得 HoraeDB 集群的水平扩容、服务高可用、负载均衡提供了实现基础。
水平扩容
集群的扩展能力是评价一个集群方案的重要指标,下面从存储和计算两方面来介绍一下 HoraeDB 集群的水平扩容能力是如何实现的。
存储
很明显,在选择 WAL Service 和 Object Storage 两个底层服务实现的时候,水平扩容能力是必须的,存储容量出现问题的时候,可以单独地进行存储服务的扩容。
计算
计算能力的水平扩容可能会复杂一下,考虑如下几个容量问题:
- 大量查询大量的表;
- 大量查询同一张数据量大的表;
- 大量查询同一张正常的表;
对于第一种情况,通过 Shard 分裂,将分裂出来的新 Shard 迁移到新扩容的 HoraeDB 实例,这样表就会分散到新的实例上。
对于第二种情况,可以通过表分区的功能,将该表分成多个子表,从而达到水平扩容的效果;
第三种情况是最重要也是最难处理的。事实上,Follower Shard 可以为 Leader Shard 分担部分查询请求,但是由于 Follower Shard 需要同步数据,它的数量是受到 WAL 同步能力的限制的。实际上,从下图可以看到,对于这样的场景(当 Follower Shard 不足以承担住访问量的时候),可以考虑在集群中添加一种纯计算节点,其中的 Shard 不参与实时的数据同步,当这个实例被访问的时候,新写入的实时数据可以从 Leader Shard 或者 Follower Shard 恢复出来,历史的数据可以直接从 Object Storage
获取并缓存,这样的话,就可以在一定程度上(实际上还是存在单表的实时数据拉取的瓶颈,但是考虑到这部分数据并非特别多,理论上这个瓶颈应该比较难以达到),达到水平扩容的效果。
|
|
High Availability
通过上面的介绍,HoraeDB 的高可用方案,其实比较自然了。考虑某台 HoraeDB 实例 Crash 了,后续的服务恢复步骤整体如下:
- HoraeMeta 通过发现心跳断了,判断该 HoraeDB 实例下线;
- HoraeMeta 选择将该实例上的所有 Leader Shard 所对应的 Follower Shard,切换成 Leader Shard,从而完成快速恢复;
- 如果没有对应的 Follower Shard 存在,那么需要执行 Open Shard 操作,不过这样的恢复是较慢的;
|
|
Load Balancing
HoraeDB 上传的心跳信息会带上机器的负载信息,HoraeMeta 会根据这些信息,完成自动的负载均衡的调度工作,主要包括:
- 新建表的时候,为该表挑选一个负载低的实例上面的 Shard;
- 将负载高的实例上的 Shard 迁移到负载低的实例;
- 更好地,可以根据 Shard 的负载,通过 Shard 的分裂和合并,来完成更细粒度的负载均衡;