搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > CloudArch > Eurosys 19' 阅读笔记:美团云的 Ursa 块存储的一致性、复制协议和错误处理

Eurosys 19' 阅读笔记:美团云的 Ursa 块存储的一致性、复制协议和错误处理

CloudArch 2019-05-13
举报

上一篇推送里介绍了 Ursa 的整体设计,但是存储系统最重要的其实是这一篇里的一致性和错误处理。在我来看最有意思的部分是复制协议,因为这部分不仅介绍了 SSD-HDD 混布系统的设计,还介绍了 Ursa 整个块存储的架构。最后则是实践的经验和性能的评估。

阅读这部分的时候获得了李慧霸博士的帮助,在此表示感谢。

--

一致性

概览

Ursa 通过 chunk server 间的 totally ordering write 来提供对每个 chunk 的线性一致性。简单来说,Ursa 的复制协议和 Paxos、Raft 这样的 RSM(Replicated State Machine)是同源的。每个 chunk 有版本号,chunk server 和 master 共同维护持久化的视图号(view number),view number 会在故障恢复后分配新 chunk server 时更新。

当客户端打开虚拟磁盘时,客户端会从 master 获得每个 chunk 的视图号和位置,然后向所有 replica 查询它上面的视图号。一旦视图号得到确认,client 会选择一个 replica 作为主副本(一般是 SSD 的那个),然后对主副本做读写请求,如下图。

如果 client 检测到某个 replica 失效,就会汇报到 master 重新分配一个 replica,更新 chunk 的位置,增长视图号。

尽管 Ursa 的复制协议 follow 了经典的设计原则,但是还是有两方面明显不同。其一是 Ursa 保证任意时刻只有一个 client 可以 access 虚拟磁盘(MatheMatrix:如果客户需要共享云盘?后面有解答);其二是混合错误模型来区别对待副本和网络失效。

Single client

Ursa 通过租约和锁协议来保证任何时候最多只有一个虚拟机可以某一个云盘读写数据。这样大大简化了强一致性设计。Client 会周期性 renew 租约,一般是 10s。如果有多个 VM 挂载同一个云盘,会在任意一个物理机上使用一个虚拟机通过类似 OCFS2 的集群文件系统协调 IO 请求,对所有挂载的 VM 提供服务。

混合错误模型

不同 GFS 这种同步复制系统(f+1 份拷贝,允许 f 个失效)或其他的异步复制系统(2f +1 份拷贝,允许 f 个失效),Ursa 将副本和网络的失效区分对待,类似 VFT、XFT 这样。Ursa 中 client 会尝试写到所有副本,但会有一个超时时间,如果发生了超时,只要主副本成功即可 commit(上图的第六步)。同时 client 会通知 master 来修复问题,例如分配一个新的 replica 来替换。

与同步复制系统相比,这样可以在一些 replica 失效时始终保持系统可用,和异步复制系统相比持久性更好,具体来说,Ursa 可以在下面的情况保持持久性:

  1. 2f+1 中不多于 f 个副本失效

  2. (失效副本数)+(链接故障数)< 总副本数

(MatheMatrix: 这里我一开始没看懂,后来咨询了本文主要作者之一李慧霸博士,这里的核心含义是 Ursa 会先尝试同步复制,但超时则降级到异步复制,从而实现用户视角的服务可用性)

Ursa 复制协议

一般情况

初始化

当打开虚拟磁盘时,客户端将从 master 获得所有 chunk 的位置和视图号,然后一步查询每个 chunk 的所有 replica 的版本号和视图号。如果一个 chunk 的所有 replica 都有与 master 保存的一致的 view number,那么就可以从中任意选一个作为 primary(优先选择 SSD 上那个);否则,客户端会先向 cluster director 通知修复一致性,然后再重试。因为前面介绍的 single client 设计,client 其实是可以在任意时间更换 primary 的选择。

读和写

一旦视图号和 primary 得到确认,client 就可以向 primary 发送读写请求了。其中读请求优先由 primary 处理,写请求则会带着 client 的 view number 和 version number。还是上面的图,当收到写请求时,primary 会先检查本地的 view number、version number 是否和这个写请求一致,如果一直就在本地写,同时复制到 backup,增长 version number,最后回复给 client。backup 上的操作也是类似的。

然而,如果 client 的 view number 和请求的不一致,那么 primary 会拒绝并回复给客户端。client 需要从 master 获得现在 view number,如果 client 的 view number 大于 primary 上的,那么 primary 将尝试通过增量修复来更新自己的状态,增量修复就是从其他副本同步修改过的数据。如果 client 的 版本号等于 primary 的版本号减 1,那么 primary 本地不会做这个写请求(因为 primary 实际已经执行过这个请求),但是会转发到其他 backup。考虑到 single client 的前提,client 的版本和 primary 的差不会大于 1.

