写点什么

微信 PaxosStore 内存篇:十亿 Paxos/ 分钟的挑战

2016 年 12 月 27 日

微信存储 QuorumKV 是一个分布式的存储系统,覆盖但不限于微信后台核心业务:账号 / 用户信息 / 关系链 / 朋友圈,等等。

过去的一年,我们受 Google MegaStore 启发,重新设计了一套全新的分布式存储系统,即 PaxosStore 分布式存储,并于最近半年对核心业务存储做了架构改造,目前已成功上线并完成了数千台机器的平滑切换,系统首次访问成功率提升一个量级,并获得更好的容灾能力,可用性显著增强。

内存云作为微信 PaxosStore 存储体系的组成部分,目前存储着微信基础账号、消息计数等核心用户数据,每天峰值请求高达数十亿 / 分钟,本文将向大家分享内存云的 Paxos 改造过程。

背景

微信内存云,目前有 2 千多台机器:单机内存 64GB,存储盘为机械盘。作为核心存储之一,内存云承载了基础账号、消息计数等核心数据的存储,保障微信登录、消息收发等微信基础功能。内存云每天服务峰值请求十多亿 / 分钟,读写比约为 3.3:1。基于 QuourmKV 的内存云具体架构如图 1 所示:

图 1 QuorumKV 架构

微信 QuourmKV 陪伴内存云走过了数次扩容,经历元旦、春晚等例行节日请求爆发增长的洗礼,也沉淀着不少故障处理经验(不限内存云)。本文主要描述新架构如何根本性地改善 QuorumKV 的容灾能力(即 CAP 中,保证 C 的前提下增强 A),在不牺牲性能的前提下,消灭部分故障场景下的最终失败和人为介入。

QuorumKV 本质上是一个 NWR 协议(N 为 3,W/R 为 2)的分布式存储,和其他 NWR 协议不同的地方在于:QuorumKV 将 NWR 应用在版本上,当且仅当版本达成一致的情况下读写单机数据,从而保证强一致性。但是这里引入了 2 个问题:

  • 数据写一份,依靠异步同步至对机;

  • 当 WR 无法形成多数时,单 Key 不可用,需进行修复。

不要小看了这 2 个问题,这是目前 QuorumKV 的绝大部分日常故障导致失败的根源,特别是对写量大的模块而言:

  • 数据机故障离线,部分 Key 最新数据在故障机上,不可用;

  • 版本机故障离线,部分 Key(版本不平) 仲裁失败,不可用。

表 2 分布式协议对比

明确问题的根源是 QuorumKV 采用的 NWR 分布式协议,那么在保证强一致性的前提下可行的解决方案为 Paxos/Raft 分布式协议。表 2 列出了我们在协议选择上的一些考量:采用无租约的方式使得系统在保证强一致性的前提下达到最大的可用性;相对而言,租约的方式必然引入主备切换导致的不可用。

在对比调研过一些业界方案(etcd/megastore 等)后,我们最终确定新架构协议方案是:无租约版 Paxos 分布式协议(如图 3 所示)。

图 3 新架构 (无租约版 Paxos 分布式协议)

面对挑战

接下来需要做的事情是,基于 Paxos 分布式协议在机械盘上搭建一套稳定高性能的分布式存储。

挑战 1:Paxos 分布式协议

我们在谈及 Paxos 算法时,通常会提及 Leslie Lamport 大神的 Paxos Made Simple;但基于 Paxos 算法的分布式协议不止与此,图 4 列出一个完整协议涉及的 3 个层次。

图 4 Paxos 分布式协议

Paxos 算法

PaxosStore 孕育了 2 套 Paxos 算法组件:Paxos Certain 组件和 Paxos KV 组件,内存云使用的正是其中的 PaxosKV 算法组件。Paxos KV 组件核心代码 1912 行,经过严格测试,目前用于线上多个 Key-Value 存储模块,包括但不限于:用户账号信息存储、朋友圈存储等。

PaxosLog

在构建 PaxosLog(简称为 PLog)时,我们针对 Key-Value 存储做了 2 项优化。

第一,PLog as DB

