atlas 是 baidu 自研的kv存储系统, 但是从论文中的描述上, 还是更像是 分布式文件对 小文件的优化, 尤其是使用场景: 小图片 等. 在统计意义上, 发现 大部分的读写都集中在 (128k, 256k], 读写比例大概是 96.3% 和 94.1%.

架构设计:

在架构上, 底层是 块存储服务层: RBS Master + RBS PartServer, PartServer 是block 的存储层, 只负责块的存储, RBS Master 管理 block-> RBS PartServer 的映射 以及 高可用管理, 但是块存储并不适合kv的高频、low-seized 的写入, 因此 需要上层的 PIS Slice 做一层cache, 除此之外, PIS 还需要维护 kv->block 的映射 (PIS: patch and index system)

贴个图

模块设计:

PIS

截图就行

replication 保证多副本高可用(半同步). 内部有 patch 和 index 模块, 新写入的kv 先进入 patch 层(cache+file; 三副本+秒级flush+电池供应), 攒到64MB 的block, 则刷新block到RBS, 并更新原来的kv数据, value 转换为数据的索引: [blockid, offset, length], 并重新块里面的数据信息, 然后写入 index module 的LSM. (这里需要注意, 当slice leader flush block 后 crash, 存在 redundancy block, 这个应该需要gc掉, 不过论文没讲到)

RBS

RBS master 维护metadata, 通过LSM 存储. metadta: block table(blockid -> partserver ip list), partserver table(partserver ip -> global part ip list). ha 主备.

RBS Master 在 client request write 的时候, 会返回 blockId 和 15个ip address(避免写入失败, 可以failover, 保证ip不一样, 并且两机架挂掉可容忍), client 成功后提交 blockid+partserver ip 给 RBS Master.

RBS partserver 和 RBS Master 之间通过心跳 keepalive, RBS Master 发现 partserver lost, 会执行恢复流程: 1. 更新 block table 避免失败的读取 2. generate repair task & dispatch to selected partserver 3. update metadata(block table & partserver table). 所以恢复流程是在 partserver 上执行的

块存储, 最大的特点是 使用了 Reed-Solomon coding, 降低数据冗余, 64MB的block, 分成了 8个 8MB 的part, 经过计算一共会产生 12个 part (保证了最多5个part挂了, 数据才无法恢复).

IO路径:

目前支持的操作有: write(key, value)、read(key, value)、delete(key)

write

client write(key, value), 会先通过 hash function(key) (比如 MD5(key) % number of slices) 选择一个 slice, slice 会映射到一个 PIS 上.

注意, 这里 slice 仅仅是 数据的逻辑单位, slice 的存储会映射到 PIS 物理存储服务上, 支持迁移slice 和 失败恢复 (问题: 论文中并没有讲到 PIS 的recovery failure), 这些行为会集中在 PIS Master (论文中也没描述, 感觉和 dynamo、codis 的数据逻辑抽象类似); 为了降低PIS Master的负载, slice->PIS Servers 的mapping 会 cache在client 侧.

PIS 收到请求后, 并不会立即发送给 RBS PartServer, 而是会攒一个 block(64MB)的写入, 来提升编码、存储效率. 64MB block 会先平均分区到 8个 parts 然后分别存储到不同的 part server (涉及到 Reed-Solomon code 编码, TODO: 补充下). 成功存储后上报给 RBS Master

画个图? 复制层 没写

read

client read(key), 会想通过 hash function 得到相应的 slice, 请求 PIS, PIS 会现在 Patch 模块进行匹配, 如果命中, 则直接返回value; 否则进入 index 层匹配, 没命中的话, 则说明key不存在; 命中, 则decode index value 获取 blockId、offset、length, 然后请求 RBS Master 获取 blockid->RBS PartServer 的映射信息, 得到 blockId 对应的 PartServer 地址, 请求 对应的RBS Partserver 获取数据. (这里不太理解了)

Otherwise, the function selects eight out of available parts of the block to read and decode to recover the requested data

delete

寻址方式同上; PIS 搜到删除请求后, 执行半同步复制; 如果数据在patch层, 则直接删除, 无需请求RBS; 如果在 index 层, 则先标记删除, 等待后续的merge gc.

gc流程是通过离线的 map-reduce 进行的, 对存在deleted kv的block, 按照: 1. 创建时间 和阈值比较(一周前) 2. 可用数据的空间比例低于阈值 进行判断是否需要gc. gc流程上, 会读取 读取有效的数据并重新写入 RBS, 成功后刷新 metadata(block table 和 partserver table【感觉应该是mapping信息】).

rewrite之后, partserver 的block data并没有被删除, 需要partserver 不断轮询并对比 RBS Master 上的元数据信息(partserver 上合法的block) 并执行删除.

another feature

除了本身的设计, 为了降低功耗, 使用了 arm 替换了 x86, 据说减少了 53%.

Reed-Solomon code: 相比于三副本 200% 的数据冗余, 这里只有 50% 的数据冗余.

性能对比

相比于单纯的 kv 存储, 写吞吐量提高了将近三倍, 因为 PIS patch 的设计降低了 写放大的影响,

个人评价

通过 PIS 的patch 层 实现 前台IO 与 后台IO 隔离的设计, 提升了 RBS 的block存储效率; PIS 实现了 slot 迁移与 存储数据value 相分离的效果 (只需要迁移key, 降低了迁移数据量). 是 kv 面向大value存储的 另一个发展方向. 但是定位就比较蛋疼了, 形式是kv的,实现是 block, 底层的 RBS 可以换成 hdfs 等存储设施.

arm 拥抱云基础设施 值得关注, 尤其是功耗这块. Reed-Solomon code 降低了冗余(这个算不上亮点, 基本是业界基本的做法了)