前面介绍的混合错误模型一方面提升了系统的可用性,但也增加了数据持久性的难度。具体来说:

  • 正常情况下,所有 replica 写入成功即 commit

  • 如果 primary 无法从所有 replica 获得成功写入的消息,那么会等待一定时间看能否获得半数成功(这样加上 primary 就是多数成功)

  • 与此同时,client 会通知 master 去修复不一致问题,或者可能最终就是分配一个新的 replica 替代失效的 replica

更换主副本

如果 client 读写发到了一个 fail 的 primary,那么 client 应该会自主更换 primary 到一个有最新数据的 backup 上,这样来实现高可用。考虑到使用了 SSD journal,即使此时是在 HDD backup 上,写入性能也不会太受影响,不过读取性能此时还是会受影响的。此时 cluster director 会同步在 SSD 上再创建一个新 replica,最终 client 会将 primary 重新移回到 SSD 上。

增量修复

为了支持增量修复,每个 replica(包括 SSD 和 HDD)都在内存里保存了一个 journal lite,cache 最近的写请求的 position、offset 和 version number。当一个 primary 或者 backup 从临时故障恢复时(例如网络分区),相关的 replica 会发送他自身当前的 version number 到其他 replica,其他 replica 收到时:

  1. 根据请求的 version number 查询 journal lite,找到修改的数据

  2. 根绝 journal lite 里的信息构造修复消息

  3. 修复消息里加上新的 version number,回复给 replica

如果无法通过 journa lite 找到数据(比如因为垃圾回收),那么就会传送完整数据。

Client-directed replication

前面提过这个事情,就是小于等于 8KB 的 IO 会由 client 直接送到所有副本,而不是通过 primary,其 version number 的维护是类似的,这样可以显著降低小 IO 的延迟。

错误恢复(View Change)

当发生了不一致问题时,master 会向具有最高 version number 的 replica 发送请求来做增量修复。反之如果是 chunk 有问题,那么 master 最终会分配一个新的 replica 来替代问题的,view number 会更新为 i+1,client 会在下次读写时得知这次 view number 的变化。

更具体地说,master 会从多数 replica 中收集 version number,然后选择其中最大的 version number vm 作为最新 state,将数据分发到新分配的 replica,如果需要的话,同时对已存在的 replica 做增量修复。

最后,所有的 replica 将 view number 更新为 i+1,因为它们此时有相同的数据(以及相同的 version number vm)。如果 master 和一个 replica 同时故障了,那么先修复 master,然后修复 replica。

故障恢复的核心思想就是 master 从 quorum 中找到最高的 version number,这个和异步复制系统是一样的。这个方案的缺点就是如果多数 replica crash 则存在丢数据的可能。相反同步复制系统可以在哪怕只有一个幸存者时工作。

下面这里的讨论看起来是如何通过识别 majority 中的永久 crash replica 来达到即使只有一个幸存者 依然可以找回数据,目前 Ursa 的做法还比较依赖手工。

讨论

特性丰富的 Client

Ursa 将很多特性例如 tiny write replication、striping、snapshot、client-side caching 都放在了 client。这和与 Qemu 的紧密集成有关。

Client 被设计为装饰器模型的 pluggable modules,所有 module 实现了公共的 read()/write() 接口,client 可以在线无感升级。

在线升级

Client

当 client 和 VMM(qemu)断开连接时,VMM 不会自动重连,所以想在不影响 Guest 的前提下升级还是比较难的。一种思路是把代码尽可能放到 shared library,这样升级就是 dynamic reloading,就很简单,但是也有很多限制,比如现有很多静态库不适合,接口不好升级等等。因此 Ursa 升级粒度是 process 而不是 library。Ursa 将 client 分为两个 process——core 和 shell。当升级时:

  1. core 停止从 VMM 接受新 IO,将 pending IO 完成

  2. 将状态保存到一个临时文件

  3. 退出并返回一个特殊返回码

shell 进程接收到 exit code 启动一个新的 core,从临时文件读取状态并恢复。

(MatheMatrxi:大体就是一个通过外包壳来分离核心代码的思路,升级期间 IO 应该都 pending 了)

Master

Master 升级是比较简单的,因为不涉及 IO 路径,只要关掉旧的立刻启动新的即可。升级期间磁盘创建和空间分配都会失败,不过客户端会自动重试。

Chunk Server