普通青年的通常做法是 PLog 和 DB 分离:增量更新记录在 PLog 中,并在 Chosen 之后顺序应用到 DB 中。这里的问题在于:

  • 至少 2 次写操作:1 次 PLog 写,1 次 DB 写。

  • Key 和 PLog 的对应关系:

  • N : 1 读写性能受限与单个 PLog;

  • 1 : 1 相同的数据重复写 2 次。

图 5 精简 1:plog as db

进步青年思考了下得出 PLog as DB 方案:我们希望 Key 和 PLog 保持 1:1 的对应关系,从而达到最大的并发性能,同时又不引入重复写。

第二, 精简的 PLog: 只保留最新的 LogEntry

图 6 精简 2:最新的 LogEntry

作为 PLog as DB 的延伸,既然每个最新 LogEntry 包含全量的数据,那么物理上保留连续的 PLog 就没有必要了。

简单轻便的 PLog 结构,带来的优势还有:

  • LogCompact 伴随每次写进行,不占用额外的存储和计算开销;

  • Log Catch-Up 通过分发最新的 LogEntry 即可完成,无需顺序追流水或者 Snapshot,应用 DB。

基于 PaxosLog 的强一致性读写协议

强一致性读协议

图 7 强一致性读协议

强一致性读协议本身和 Paxos 算法没有太大关系,要点是多数派:广播的方式获取集群中多数机器(包含自身)的 PLog 状态,即最新的 LogEntry 位置和对应 LogEntry 处于 Pending/Chosen 状态。

  • 当集群中多数在 Log Entry i 上处于 Chosen 状态时,可以确定 Log Entry i 是最新的。对于读多写少的业务,主要面对这种情况,整体读就可以非常轻量且失败非常低。

  • 当集群中多数在 Log Entry i 上处于 Pending 状态时,无法确定 Log Entry i-1 是否最新,因为可能存在某台机器 Log Entry i 处于 Chosen 状态。对于读多写多的业务,读的失败就会相对高很多。可行的优化有:(1)尽量收集多机的状态信息,如果所有机器的 Log Entry i 都处于 Pending 状态,就可以确定 Log Entry i-1 的数据是最新的;(2)使用隐含条件:只有 A/B 机器可读写。

强一致性写协议

强一致性写协议的大多数问题来自 Paxos 算法本身,这里我们主要做了 3 项优化(或者说解决 3 个问题)。

第一,优化写。

  • The leader for each log position is a distinguished replica chosen alongside the preceding log position’s consensus value. The leader arbitrates which value may use proposal number zero. The first writer to submit a value to the leader wins the right to ask all replicas to accept that value as proposal number zero. All other writers must fall back on two-phase Paxos.

上述文字摘抄自 MegaStore: FastWrites 部分,描述:LogEntry i-1 值的归属者可以在写 LogEntry i 时跳过 Paxos 算法的 Prepare 阶段直接进行 Accept 阶段。基于这段迷一样的文字,我们实现 Paxos 优化写算法:减少 1 次写盘、2 次协议消息发送、2 次协议消息接收;最终实现了写耗时和失败的降低,如图 8 所示。

图 8 优化写和普通写的性能对比

第二,如何确定谁的提议写成功了?

Paxos 算法只保证 LogEntry i 确定唯一值,但在多个 Proposer 的条件下(即 A/B 机均可发起强一致写),只有确定值的归属者可以返回成功。这里我们复用了 etcd 中 requestid 的方案。

图 9 requestid 方案

其中 member_id 用于区分不同的 Proposer,timestamp 为毫秒级别的时间戳,req_cnt 随写请求单调递增。基于 requestid 方案可以满足单机 25w/s 的写请求,足够了。

备注:requestid 只需要在单个 plog 纬度上保证唯一即可。

第三,Paxos 活锁问题。

Paxos 算法本身不保证终止性,当出现写冲突时算法可能永远终结不了,即存在活锁问题;因此在实际工程中我们需要进行一些权衡:

  • 限制单次 Paxos 写触发 Prepare 的次数;

  • 随机避让。

我们目前使用了 Prepare 次数限制策略,从现网监控来看由写冲突导致的失败比例极小:

图 10 Prepare 次数限制导致的失败监控

挑战 2:基于机械盘的 DirectIO 存储

