自制分布式存储系统入门篇:系统概要设计
最终用户 API
最简单的 Key-Value 存储只需要实现 Put 和 Get 方法就可以了,但是如果只提供这 2 个 API,在遇到一些比较复杂的场景时,就会给调用方带来比较大的负担。甚至于一些需要一致性保证的场景下,无法单靠调用方来完成这些功能。
Redis 是一个比较成熟的,得到广泛应用的 NoSQL 存储,我们可以参考 Redis 的接口设计,实现其中利用率比较高的一些 API。
方法名 |
含义 |
GET |
Get by key |
GETSET |
Set, returning old value |
MGET |
Get multiple |
SET |
Set by key |
SETNX |
Set if doesn’t exist |
SETXX |
Set if exist |
MSET |
Set multiple |
除此之外再加上一个 Exists 判断给定的 Key 是否存在但不返回其内容。
这里需要特别注意的是,MSET 应该具有原子性——要么都成功,要么都失败。MGET 和 MSET 这 2 个成对的操作理应满足一定的一致性原则,例如不应该出现 fractured reads [1],但是这样做的实现难度较高,我们暂时先不考虑。
整体架构
目标系统的架构如系统架构图所示:
所有的用户请求首先进入 API Server,然后由 API Server 处理系统内部的逻辑。API Server 周期性的与 MetadataServer 同步数据的拓扑信息,例如哪些 DataServer 持有哪些数据,然后将用户的请求转发给对应的 DataServer。DataServer 是数据的真正持有者,服务的真正提供者。DataServer 与 MetadataServer 周期性的进行同步,向 MetadataServer 汇报自己的状态信息,接受 MetadataServer 的控制指令。
主要流程
用户进行 MGET 请求
用户进行 MGET 请求时,首先将请求发送给 API Server。API Server 周期性的与 Metadata Server 进行消息同步,以获取整个集群中的拓扑关系,这一关系中含有哪些数据存放在哪些 Data Server 的映射关系。API Server 将请求发送给对应的一个或多个 Data Server,将获取到的数据进行聚合后,回复给用户。
用户进行 PUT 请求
用户进行 PUT 请求时,首先将请求发送给 API Server。API Server 将数据写入对应的 Data Server 中,然后向该数据分片的主节点请求提交该请求。主从节点内部协调后,向 API Server 返回协调后的结果。API Server 向用户返回该结果。
如果 PUT 的 Value 特别小的话,随着控制指令进行主从协调可以节省一些消息交互。但是更多的情况下,Value 没有这么小。此时由 API Server 进行数据复制,比主节点进行复制更合理,因为 API Server 的数量是随着请求负载增长而增长的,但是主节点的数量并不具有这样的关系,因此这样做可以降低主节点的压力。