小米开源分布式KV存储系统Pegasus( 二 )


有了这几个定位之后,我们架构也比较清晰的浮出了水面:
架构一览
总体来说,该架构借鉴了不少 HBase 的内容:
和 HBase 不同之处在于以下几点:
目前的高可用依赖于,这是为了项目开发简单上的一个权益之计 。后面可能引入 Raft,从而彻底消除对的依赖 。
多副本的一致性协议
前面提过,我们的每个都是有多副本的 。为了满足多副本情况下的强一致性,我们必须得采用一致性协议算法 。我们使用的是 MSRA 发表的。和 Raft 的对比可以参考我们在项目中的文档,这里不再详细展开 。总的来说,我们认为依照的论文来实现一个可用的存储系统,其难度要比 Raft 更低 。
写请求流程
在协议里边,负责接受读写请求的叫做,相当于 Raft 中的 ;另外两个接受请求的是,相当于 Raft 中的。在如此架构下,对一个写请求也比较简单:如果客户端有一个写请求,首先需要向来查询 Key 的位置,之后再向所在的发起写请求,然后将其同步给,二者都成功后再返回客户端写请求成功的回复 。
的读请求流程图
读请求操作更加简单,客户端直接和发起读请求,因为拥有全部数据,所以直接进行响应 。
实现上的那些坑
前面大致回顾了的设计考虑和总体架构 。现在进入我们的正题,来看看这样的架构在实现上会有哪些问题?
扩展性
首先来看扩展性 。扩展性分为两点:
的选取
对于,业界解决方案一般有两种,第一种是哈希,第二种是排序 。
对于哈希,就是将所有的 key 分成很多桶,一个桶就是一个,然后根据 key 的哈希值来决定分配到某个桶中;对于排序而言,所有 key 一开始都在一个桶里边,然后依据桶的容量进行不停地动态分裂、合并 。

小米开源分布式KV存储系统Pegasus

文章插图
对比这两种方案,排序比哈希多一个天生的优点是排序方案有全局有序的效果 。但由于排序需要不停地做动态分裂,因此在实现上更麻烦 。
另外,如果采用 hash 的,数据库不太容易因为业务的访问模式而出现热点问题 。排序由于将所有的 key 都按序排列,相同前缀的请求很有可能会被集中在一个或者相邻的中,而这些请求则很有可能落在同一台机器上 。而对于哈希来说,所有的 key 已经预先都打散了,它自然而然就没有这个问题 。
不过,单纯对比两个方案的优劣意义并不大,还是要从业务出发 。我们认为,在互联网的业务场景中,不同的 key 之间并不需要有一个偏序关系 (比如两个用户的名字),所以在权衡之后我们采用哈希方案 。
Hash实现方式
在实现的时候,我们遇到的第一个问题就是“数据怎么存储”的问题 。“把一个 key 依据 hash 值落到一个桶中”,这只是一个理想化的抽象而已 。在真实的系统实现上,你必须还得提供一层“表”的概念把不同的业务给分割开 。有了表的概念后,我们在实现上就开始产生分歧了 。具体来说,我们在存储上一共有“多表混存”和“分开存”两种方案 。
所谓多表混存,就是将表 ID 以及 hash key 合起来算一个新的哈希值,根据这个哈希值从全局唯一的space 里选取一个做存取 。如上图的左半部分所示 。
而对于分开存而言,把表的语义下推到了存储层 。如上图的右半部分所示:每个表都有一个单独的space,当有一个读写请求时,要先根据表 ID 找到对应的space,再根据 hash key 去找对应的。
那么这两种方案到底哪一个更好呢?从理论上来说,我们认为多表混存方案更好,因为它更符合软件工程中分层的思想;混存也更容易实现,因为只需要管理一份元数据,负载均衡也更为简单 。但是从业务角度来看,采用各表分开存的方案更好 。因为分开存意味着更简单的表间资源限制、表级别的监控和删除表的操作 。考虑到运维上的误操作,分开存储也更有优势,即使你误删了一个,该错误操作扩散给不同业务的影响也是要少一些 。