这个就比较难了,特别是在 chunk server 升级中发生 failure 时会对 failure handle 产生 confuse。所以 Ursa 设计了一个优雅热升级的策略:

  1. 发送一个特别信号到 chunk server;

  2. 关闭服务端口,停止接收 IO;

  3. 等待 in-flight io 结束;

  4. 等待新的 chunk server 启动;

  5. 检查新 chunk server 正常工作。

如果热升级成功了,旧的 chunk server 关闭所有连接( MatheMatrix:比如管理端口的连接)退出,客户端自动重连到新的 chunk server。反之如果失败了,旧的 chunk server 会杀死新的 chunk server,重新打开服务端口继续提供服务。

逐个升级

一个 Ursa 集群有众多服务进程做成,升级时每次升级一个进程,检查状态再升级下一个。所以升级集群可能会持续好几天时间。所有组件保持向后兼容性,到目前 Ursa 在一个部署超过两年的集群里对 replication protocol 升级过四次版本,每次升级都会增加新的操作并保证之前的操作不会发生变化来达到向后兼容。

发挥磁盘并行性

根据经验,Ursa 对 SATA SSD 和 PCIe SSD 会跑两个和四个进程,HDD 上跑单线程单进程,这个线程同时处理 journal replay、small write 和 replication( large write)。评估显示单线程进程只用电梯算法就可以跑满 HDD,多进程、多线程这些都会扰乱电梯算法,降低性能。

硬件可靠性

根据统计(下面的表格),HDD 贡献了 70% 的错误,比 SSD 高一个数量及。而 SSD 的 wear leveling(损耗平均)保护了 SSD 不会被频繁的 journal 写影响寿命。

Eurosys 19' 阅读笔记:美团云的 Ursa 块存储的一致性、复制协议和错误处理

不过虽然 SSD 平均故障率比 HDD 低,但是有一个潜在风险就是 SSD 可能因为固件 bug 造成同一批、同一个供应商的 SSD 批量出现问题。(MatheMatrix:然后举了腾讯云去年的例子 /捂脸)

为了解决这个影响,需要增加购买 SSD 的多样性,而且把 primary chunk 和 backup chunk 的 journal 存放在不同批次、供应商的 SSD 上。和全 SSD 存储不同的是 Ursa 可以通过 HDD 避免因为 SSD 固件 bug 造成的批量 SSD 下线造成数据丢失。

局限

  1. primary replica 故障恢复时客户就需要忍受 HDD 的性能影响,这要求我们尽可能缩短降级时间;

  2. 因为每个机器能提供的 SSD journal 大小有限制,SSD journal 无法持续性提供长时间很高的随机写(这个在美团云服务中比较少见),这个通过 HDD journal 和限速来 work around;

  3. SSD 的实效恢复要比全 SSD 更 urgent(MatheMatrix:和 1 好像一样)

  4. 对 SSD 的大小要求比 SSD cache 大很多

评估

这里做了一些测试,我直接展示数据吧,比较了 Ursa(SSD 和 SSD-HDD 混布)、Ceph 和 Sheepdog:

Eurosys 19' 阅读笔记:美团云的 Ursa 块存储的一致性、复制协议和错误处理

QD(queue depth)最大为 16 因为 qemu NBD driver 只能提供到 16。Sheepdog 和 Ceph 都是部署在全 SSD 环境的。(c) 这里表现比较差主要是因为 1MB 的 BS 大于 64KB 的 journal pass 阈值,所以 backup 都直接写入 HDD 跳过 journal 了。

Eurosys 19' 阅读笔记:美团云的 Ursa 块存储的一致性、复制协议和错误处理

图 10 主要是因为 Ursa 很依赖 journal 的 range query 的性能,因此和 PebblesDB 做了一个比较。

图 14 这里的选取了三种有代表性的 trace 数据 prxy_0proj_0mds_1 分别代表不同的 IO pattern。

相关工作

这里举了一些块存储、EC、混合存储、文件系统、对象存储和一致性的文章

总结

未来计划 RDMA/DPDK/SPDK 来达到超低延迟;提高顺序写的 IOPS。

Ursa 的一部分组件是开源在 http://nicexlab.com/ursa/ 的,包括:

  • st-pio 前面说的 libaio 的包装

  • st-redis hiredis 的包装

  • logging lib 支持动态更新配置的轻量级 logging library

  • crc32 高性能的 crc32 library

  • testing script

后面是致谢和线性一致性的正面,包括 normal case 和故障恢复。

版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《Eurosys 19' 阅读笔记:美团云的 Ursa 块存储的一致性、复制协议和错误处理》的版权归原作者「CloudArch」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注CloudArch微信公众号

CloudArch微信公众号:CloudArch

CloudArch

手机扫描上方二维码即可关注CloudArch微信公众号

CloudArch最新文章

精品公众号随机推荐

举报