ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Megastore深度解读:实体组、Paxos与跨地域多写的一致性实现

Megastore深度解读:实体组、Paxos与跨地域多写的一致性实现 多年以前我第一次读到 Megastore 相关论文的时候第一反应是“这名字起得真够直白”——它确实就是一个试图让数据库在全国乃至全球各地都能写入的存储系统。和传统数据库那种“写主库、读从库”的玩法不同Megastore 想做的事情是你在北京写一条数据在上海、广州甚至海外的机房也能立刻读到一致的结果反过来任何地方也都能发起写入而不必把流量都导到一个总节点。放到今天来看这个思路几乎成了分布式数据库的标配但放在那个年代它是相当激进的设计。这篇博文会围绕 Megastore 的标题展开适合三类读者一是正在做分布式系统选型或者被面试题里“多活”“强一致”“异地多写”这些词折磨的工程师二是想理解 Google 内部那套存储体系演进脉络、但又啃不下原论文的人三是纯粹对“数据库到底能不能跑在多个机房”这个问题好奇的技术爱好者。我会把它的核心思路拆开揉碎讲清楚它为什么能多写、靠什么保持一致、代价是什么中间也会穿插一些我自己的理解和踩坑式的经验希望对你有实际帮助。1. 先说清楚Megastore 到底解决什么问题1.1 单一写入点的痛如果你维护过任何一套正经的 MySQL 或 PostgreSQL 高可用架构你大概率对下面这套流程很熟主库扛写入从库提供读流量主库挂了就切换切换期间可能丢一点数据也可能要人工介入。这套方案用了很多年绝大多数中小团队也够用。但它有一个底层限制写入必须落在同一个地方。只要写入点只有一个你就永远绕不开两个问题。第一个是地理距离带来的延迟用户在北京主库在上海跨地域的每次写请求都要走一遍物理光纤延迟再低也有几十毫秒而且不稳定。第二个是容灾问题主库所在的机房整体故障时切换过程非常痛苦哪怕你做了跨机房的从库同步也常常面临“数据要不要丢”的取舍。很多团队做过“异地多活”最后发现网络分区、时钟偏差、冲突解决这些坑一个比一个深能把人磨到怀疑人生。Megastore 就是想从根上解决这个“单一写入点”问题。它允许每个部署点都可以接受写入而且引入了一套基于 Paxos 的同步复制机制让所有副本长期保持一致。这套思路放在今天看是很多 NewSQL 数据库的标配但 Megastore 是早期把“跨地域多写”和“强一致”同时做到工程可用的代表之一。1.2 多地写入为什么难你可能觉得奇怪多地写入不就每个机房都能写吗但真正做起来难的是后半句——“同时保证一致性”。先说一个最基础的冲突场景。用户在北京的机房把自己的昵称改成“老王”同一秒在纽约的机房又把昵称改成“老李”。两边的写入都成功了那数据库里最终该存谁的结果如果答案是“先到先得”那“先到”怎么定义以哪个机房的时钟为准如果答案是“按主键覆盖”那输掉的一方要不要报错这些问题没有一个简单的答案。再看一个更隐蔽的问题。假设一个电商订单的创建和库存扣减这两个操作分别落在两个机房的数据库实例上。创建订单成功但扣库存失败整个系统就出现了不一致。传统数据库靠本地事务解决跨机房的场景下分布式事务的成本极高网络可能随时断开参与节点可能挂掉事务协调者也可能是瓶颈。Megastore 的答案是不搞通用分布式事务而是把数据切分成小粒度实体组每个实体组内部的写入走单点 Paxos跨实体组的事务则提供尽力而为的约束。这个设计很务实后面我会详细拆。还有一个容易被忽略的问题是时区、时钟和因果顺序。两个节点上的事务如果都依赖本地时间来判断先后时钟一飘就出乱子。Megastore 用 Paxos 产生的日志顺序来定序而不是依赖物理时钟本质上就是告诉系统“谁先谁后”不是由时间戳决定的而是由一致性协议决定的。这个点特别重要也是很多人一开始读论文时容易绕进去的地方。1.3 为什么要读 Megastore 这篇论文放在 2025 年回头看Megastore 本身已经不再是最前沿的系统了它的后继者 Spanner 在架构上做了大量演进而且业界也出现了更多采用 Raft 的分布式数据库。但这不代表 Megastore 不值得读。它是最早把“类 SQL 数据模型”和“跨数据中心强一致复制”结合并工程化的系统之一。绝大多数工程师对 Paxos 的认知停留在理论层面而 Megastore 展示了 Paxos 在真实产品里怎么打折、怎么重试、怎么做快照这是非常珍贵的工程细节。它不是那种完美到不可落地的论文恰恰相反它老老实实承认了自己的限制——吞吐量有限、跨实体组事务偏弱——这种诚实对后来者非常有价值。我自己的经验是如果你能理解 Megastore 为什么在 Bigtable 之上加了一层实体组和 Paxos再去看 Spanner、TiDB、CockroachDB 的设计思路会清晰很多。因为它们的核心也都是“KV 存储 一致性协议 事务层”的三明治结构只是每一层的选型不同而已。2. 架构速览站在 Bigtable 肩膀上的存储层2.1 Bigtable单数据中心内的分布式 KV 底座在讲 Megastore 之前必须稍微铺垫一下它的地基——Bigtable。Bigtable 是 Google 内部用来存海量结构化数据的分布式 KV 存储它把数据按行键排序切分成 Tablet分布在大量服务器上同时通过 Chubby 做协调和元数据管理。Bigtable 的写入接口很简单但伸缩性很强支持百万级 QPS 级别的扩展能力。但 Bigtable 有个天性它是为单数据中心设计的。这里的“单数据中心”不是指它只能部署一个机房而是指它的一致性模型和容灾能力是围绕单个集群设计的。跨数据中心复制在 Bigtable 上不是没有但通常采用异步复制也就是一个机房作为主其他机房作为从。一旦主机房出问题实际能提供服务的还是主集群其他机房的数据可能落后。Megastore 的聪明之处在于它没有另起炉灶写一套完整的分布式数据库而是决定在 Bigtable 之上“包装”出一层支持跨副本同步的逻辑层。这就像是在一个单机文件系统之上做了一个分布式锁服务从而让上层看起来像是一个多副本强一致的数据库。这种分层复用的思路极大地降低了构建成本但也在性能上留下了一些代价。2.2 副本组与 Paxos一致性从单点走向多点Megastore 把数据复制到多个数据中心每个数据中心内部有一组 Bigtable 副本这些副本组成一个“副本组”。副本组之间的同步不是靠 MySQL 那种 binlog 异步推送而是靠 Paxos 协议在每个实体组上做日志复制。这里要特别强调一个点Megastore 不是对整个数据库跑一个全局 Paxos而是把数据划分成海量小实体组每个实体组各自跑一份独立的 Paxos。你可以把实体组理解成“数据库里的一小片数据”它可能是一个用户的所有信息也可能是一个订单及其明细。每个实体组有自己独立的 Paxos 日志和状态机。这个设计的优势非常明显不同实体组的 Paxos 互不影响某个实体组的 leader 挂了其他实体组照常服务你可以把不同实体组分布在不同的物理节点上天然实现了负载均衡。代价则是 Paxos 实例的数量极其庞大而且每个 Paxos 组都要维护自己的副本状态复杂度并不低。2.3 数据模型从 Bigtable 行到 Megastore 实体组Megastore 在数据模型上给用户提供的是一种“类关系型”的表结构有 schema有行有索引支持事务。但底层存储仍然是 Bigtable 那种列簇式的 KV。每一个 Megastore 表都会被映射到 Bigtable 的一张物理表上。主键就相当于是 Bigtable 的行键列则相当于 Bigtable 的列。实体组则通过一个“实体组根键”来划分同一个实体组的所有行在物理存储上共享相同的前缀这样它们会被 Bigtable 放在相邻的位置读写时可以利用局部性。举个例子。一个社交应用里有一个 User 表和一个 Message 表。我们可以把 User 表的主键作为实体组根把 Message 表设计成 User 表的子表子表的主键里带上父表的 UserId。这样一来同一个 User 的所有 Message 行物理上聚集在一起任何一个用户的数据都被完整地放进了同一个实体组。对这个实体组的所有写入都只需要在组内跑 Paxos就可以保证一致性。这个“实体组”设计是整个 Megastore 的灵魂。它巧妙地把“分布式一致性的范围”压缩到了很小的粒度从而避免了全局事务的性能地狱。对于大量只访问单个用户数据的应用场景来说这个模型几乎是为它们量身定做的。3. 核心机制拆解Paxos、实体组与事务范围3.1 Paxos 怎么在多个数据中心之间达成一致Paxos 是个经常被人提起但很少被人亲手实现的协议。Megastore 里的 Paxos 用法很有意思它没有采用那种“每一轮写入都要选一个新 leader”的做法而是尽量维持一个长期 leader让它连续处理多条日志。回看 Megastore 论文里的描述写入流程大概是这样的客户端把写入请求发给某个副本的 coordinator。coordinator 作为 Paxos leader把自己的写入操作追加到实体组的日志中。Paxos 的 acceptor分布在各个数据中心的副本对日志位置进行投票多数派返回成功。一旦多数派确认日志就被 commit每个副本各自 apply 到本地 Bigtable。写入请求同步返回客户端成功。这个流程听起来不复杂但工程里有大量细节。比如Paxos 的 leader 必须记录自己已经提交到的日志位置否则重启后可能重复提交又比如如果 leader 宕机新的 leader 必须知道自己之前提交了哪些日志才能安全地继续推进。Megastore 的做法是让每个副本都保存一个“已知已提交到哪”的指针配合特定的回放逻辑保证不会出现两个副本对同一个日志位置做出不同的决定。我必须提醒你如果你打算自己实现 Paxos只看论文是不够的一定要去搜工程实现的博客和代码。Paxos 的坑在于“活锁”“脑裂”“重复日志”这些词听上去很简单一旦落到真实网络环境下你会被 timeout、重试、乱序这些现实问题折磨疯掉。Megastore 论文的好处是它给出了不少可落地的妥协方案这正是它比教科书有价值的地方。3.2 实体组物理相邻、逻辑隔离的小世界实体组的设计解决了两个关键问题事务边界和性能边界。事务边界非常好理解——同一个实体组内的多行写入可以放在同一个 Paxos 日志位置里由同一轮 Paxos commit因此实现了原子性。跨实体组的事务则无法享受这种保障。所以你在设计 Megastore schema 时必须尽量把“需要一起修改的数据”放在同一个实体组里否则应用层要自己处理部分失败。性能边界的含义则更微妙。Paxos 的每次 commit 都需要和多数派通信通信次数越多成本越高。如果两个实体组在同一个 Bigtable 节点上它们的 Paxos 是独立的写入可以并行但如果两个实体组需要跨数据中心同步那么每个实体组的每次写入都要走一次跨地域网络。Megastore 的吞吐量上限因此远低于单机数据库这也是它后来被 Spanner 取代的原因之一。我看到很多人初次接触实体组时容易把它和“分库分表”混淆。分库分表是把不同的表放到不同的物理库上表之间没有层级关系实体组则是把一组逻辑相关的行捆绑到一个物理单元。分库分表解决的是扩展性问题实体组解决的是“隔离事务范围”的问题两者不在一个维度上。3.3 读路径的三种选择强一致、快照与会话一致一个经常被忽略但实际影响很大的设计是 Megastore 的读机制。它没有规定所有读都必须走 Paxos而是提供了三种方式强一致Current读需要读取小区块的最新值。实现方式是先确认当前实体组的日志已经复制到哪个位置再读取。这种方式延迟最高但能保证完整的一致性和实时性。快照Snapshot读读取某个时间点的数据允许读到旧一点的版本。代价是可能读不到最新写入但性能最好。会话一致Bounded读结合了前两种读取时可以接受读到旧数据但保证不会读到比用户自己上一次写入更旧的数据。这个设计理念很像我们今天常说的多副本一致性模型的分级不同业务场景对一致性的要求不一样没必要全部用最贵的方案。比如商品详情页可以接受快照读但下单扣库存就必须强一致。Megastore 把这个选择权交给了开发者而不是一刀切强制大家最高规格运行。我个人非常喜欢这种“分级一致性”的思路它才是工程落地的常态。现实中的系统几乎没有哪个会百分之百用线性一致读总有某些业务可以容忍短暂滞后。大家在面试时如果提到 Megastore能够把这三个读模式讲清楚会比单纯背概念高不少分。3.4 写路径与日志回放认真对待每一条日志Megastore 的写路径本质上是“把日志复制到多数派然后每个副本回放日志”。这个模型有一个隐藏麻烦如果一个副本落后太多回放日志就要花很长时间如果日志本身被清理掉了落后副本就无法恢复。因此Megastore 会定期把状态做快照新副本或者落后副本可以通过“快照 日志回放”的方式追上来。这个思路其实和 WALWrite-Ahead Logging、复制状态机Replicated State Machine是同一套脉络。分布式数据库基本都逃不开这三个词日志、快照、回放。Megastore 的贡献在于把日志按实体组拆分使得不同实体组可以独立前进、独立回放。这个微创新让整个系统在不同数据分区上的可用性不再互相绑架。工程实现上还有一个容易被忽视的细节日志回放必须处理幂等性。如果一条日志被两个副本同时执行了两次结果不能错。Megastore 的处理方式是在写入时给每条日志一个唯一的标识回放时通过 Bigtable 的原子写来保证重复执行不会产生副作用。很多分布式数据库没有处理好这个细节最终在故障恢复时暴露问题所以这个“幂等”的点非常值得大家写代码时注意。4. 怎么评价这套设计优势与代价并存4.1 Megastore 做对了什么第一个做对的地方是它承认了全局强一致性的代价所以用实体组把一致性范围缩小到可管理的粒度。这是一个极具工程智慧的取舍。对绝大多数业务来说90% 的事务都只涉及单用户或者单订单的数据。把这些小范围事务做成强一致把跨范围事务尽量规避整体上就能提供接近单机数据库的体验。第二个做对的地方是它把“跨数据中心复制”从应用层下沉到了存储层。应用不再需要关心如何同步各机房数据、如何检测冲突、如何手工切换——这些都被内置的 Paxos 复制解决了。尽管 Megastore 的吞吐量不高但它的高可用性让很多产品做到了“任何机房可读可写”这是非常关键的业务价值。第三个做对的地方是它给出了具体可行的 API 和 schema 设计建议。大家在读论文时会看到它对实体组根、子表、局部索引的设计做了很详细的说明。它不是只给抽象模型而是给了开发人员一整套编码规范这种“设计模式”式的论文在系统工程领域非常少见。4.2 那些让人头疼的限制Megastore 最被诟病的限制是吞吐量和延迟。因为每个实体组的每次写入都要走一轮跨数据中心 Paxos一次 commit 的网络往返次数远高于单机数据库。延迟方面跨地域写可能达到数百毫秒吞吐方面也很难应对高频写入的场景。Google 内部实际上把它用在 AppEngine 等场景而不是超大规模在线交易系统。另一个限制是跨实体组事务的弱保证。虽然 Megastore 提供了跨实体组事务的一些支持但这部分事务需要额外的协调机制而且一旦出现分布式故障回滚并不总是干净利落。论文里也承认跨实体组事务无法提供完整的 ACID而只能提供尽力而为的约束。如果你需要一个真正意义上面向通用业务的全分布式事务数据库Megastore 并不是一个好答案。还有一个实际问题运维复杂度。实体组、副本组、Paxos 日志、快照回放每一样都不是省油的灯。如果你没有一支足够强的分布式系统团队日常运维 Megastore 的难度会非常高。这也是为什么后来 Google 升级到 Spanner 之后大幅简化了系统运维工作——因为 Spanner 通过 TrueTime 和统一分片把一致性问题封装得更彻底。4.3 数据库热词背后今天的存储系统还在解决同样的问题仔细看时下数据库领域的流行词向量数据库、NewSQL、存量数据同步、数据库并发锁、死锁排查、连接池优化甚至国产数据库、人大金仓、达梦这些产品。它们的名字千差万别但底层要解决的命题其实和 Megastore 高度重叠如何在高并发下保持数据一致如何在分布式环境下提供可靠的写入如何在故障时不丢数据不死锁。我经常和同事说数据库领域的创新很少是“全新问题”更多是“老问题的新解法”。Megastore 提出了“实体组 Paxos”的解法Spanner 把它演进为“全局分片 TrueTime”TiDB 和 CockroachDB 用 Raft 实现了类似的多副本强一致。向量数据库则聚焦在相似性搜索和向量索引上。如果你能把 Megastore 读明白再看这些系统的架构文档很容易找到它们各自的“实体组”是什么、“Paxos”是什么、“事务边界”划在哪里这种迁移能力才是读论文最大的收获。5. 从论文到工程学习 Megastore 的实操建议5.1 先别急着抠代码先抓四件事如果你决定去啃 Megastore 论文我建议不要按顺序硬读。先抓四个核心概念把它们之间的关系串起来实体组的定义哪些物理行属于同一个实体组根键怎么设计Paxos 日志每个实体组如何维护自己的复制日志写入如何 commit事务边界事务能覆盖哪些数据哪些操作不能放在同一个事务里读模式强一致读、快照读、会话一致读分别适用什么场景把这四个概念搞清楚再回头读论文的架构和实现细节效率会高很多。很多人读分布式论文容易钻入协议细节里出不来其实先建立“分层思维”——存储层、复制层、事务层、接口层——才是最重要的。5.2 用什么工具和测试环境做验证要让 Megastore 里的概念真正在脑海里落地光看论文是不够的建议动手做点小实验。要求不高你只需要一台内存足够大的普通服务器或者一台云主机。装一个单机 Bigtable 模拟器Google 的 Bigtable emulator 可以用来模拟底层 KV 行为方便你理解行键、列簇和 Tablet 的概念。用 Raft 类框架做状态机复制实验比如 etcd 或者 Raft 的开源 Java/Go 实现可以帮你直观感受“日志复制 状态机回放”的机制。搭建一个简单的多副本复制环境用 Docker 起三个 MySQL配置半同步复制或组复制观察主从切换时一致性的边界。虽然没有 Paxos但能帮你理解“多数派”和“故障切换”的现实意义。如果你不想搭真实环境也可以去跑一些分布式数据库的本地模式比如 CockroachDB 的 single-node 模式或者 TiDB 的 playground 模式通过 SQL 操作来体验分布式事务和快照隔离。用这些现代工具反向验证 Megastore 论文里的设计也是一种很好的学习方法。5.3 动手小实验用开源工具模拟“实体组”的思路很多人问单机环境下能不能模拟实体组其实可以。做一个简单的实验建两张表一张user一张user_message。把user_id作为根键把user_message的主键设计成(user_id, message_id)。这样同一个用户的消息在物理上一定相邻。在单机 MySQL 里你感受不到分布式的好处但你可以通过 EXPLAIN 看到带user_id前缀的查询天然就能做局部索引扫描。把这个 schema 迁移到 CockroachDB 或 TiDB 上创建同样的表。分布式数据库会自动把主键前缀相同的数据尽量调度到同一个 Range 或 Region 里。你会发现当你对同一个用户的多行数据进行事务操作时性能明显优于跨用户的事务。这个实验能让你直观感受到“物理局部性对分布式事务的影响”。如果你有精力还可以写一个简单的多进程程序模拟多节点同时写入同一实体组观察冲突和重试。真实的分布式环境中网络抖动、超时重试是常态单机模拟永远无法完全复现。但这个过程能让你把“Paxos 只是选择日志顺序的方式而不是解决所有分布式问题的银弹”这句话理解得更深刻。5.4 常见问题读 Megastore 时绕不开的坑“Megastore 是不是就是 NewSQL” 不完全是。它确实提供了 SQL-like 接口和分布式事务但跨实体组事务很弱而且吞吐量不高。它更像是“半 NewSQL”。理解它的局限比给它贴标签更重要。“Paxos 和 Raft 我到底该学哪个” 从实用角度看绝大多数现代系统用 Raft 实现因为 Raft 更好理解也更好工程化。但 Megastore 论文用的是 Paxos学习重点是理解“多数派提交”和“日志顺序”这两个概念具体用哪个协议实现并不影响架构思想。“实体组是不是一定要按用户维度划分” 不一定。实体组的本质是“共同生命周期”的数据集合。在订单系统里一个订单的所有明细可以是一个实体组在 IoT 场景里一个设备的所有读取记录可以是一个实体组。维度设计的目标永远是让高频事务落在同一个实体组内。“跨地域多写和异步多写有什么本质区别” 异步多写允许各节点临时不一致最终通过补偿机制收敛同步多写如 Megastore则保证每次写入后所有多数派节点立刻一致。前者的延迟更低、可用性更高但一致性问题必须上层处理后者的延迟更高但一致性体验更接近单机。两者没有绝对优劣只有场景适配。5.5 一些更实际的思考什么时候该借鉴 Megastore这篇文章写到这里你可能会问既然 Megastore 已经过时今天还有多少实际参考价值我的看法是它的核心设计思想仍然活在很多现代系统里只是换了外衣。如果今天的你正在设计一套跨机房多活架构可以借鉴实体组的粒度隔离思路而不是一上来就上全局分布式事务。把业务拆成若干“领域聚合根”每个聚合根内部做强一致复制聚合根之间用异步事件或补偿事务处理最终一致性——这套朴素的方法论就是实体组思想的现代版。如果你在做数据中间件或数据库内核可以借鉴 Megastore 的读分级设计给不同一致性要求的请求提供不同的读路径避免所有流量都走最昂贵的强一致链路。性能优化很多时候不是靠堆硬件而是靠分场景降级。如果你只是偶然看到这个标题和“各地写入”几个字被吸引过来那么我希望这篇文章至少帮你建立起一个认知跨地域写入不只是“网络好就能做”的事它背后是协议、日志、快照、故障恢复、事务边界等一系列复杂工程问题的组合。理解了这些问题你对分布式数据库的理解会上一个台阶。我在实际实践中体会最深的一点是Megastore 最大的价值不在于“它是多么先进的数据库”而在于它示范了如何在一片充满理论问题的领域里做出一个务实可用的系统。它把难题分成小块每一块都找到妥协方案最后在可接受的损失内拿到了工程上的胜利。这种思维方式比记住任何一张架构图都值钱。
返回列表