我们在新架构上采用 DirectIO 的方案实现了一套保证数据安全落盘的存储组件,DirectIO 方案与其他两种写盘方案的对比如表格 11 所示。

表 11 写方式比较

然而仅仅保证数据安全落盘还不够,我们还要做到稳定:基于 Bitcask 写模型搭建的存储,需要定期的整理磁盘文件(我们称之为 Merge),以重复利用磁盘空间,其中涉及操作有:Merge 新文件写盘、旧文件删除,对外表现为:Merge 写和正常写竞争、文件删除引起系统卡顿。为克服这 2 个问题,我们做了以下优化。

  • 控制 Merge 写盘速度和大小。

  • 在 DirectIO 的 4K 块中引入 BlockID,以支持文件的循环利用(不再删文件)。

图 12 4K DirectIO BlockID

挑战 3:复杂的现网场景

机械盘 RaidCache

RaidCache 以电池作为后盾,可以在 WriteBack 模式下持有待写盘的数据批量写盘,以极大提升机械盘的写盘性能;缺点在于当电池掉电时,为了数据安全必须从 WriteBack 模式切换到 WriteThrough 模式,写盘性能急剧下降(真实的机械盘)。磁盘从 WB 降级为 WT 是现网运营中常见的问题,通常 1~2 小时后电池充电完毕即可重回 WB 模式,我们来看看这 1~2 小时磁盘退化的影响。

单次磁盘退化导致磁盘写操作耗时和失败率升高,通常情况下被 Paxos 协议本身的多数派所容忍;但是本机作为 Proposer 主动发起写时,写盘失败带来的就是单次写请求失败,前端会自动跳转对机重试,此时问题来了:磁盘退化者写失败之前将 LogEntry 置于 Pending,对机重试的最终结果将 Pending 推成 Chosen,但此时 requestid 表明 Chosen 值源于磁盘退化者,对机写被抢占,返回最终失败。

简单的说,磁盘退化期间写最终失败较高:通过将 requestid 前传到对机,让对机用已有的 requestid 重试可以将写最终失败降低 1 个数量级。

PLog**** 对齐

当单机包含 kw 级别的 PLog 时,保持系统中所有 PLog 均处于对齐状态就变得很困难;但只有在所有 PLog 均处于对齐状态时,系统才能保持最大化的可用性。经历一番权衡后,我们的系统中挂载了以下逻辑 (按时效性排序) 来保证 PLog 对齐:

  • 失败 (本地落后) 触发异步 Catch-Up;

  • 三级超时队列:如果 LogEntry 超时后依旧处于 Pending 状态,就触发协议写重试;

  • 全量数据校验:校验数据一致性的同时也触发 PLog 对齐。

LeanerOnly**** 模式

机器重启后发现文件丢失,数据被回退了怎么办?Paxos 协议保证了绝大部分情况下强一致性和可用性,但不是全部。

  • 某 LogEntry 承诺机器 A 的 Paxos 请求后,因为数据回退状态清空,重新上线后承诺机器 B 的 Paxos 请求,但是前后 2 次承诺相互矛盾,从而导致数据不一致。

上述描述的是拜占庭失败导致的数据不一致,这种失败违反了 Paxos 协议的假设前提;但现网运营中确实又需要处理这种情况,从而有了 LeanerOnly 模式的引入。

  • LeanerOnly 模式下,本机只接收 Chosen 后的 LogEntry,不参与 Paxos 协议写,也就不会违背任何承诺。

  • 三级超时队列,使得 LogEntry 尽快走向 Chosen。

  • 异步 Catch-Up 和全量数据校验使得系统 PLog 较快对齐。

假设进入 LeanerOnly 模式 2 小时后,系统中旧 LogEntry 处于 Pending 状态的可能性可以忽略不计,那么该机可以解除 LeanerOnly 模式。

成果

除了上文描述的优化外,我们还定制了一套本地迁移的方案用于新旧架构的平滑切换,由于篇幅限制,在此就不一一展开了。最终我们实现上千台机器安全无故障的从 QuorumKV 架构切换到新架构,下面同步下新架构的性能数据和容灾能力。

性能数据

压力测试条件:Value 约为 120B,读写 3.3 : 1。

