一致性模型:从最终一致到线性一致
用一条订单状态链路区分最终一致、会话保证、因果一致、线性一致与事务隔离,并把抽象承诺翻译成接口语义。
“我们是最终一致的”通常还不足以让调用方写出正确代码。它没有说明用户提交订单后能否立刻读到新状态,没有说明两个有依赖的事件会不会倒序出现,也没有说明并发写冲突由谁决定。所谓一致性模型,描述的是客户端可以观察到哪些读写历史,而不是数据库内部用了多少副本。
本文沿着一笔订单的状态变化展开:创建订单、预占库存、支付成功、页面轮询订单状态。目标是回答:不同模型分别禁止了什么现象,调用方还需要承担什么,以及何时该要求线性一致。模型定义主要参照 Herlihy 与 Wing 的线性一致性论文、Jepsen 的一致性模型整理与 CRDT 论文。
先把“数据一致”拆成可观察的承诺
| 承诺 | 客户端可能看到什么 | 它没有自动保证什么 |
|---|---|---|
| 最终一致 | 停止更新后,副本最终收敛 | 刚写完能读到、依赖顺序、冲突含义 |
| 会话保证 | 自己的写不倒退、读版本单调前进 | 不同用户看到同一全局顺序 |
| 因果一致 | 有因果关系的事件不会倒序可见 | 无关并发事件的唯一顺序 |
| 线性一致 | 已完成写之后的读必须看到该写 | 多个对象一起原子更新 |
| 串行化事务 | 多操作效果等价于某个串行顺序 | 这个顺序尊重真实时间 |
这些模型不是一条简单的“弱到强”刻度。会话保证围绕一个客户端,因果一致保留依赖关系,线性一致增加实时顺序,事务模型又讨论多个对象。选择之前,应先问用户接下来会基于哪一个读结果作出动作。
图中的关键区别是支付成功写入已经返回之后,页面到底允许读到什么。若页面只展示状态,短暂读旧可能可接受;若它据此发货或再次扣款,读取路径就需要更强保证。
一、最终一致只保证“停止变化后会收敛”
订单服务的主库把 PAID 事件异步复制给只读副本。用户付款成功后刷新页面,若请求命中尚未收到事件的副本,仍可能看到 PENDING_PAYMENT。最终一致允许这一现象;只要新的写入停止、复制最终送达,副本会回到同一个状态。
这里有三个容易遗漏的条件。第一,消息必须最终送达或可被重放;第二,重复事件不能把状态推错;第三,并发更新必须有合并规则。仅有“后台同步任务”并不能推出最终一致。同步任务永久失败、事件顺序颠倒、两个副本各自接受不兼容写入时,状态可能永远不收敛。
对订单这种状态机,事件可以带版本:PAID(version=8) 只能覆盖版本小于 8 的状态;收到 CANCELLED(version=7) 时不能把订单从已支付倒退。对计数器、集合和协同编辑,CRDT 为一类可交换或可合并的数据结构提供了收敛条件;它并不会自动保护库存上限、唯一用户名或余额不为负等业务不变量。
最终一致适合把“主事实”和“派生视图”拆开。搜索索引、推荐列表、活动页计数常可异步更新;支付账本、库存所有权应保留权威写路径。真正的设计问题是旧值会被谁消费,以及旧值能否触发不可逆动作。
二、会话保证解决的是用户刚刚经历过的倒退
用户创建订单后,最难解释的体验往往不是两位用户看到不同顺序,而是同一用户刚看到“支付成功”,刷新后又回到“待支付”。这类问题可以不要求全局线性一致,而通过会话保证缩小范围。
常见的 read-your-writes 要求是:一个客户端成功写入后,后续读取至少要看到自己的那次写入。实现可以让客户端携带已见版本,服务端只从版本不低于该值的副本读取;也可以把短时间读取路由到主副本。monotonic reads 则要求一次会话看到的版本不后退,适合轮询任务状态。
POST /payments/o-1/confirm
→ 200 { orderVersion: 8, status: "PAID" }
GET /orders/o-1
→ 请求带 minVersion=8
→ 副本版本 < 8:等待、转发权威副本,或明确返回仍在同步
会话保证把等待成本集中到受影响的用户,而不强迫所有读都走最慢路径。代价是客户端、网关或服务端要保存版本上下文;如果用户换设备、丢失令牌或读到第三方缓存,保证也会改变。接口文档应该说明这个上下文怎样传递,而不是只写“最终一致”。
四种常见会话保证分别约束什么
会话一致性不是单一模型,工程里常见四类保证。Read-your-writes 保证后续读取包含本会话已经确认的写。Monotonic reads 保证一次会话看到的版本只向前推进,避免订单从 PAID 又退回 PENDING_PAYMENT。Monotonic writes 要求同一会话的写按提交顺序生效,例如先修改地址、再确认使用该地址,不能让第二次更新越过第一次。Writes-follow-reads 则要求在读取某个版本之后发出的写,必须基于不早于该版本的状态。
这四项可以独立实现。只把用户固定到一个副本,可能同时得到 read-your-writes 和 monotonic reads,但副本故障后切换到落后节点,保证就会消失。版本令牌更明确:响应返回 observedVersion=41,后续请求携带 minVersion=41;路由层选择已经追到 41 的副本,或把请求转给权威副本。
版本令牌也需要作用域。订单版本 41 与用户资料版本 41 没有可比性;一个表的日志序号也未必覆盖另一个分片。令牌应说明对象、分区或日志范围。把全局数据库时间戳暴露给客户端虽然统一,却会让内部拓扑和接口长期耦合;更稳妥的做法是由服务解释不透明 token。
移动端离线是另一种边界。设备 A 更新昵称后,设备 B 没有 A 的会话上下文,read-your-writes 并不要求 B 立即看到新值。如果产品希望跨设备也满足这一直觉,就要把“会话”提升为账户级上下文,由服务端保存用户已确认版本,成本也随之增加。
三、因果一致保留“因为,所以”的顺序
支付成功之后才会触发“允许发货”,这是因果关系。若仓库系统先看到了 ShipmentCreated,却还看不到它依赖的 PaymentConfirmed,它就无法解释这条操作是否有效。因果一致要求:凡是一个事件发生在另一个事件之前,所有观察者都必须以同样顺序看到它;两个没有依赖关系的并发事件则可以按不同顺序出现。
因果关系来自程序顺序、读后写和消息传递。客服先读取“用户已申请退款”,再添加“已同意”,后者依赖前者;两个用户同时修改各自的收货地址通常互不依赖。实现因果一致需要携带依赖版本,例如版本向量或依赖上下文,并在读取时等待缺失依赖到齐。
它比最终一致更符合人类对对话、评论回复和工作流的直觉,又不必为所有无关操作建立全局单序。代价是元数据、依赖追踪和副本等待。对只有一个中心写库的简单业务,它可能没有必要;对多区域协作或离线编辑,它能避免许多“回复先于原消息”的怪异历史。
happens-before 怎样从业务动作中产生
Lamport 的 happens-before 关系可以用三条规则理解:同一进程内前面的操作先于后面的操作;发送消息先于接收消息;因果关系具有传递性。订单服务写入 PaymentConfirmed,物流服务读取该事件后创建发货单,于是支付确认先于发货。即使两个事件由不同数据库保存,这条依赖仍然存在。
并发意味着无法从已知证据判断先后,不等于两个动作发生在完全相同的物理时刻。用户 A 和用户 B 都在未读到对方修改的情况下编辑文档,两次更新可以视为并发。系统可以用版本向量表达各副本已见的更新集合;发现两个版本互不包含时,就知道发生了并发分叉。
因果一致系统在返回一个事件之前,需要确保它依赖的版本已经可见。这可能表现为等待本地副本追平、把请求转发到拥有依赖的区域,或连同缺失数据一起返回。依赖链很长时,元数据与等待也会变大,因此实现通常按会话、分区或对象族限制追踪范围。
社交动态是典型例子:用户发布原帖后立即评论,其他读者不应先看到评论再看到原帖;两个互不关注的用户同时发帖,没有必要由全球共识决定唯一顺序。因果一致保存了产品真正需要的关系,同时允许各区域就近处理无关写入。
四、线性一致承诺单对象的实时语义
线性一致要求每个操作看起来都在调用和返回之间某一瞬间原子生效,并尊重现实时间。如果 reserve(o-1) 已经返回成功,之后才开始的 getAvailable() 不得返回预占前的库存。这并不要求所有请求经过一台机器;重叠的并发操作仍可选择任一合法顺序。
锁、Leader 租约、配置版本、余额扣减和唯一配额常需要这种语义。它们的共同点是:调用方会把读结果直接当作授权依据。例如一个节点读取到自己仍是 Leader 后发送写入,若读取落后,两个节点可能同时认为自己有资格执行。
线性一致通常依赖共识或权威副本,并在读写时进行多数派确认、租约验证或读屏障。这里的成本不只是平均延迟,也包括少数派分区时拒绝服务、故障切换窗口和容量压力。把所有展示读提升为线性一致,常会让不需要强语义的路径共同承担协调成本。
线性化点是证明工具,不一定是一行代码
分析一个实现时,通常尝试为每个成功操作找到一个线性化点。条件写可能在数据库提交时生效;基于 Raft 的写在日志条目被提交时获得可见性;锁获取可能在服务端原子比较并更新 owner 时生效。这个点必须落在调用开始与返回之间。
线性化点是对外历史的解释,不一定对应一条源代码语句。批量提交、复制确认和故障切换会让内部过程跨越多个步骤。只要能把每个已完成操作放到一个合法总序,且尊重非重叠操作的实时先后,外部观察就满足线性一致。
失败调用更麻烦。客户端超时后不知道写是否越过线性化点;系统可以已经提交,只是响应丢失。线性一致约束系统历史,却不会自动告诉客户端某次未知操作排在历史的哪一处。业务幂等键和状态查询仍然必要。
顺序一致与线性一致差在现实时间
Sequential consistency 也要求所有进程看到同一个操作顺序,并保持每个进程自己的程序顺序,但它不要求这个总序尊重不同客户端之间的现实时间。客户端 A 的写已经返回,稍后客户端 B 发起读取,顺序一致历史仍可能把 B 的读取排到 A 的写之前;线性一致禁止这种解释。
这个差异对锁、配置开关和所有权非常重要。管理员看到“禁用功能”已经成功后,新请求还按旧配置执行,会违反线性一致的直觉。对离线分析或无实时决策的流水线,顺序一致可能已经足够。模型强弱要和调用方的时间假设对应。
读写寄存器只是起点
论文常用单个 register 解释线性一致:write(x) 与 read() 的历史比较容易检查。真实服务中的“对象”可能是一个 Key、一行记录、一个分片,或者被事务封装的一组记录。只有先声明对象范围,线性一致承诺才完整。
库存服务若分别对 available 和 reserved 两个字段提供线性一致读写,却允许调用方分别更新,仍可能打破 available + reserved = total。把两字段放进一个原子对象或事务,才能保护跨字段不变量。扩大对象范围会增加冲突和协调成本,因此边界应该跟着业务不变量划定。
五、线性一致不能替代事务
设账户 A 扣 100,同时账户 B 加 100。即使 balance[A] 和 balance[B] 的每一次单 Key 读写都线性一致,客户端仍可能在中间看到 A 已扣款、B 未入账。两个对象要作为一个整体生效,需要原子事务或显式状态机。
串行化(serializability)要求多个事务的效果等价于某种串行执行;这个顺序未必尊重墙上时间。strict serializability 同时要求事务串行化并尊重实时顺序,可以理解为事务层面的强实时语义。快照隔离、可重复读和读已提交又有不同的异常集合,不能因为数据库写着“事务”就默认它们等价。
对跨服务下单,常见做法是把本地事务的边界保持在一个服务内:订单表和 Outbox 一起提交;库存、支付通过幂等命令和状态迁移协作。Saga 不是把分布式操作变成一个隐藏的全局事务,它要求每一步的确认、失败和补偿都可观察。
隔离级别决定事务之间能看到什么
原子性回答一个事务的修改是否整体提交,隔离性回答并发事务怎样互相可见。Read Committed 通常避免脏读,但同一事务两次查询可能得到不同结果;Repeatable Read 约束已读行,仍需结合具体数据库判断幻读与写偏差;Snapshot Isolation 让事务从一致快照读取,却可能允许两个事务基于同一旧快照分别提交,形成 write skew。
值班排班是常见 write skew:规则要求至少一名医生值班。两名医生的事务都从快照看到对方在岗,于是各自将自己改为休假;它们更新不同行,可能同时提交,最后无人值班。每一行的读写都没有脏数据,业务不变量仍被破坏。Serializable 要求结果等价于某个串行执行,其中至少一个事务必须失败或看到另一个更新。
Strict serializability 再加上实时约束。如果事务 T1 已完成后 T2 才开始,等价串行历史必须把 T1 放在 T2 前。它结合了事务串行化与线性一致的时间直觉。讨论“数据库强一致”时,至少要说明是单对象线性一致、快照隔离、Serializable,还是 Strict Serializable。
数据库一致性与 CAP 的 C 不是同一个 C
ACID 中的 Consistency 指事务把数据库从一个满足约束的状态带到另一个满足约束的状态,约束由 schema、触发器和应用共同定义。CAP 中的 C 在形式化讨论中接近单对象原子一致性,也就是线性一致。两者同名,问题范围不同。
一个数据库可以在单节点事务中保持外键和余额约束,却让异步副本返回旧值;它满足应用定义的事务一致性,并不提供副本读的线性一致。反过来,一个分布式 Key-Value Store 可以让单 Key 操作线性一致,却不知道“账户余额不得为负”这一业务规则。命名相似不能替代契约。
六、把模型翻译成接口,而不是形容词
下面的订单接口比“采用最终一致架构”更有用:
GET /orders/{id}?consistency=display
允许:最多 5 秒滞后;响应含 version、asOf
用途:订单列表、通知中心
GET /orders/{id}?consistency=action
要求:至少读到客户端携带的 minVersion
用途:确认支付后的跳转、取消前校验
POST /orders/{id}/ship
要求:在权威状态上检查 PAID,使用 idempotency-key
超时:返回 UNKNOWN;必须按业务键查询结果,不能盲目重放
一个接口可以按操作选择不同保证,但它必须公开选择。展示读的 asOf 让前端能显示更新时间;minVersion 给会话保证一个传播载体;写操作的幂等键处理结果未知。强一致数据库也需要这些契约,因为网络超时后客户端仍可能不知道命令是否已提交。
七、并发历史要用调用与返回来理解
一致性模型讨论的是一段带有调用和返回的历史,某一个时刻的数据库快照不足以判断它。下面两次操作重叠:用户 A 开始把订单改为 CANCELLED,用户 B 在 A 返回前开始读取。线性一致允许 B 读到取消前或取消后,因为两个操作的时间窗口交叠;系统可以把它们排成任一顺序。
时间 →
A: invoke cancel ──────────────── return OK
B: invoke get ─ return PENDING_PAYMENT
如果 B 的读取开始在 A 返回之后,读取再返回 PENDING_PAYMENT 就不符合线性一致。这个区别解释了为什么只看最终订单已经取消无法证明系统正确:最终快照相同,两段历史的实时约束完全不同。
因果一致的判断也类似。客服系统中,操作员先读到“用户申请退款”,再写下“退款已批准”;另一位操作员可能同时更新地址。所有副本都应让“申请”先于“批准”可见,但地址更新可以在不同副本呈现不同先后。把无关并发也强行排进一个全局顺序,会增加协调;遗漏真正依赖,则让工作流变得不可解释。
因此,接口日志至少要区分请求 ID、业务键、客户端会话、调用时间、完成时间、返回版本和实际读取副本。没有这些字段,事后只能知道数据最后是什么,无法判断用户当时看见的历史是否违反承诺。
八、读写路径怎样承载这些保证
不同承诺对应不同的实现入口。最终一致通常由异步复制、消息日志或变更数据捕获驱动,关键是可重放、去重和监测积压;会话保证需要把已见版本随 cookie、token 或请求头传递;因果一致需要保存依赖上下文;线性一致则常通过 Leader、quorum 或读屏障找到一个不会落后的观察点。
不要把实现名词当成承诺本身。读主库经常能实现 read-your-writes,但主库故障切换后仍要定义版本和重试语义;quorum 的读写组合可能提供很强的可见性,也仍需检查成员变更、临时副本和读修复;消息队列保证顺序也常只在一个分区键内成立。对外可承诺什么,要从实际读写路径和故障切换路径推导。
缓存尤其容易模糊边界。若订单详情经过 CDN 或应用缓存,写库成功并不意味着缓存已经失效。可以让动作页绕过缓存、让缓存响应携带版本或更新时间、在状态迁移时主动失效;不能只把缓存 TTL 缩短然后声称接口已经线性一致。TTL 只是一个过期上界,不能建立读写的实时顺序。
主从复制中的读选择
单主异步复制常提供几种读路径。读主节点能减少写后读旧值,但故障切换期间新主是否包含旧主已经确认的全部写,取决于提交规则;读任意副本延迟低、容量大,却需要接受滞后;等待副本追到某个日志位置可以实现版本下界,但慢副本会把延迟暴露给请求。
同步复制也不是一个布尔开关。写入等待一个同步副本、等待多数派,或等待所有区域,分别对应不同故障容忍和延迟。一个副本收到数据但尚未持久化,与已经刷盘的确认含义也不同。接口的“成功”要映射到实际确认条件。
Quorum 公式成立还不够
固定 N 个副本中,若写入 W 个、读取 R 个,并且 W + R > N,读写集合必有交集。但交集只保证读到某个参与过写的副本,不会自动处理并发版本、旧节点重新加入、成员变化和 sloppy quorum。读取还要比较版本并选择合法结果,写入要确保参与集合定义稳定。
因此 quorum 是实现模型的一块积木。要证明线性一致,还要说明版本顺序、并发写裁决、故障转移和读取修复。只把参数设成 QUORUM,不能替代对实际协议的分析。
九、完整走一遍订单从写入到展示
用户支付订单 o-1,支付服务拿到渠道成功回执后,在本地事务中写支付记录与 Outbox 事件,订单服务消费事件并把订单从 PENDING_PAYMENT 改成 PAID(version=8)。页面随后读取订单,仓库系统稍后创建发货单。
支付账本需要保护重复回调和金额不变量,通常用本地事务、渠道流水唯一约束与幂等状态机。订单更新可以通过至少一次消息到达,因此消费者按 eventId 去重,并用订单版本防止旧事件倒退。此时“支付成功”和“订单页已显示成功”不是同一个提交点。
页面展示可以读取异步副本,响应携带 version 与 asOf。支付完成页持有版本 8,下一次请求携带 minVersion=8;若本地副本只有 7,服务端等待短暂追平或转到主节点。普通历史列表没有这个令牌时,可以接受几秒滞后。
仓库创建发货单的动作不能依据任意展示副本。它消费 OrderPaid(version=8),并要求其依赖的支付事实可查询;创建发货单使用 orderId 幂等。即使用户页面仍显示旧状态,仓库链路也不会因此重新扣款;即使消息重复,发货单也只产生一个业务结果。
这条路径同时使用多种模型:账本事务保护本地不变量,订单投影最终收敛,支付完成页获得 read-your-writes,发货事件保留因果顺序,库存所有权使用线性一致条件写。把整套系统概括为“最终一致”或“强一致”会丢掉真正影响调用方的差异。
十、模型选择需要同时看错误代价和协调代价
强模型减少调用方可能遇到的历史,代价是更多协调、较高尾延迟和分区时拒绝。弱模型允许本地处理与异步复制,代价转移到版本传播、冲突合并、用户体验和补偿。不存在脱离业务后果的最佳模型。
配置中心、锁和唯一资源所有权通常不能容忍读取倒退;支付完成页至少需要读己之写;评论回复和工作流常需要因果顺序;搜索索引与统计计数适合异步投影。即使在同一产品内,不同路径也会做不同选择。
选择较弱模型时,要写出额外机制:最大陈旧度、版本标记、会话 token、冲突规则、收敛 SLO 和人工修复。选择较强模型时,则要写出无法形成 quorum 时的响应、deadline、容量上限和降级范围。两边都需要明确的失败语义。
十一、怎样验证承诺没有停在文档里
一致性模型不能只通过“最终数据库快照正确”验证。检查线性一致需要记录每次调用的开始时间、完成时间、输入和返回值,再判断是否存在一个符合实时顺序的线性化点。Jepsen 的测试方法正是收集这种历史,在网络分区、进程终止和时钟异常下检查历史是否合法。
对最终一致和会话保证,测试指标不同:复制延迟分位数、版本倒退次数、消息积压、无法在 SLO 内收敛的业务键数量。可以在测试环境故意让一个副本延迟,验证页面是否显示旧版本标记、minVersion 是否转到权威读、重复事件会不会把订单状态倒退。
陈旧度最好同时按时间和版本衡量。副本落后 2 秒,可能只缺一条低频更新,也可能在高峰期缺少数千条日志;只看复制队列长度,又无法直接判断用户看见的数据有多旧。响应返回 asOf 与版本下界,监控记录副本 replay position,才能把基础设施延迟映射到接口承诺。超过陈旧度 SLO 后,系统可以转发权威读、显示同步中,或拒绝会触发不可逆动作的请求。
最重要的是把故障后的人工路径也测进去。订单卡在 PENDING_CONFIRMATION 时,谁能查询库存预占,谁能发起对账,补偿是否会重复释放。模型规定客户端可见历史,恢复流程决定异常历史能否回到可解释的终态。
测试历史需要保留 invocation 与 completion
假设测试只记录“写 1 成功、读到 0、最终值为 1”。我们无法判断读是在写开始前、写执行期间,还是写返回之后发生。前两种情况在线性一致模型中可能合法,最后一种才是明确违反。测试客户端必须分别记录操作发起和完成事件,并使用同一个逻辑客户端标识程序顺序。
历史检查器会尝试寻找一个合法串行顺序。对 register,写改变当前值,读必须返回该位置之前最近的写;对 compare-and-set,还要检查比较条件和返回结果。操作并发较多时,可能顺序组合会迅速增长,检查器通常利用模型约束剪枝。超大历史可以按对象拆分,但拆分前必须确认模型本身支持按对象组合。
测试也要区分 fail 与 info。明确业务拒绝可以记为 fail,表示操作没有生效;连接断开后的结果未知应记为 info,因为操作可能已经生效。把所有异常都当成未执行,会让检查器接受一段实际上重复写入的历史。测试代码的错误分类和生产客户端一样重要。
故障注入要覆盖提交点前后
只随机杀进程,往往无法稳定命中最有价值的窗口。可以分别在服务端接收请求后、日志追加后、复制到少数副本后、达到 quorum 后、响应写出前注入暂停或崩溃,观察新 Leader 和客户端如何处理。每个注入点对应不同的“可能已提交”状态。
网络测试不应只有彻底断网。单向分区、丢包、延迟、乱序和连接仍存活但消息不前进,会触发不同的超时与选主路径。磁盘暂停、线程池耗尽和长时间 GC 也可能让一个节点从外部看起来像网络故障。模型关心可观察历史,不关心故障来自网线还是运行时停顿。
对会话保证,可以让同一虚拟用户在不同副本间切换,持续写入递增版本并读取,检查 read-your-writes 和 monotonic reads;对因果一致,可以生成“发帖后回复”的依赖图,同时混入无关并发事件,检查依赖是否倒序;对最终一致,要在停止新写后观察所有副本是否于约定 SLO 内收敛。
版本号解决顺序,时钟只提供部分证据
物理时间戳看起来直观,但机器时钟存在偏差和回拨。用客户端时间直接做 Last Write Wins,时钟快的设备可能长期覆盖后来发生的更新。NTP 能缩小误差,不能把分布式时钟变成绝对真相。
逻辑时钟描述已知顺序。Lamport Clock 可以保证若 A happens-before B,则时间戳 A 小于 B;反方向不成立,较小时间戳不能证明存在因果。版本向量能识别一个版本包含另一个版本,或二者并发,代价是维护每个参与者的计数,成员多且动态时元数据会膨胀。
混合逻辑时钟把物理时间与逻辑计数结合,便于按近似现实时间查询,同时在时钟误差内保持逻辑顺序。无论使用哪种版本,接口都要说明它比较的范围:一个对象、一个分片、一个区域日志,还是全局事务时间。脱离范围比较两个数字没有意义。
线性一致本身不要求每台机器时钟同步。协议可以靠消息、quorum 和任期建立顺序。若实现用租约减少读取协调,就必须把时钟漂移、租约到期和进程暂停纳入安全证明;否则旧 Leader 可能在长暂停恢复后继续用过期租约服务请求。
十二、选择时先写下这五个问题
- 这次读取是展示,还是下一步授权或扣费的依据?
- 用户是否必须读到自己的写,版本上下文怎样传递?
- 哪些事件存在因果依赖,能否接受倒序展示?
- 哪个对象必须像只有一份权威副本那样工作?
- 多个对象要一起变化时,本地事务、状态机和补偿各由谁负责?
一致性模型的价值在于把“偶尔不一致”变成可验证的边界:谁可以读旧、旧到什么程度、什么时候必须等待、冲突如何收敛。没有这些答案,选择一个听起来更强或更弱的模型都无法替接口承担责任。
评审结论最终应落实到接口字段、状态机、监控和故障测试,而不是停在数据库产品标签上。
参考资料
- Maurice P. Herlihy, Jeannette M. Wing, Linearizability: A Correctness Condition for Concurrent Objects
- Jepsen, Consistency Models
- Marc Shapiro et al., Conflict-free Replicated Data Types
- Kyle Kingsbury, Jepsen
如果这篇文章对你有帮助