压力测试机型:64GB 内存,机械盘。

表 13 新旧架构性能对比

图 14 新旧架构平均耗时和最终失败对比

具体数据如表 13 所示,可以看出在请求量相当的条件下,新架构在平均耗时和最终失败上都优与旧架构;并且新架构只需要 6 台机器。

备注:新架构部署上少了 3 台机器,带来单机约 50% 的内存和磁盘增长(容纳 3 份数据)。

系统可用性

实例 1:网络故障期间可用性对比

图 15 网络故障期间 PaxosStore 内存云首次访问失败率监控曲线

图 16 网络故障期间 QuorumKV 内存云首次访问失败率监控曲线

某次内网故障期间(持续约半小时),PaxosStore 内存云展现了卓越的容灾能力:图 15 为故障期间 PaxosStore 内存云的首次访问失败率监控,可以看到失败率是十分平稳的(因为网络故障期间前端请求有所降低,失败率反而小了些);与之对应 QuorumKV 内存云则表现不理想。

备注:需要强调的是得益与微信后台整体优秀的容灾设计,用户对本次网络故障感知度很低。

实例 2:某城市切换架构前后失败率对比

图 17 PaxosStore 内存云首次访问失败率监控曲线

图 18 QuorumKV 内存云首次访问失败率监控曲线

图 17 和图 18 分别给出了新旧架构 KV 首次访问失败率监控曲线(百万分之一),可以看到切换后系统的首次访问成功率从 5 个 9 提升至 6 个 9,系统可用性得以增强。

备注: 此处为极为微观的数据,前端会有简单的重试达到 100% 成功。

小结

全新的内存云存储,通过精简的 PLog as DB、分布式强一致性读写协议等一连串优化,在性能上得到显著提升;结合 DirectIO 存储系统、PLog 全量对齐等,系统首次访问失败率指标下降一个量级。

PaxosStore 是微信内部一次大规模 Paxos 工程改造实践,创新性地实现了非租约 Paxos 架构,未来还有后续文章和开源计划,敬请期待。

作者简介

魏澄,微信高级工程师,目前负责微信基础存储服务,致力于强一致、高可用的大规模分布式存储架构的设计与研发。


感谢陈兴璐对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 12 月 27 日 16:3010156

评论

发布
暂无评论
发现更多内容

作业一:食堂就餐卡系统设计

静海

UML

食堂就餐系统

focus

合并两个单向链表

食堂就餐卡设计

Bear

极客大学架构师训练营

第一周学习总结

月殇

极客大学架构师训练营

第一周 架构方法学习总结

L

Python+Appium运行简单的demo,你需要理解Appium运行原理!

清菡

架构师训练营Week1 - 学习总结

极客大学架构师训练营

食堂就餐卡系统设计

L

潮汕之旅第一站

熊斌

摄影 游记

第10周总结

第一周作业

kevin

极客大学架构师训练营

架构师训练营第一期-第一周课后作业

卖猪肉的大叔

第五周总结

架构师训练营第一章作业一:就餐管理系统UML图

zenfery

极客大学架构师训练营

架构师训练营1期作业-学习总结

道长

极客大学架构师训练营

作业二:第一周学习情况总结

静海

性能测试总结

第八周总结

架构师训练营第一周命题作业

一马行千里

极客大学架构师训练营

架构方法

Eddy.何

极客大学架构师训练营 命题作业

【架构师训练营】第一周作业:画图

MindController

架构师

架构师训练营第一期-第一周学习总结

卖猪肉的大叔

微服务架构

架构师1期week01总结

FG佳

【第一周】课后作业

云龙

极客大学架构师训练营

架构师训练营第一周作业 食堂就餐卡系统设计

帅到没朋友

极客大学架构师训练营

第一周命题作业

月殇

极客大学架构师训练营

【架构师训练营第 1 期】第一周作业

知鱼君

极客大学架构师训练营

第一周架构之UML

若水先生

极客大学架构师训练营

食堂就餐卡系统设计

jizhi7

极客大学架构师训练营

演讲经验交流会|ArchSummit 上海站

演讲经验交流会|ArchSummit 上海站

微信PaxosStore内存篇:十亿Paxos/分钟的挑战-InfoQ