再论 CAP:会议和实时通信为什么要选 AP,以及最终一致性怎么落地

Posted on 六 10 10月 2026 in Tech

Abstract 再论 CAP:会议和实时通信为什么要选 AP,以及最终一致性怎么落地
Authors Walter Fan
Category Tech
Version v1.0
Updated 2026-10-10
License CC-BY-NC-ND 4.0

大纲

展开看看
  • 网络问题远不止分区:连通性/DNS/丢包/时延/带宽/时钟,一张分层清单
  • P 没得选:这些毛病全归结为 P,CAP 真正的选择题只在 C 和 A 之间
  • 选边的判据:交易选 C,会议选 A,关键是问"这条数据晚一点对齐,用户会损失什么"
  • 媒体面天生就是 AP:音视频包宁可丢也不等,一等就卡,延迟才是实时通信的命
  • 信令面的收敛:谁在房间里、谁举手、谁共享屏幕——允许短暂不一致,最后要对齐
  • 最终一致性怎么落地:幂等、版本号/逻辑时钟、CRDT 式合并、权威源兜底
  • 三条红线:计费、权限、录制合规,这些场景不许最终一致
  • 一张取舍清单:设计实时系统时,每类状态先过一遍这几个问题

最近接连碰到几次网络问题:机房之间的专线抖了一下,两边的信令服务各自以为自己是主,同一个会议里出现了两份"谁是主持人"的记录;还有一次跨区域部署,网络分区恢复之后,一部分用户看到的参会者名单和另一部分对不上,持续了十几秒才慢慢对齐。

这些毛病有个共同点:它们都不是"写错了代码",而是网络本身靠不住——分区(partition)会发生,脑裂(split-brain)会发生,DNS 会解析到一个早就换掉的旧 IP,专线会时断时续,抖动(jitter)更是天天发生。网络能出的岔子有一长串(下一节我按类列全),但它们都指向同一件事:你没法假设网络永远畅通,于是 CAP 定理里那个 P 根本不是选项,是前提。

真正的选择题只有一道:网络出问题的时候,你要一致性(C)还是可用性(A)。 这篇想把这道题讲透,然后重点说清楚我们这行——音视频会议、实时通信——为什么几乎总是选 A,以及选了 A 之后,"最终一致性"到底怎么落地才不翻车。

  • 面向有一点分布式背景的工程师;CAP、幂等这些词我会在第一次出现时用一句话解释
  • 不讲定理证明,讲工程取舍和我踩过的坑

一、P 不是选项,是前提

先把 CAP 这三个字母摆正。CAP 说的是:在一个分布式系统里,一致性(Consistency)、可用性(Availability)、分区容忍(Partition tolerance) 这三样,网络一旦出问题,你最多同时保住两样。

很多人把它理解成"三选二,随便挑"。不对。在真实世界里,P 是老天爷发的牌,不是你能退的。而且"网络出问题"远不止分区一种——我最近接连踩的几次坑,摊开看是好几类毛病,下面这张表按性质分了分层:

类别 常见故障 它破坏了什么
连通性 网络分区、脑裂、闪断(flapping)、非对称路由(A 发得到 B、B 发不回 A)、NAT/防火墙穿透失败 通路本身断了或变成单向,节点互相"看不见"
名字解析 DNS 解析失败、解析到旧 IP、TTL 过期、权威服务器挂了 连地址都拿不到,或拿到过期的,故障从入口就开始
丢包 丢包、乱序、重复 包到不了、顺序乱、收到多份——实时媒体的头号敌人
时延 抖动(jitter)、高延迟、缓冲膨胀(bufferbloat) 包到了但不准时,"谁先谁后"变得模糊
带宽 带宽不足、拥塞、运营商限速 管子太细或太挤,大流量先遭殃
时钟 时钟漂移(clock skew)、慢节点/灰色故障(gray failure) 物理时钟对不齐、节点没死但变慢,比彻底宕机更阴险

这里头有几个关系要理清:脑裂是分区的后果(分区之后两边各自认主);丢包和抖动往往是拥塞的表现;而 DNS 故障最容易被忽略——它不在"你的服务"里,却是几乎所有外部调用的第一跳,一旦解析挂了或解析到旧 IP,后面再健壮的重试都白搭(这事我在另一篇讲 DNS 的文章里专门扒过)。

这些毛病有一个共同点:你写再漂亮的代码也消不掉它们,因为它们在网线上、在路由器里、在运营商的骨干网上、在别人的 DNS 服务器里,不在你的进程里。用 CAP 的话说,它们全都归结为一个字——P:网络不可靠是常态。既然 P 躲不掉,CAP 就退化成一道二选一:

分区发生时,你要么拒绝服务来保一致(CP),要么继续服务但允许暂时不一致(AP)。 没有第三条路。所谓"CA 系统",只存在于网络永不故障的幻想里。

多年前我在一篇服务器开发检查清单里写过一句话,今天看依然成立:金融银行需要强一致,宁可牺牲可用性;会议服务器需要高可用,最终一致就够了。 那会儿是一句结论,这篇想把"为什么"和"怎么做"补上。


二、怎么选边:问一个问题就够了

交易选 C、会议选 A,这个结论好记,但真到了设计台面上,一个系统里有几十种状态,不可能一刀切。判断每一类数据该靠哪边,我习惯只问一个问题:

这条数据晚一点才对齐,用户会损失什么?

答案要是"钱、权限、法律责任",那就得选 C,晚对齐不如不服务。答案要是"短暂看到旧画面、名单慢半拍刷新",那选 A,服务不能停。

维度 偏 C(强一致) 偏 A(高可用 + 最终一致)
典型场景 支付、扣款、下单、库存 音视频流、参会者名单、在线状态
分区时的行为 宁可报错、拒绝写,也不能算错账 继续服务,先本地处理,恢复后再对齐
用户容忍度 一分钱都不能错 看到几秒旧数据无所谓,卡住才受不了
对齐窗口 必须立即,否则不算数 秒级甚至更短对齐,用户基本无感
代价 牺牲可用性(可能转圈、报错) 牺牲瞬时一致(可能短暂看到不一致)

举个两边对照的例子。你在会议里转一笔红包(真有这种功能),这是交易,网络分区时宁可弹"网络异常,请重试",也绝不能两个机房各记一笔,把一块钱变成两块。但同一个会议里,"谁刚进来了"这种状态,分区时就该各显示各的,等网络恢复了名单自动对齐——你总不能因为机房之间网抖了一下,就把整个会议室冻住吧。

同一个产品,不同的数据,走不同的路。 这是 AP 系统最容易被误解的地方:选 A 不等于全局放弃一致,而是把需要强一致的那一小撮数据圈出来单独保护,剩下的大头走最终一致。


三、媒体面:实时通信天生就是 AP

说到音视频,有个事实很多人第一次听会觉得反直觉:实时媒体流从底层协议开始,就是为"宁可丢、不要等"设计的。

音视频包走的是 UDP/RTP,不是 TCP。TCP 的哲学是"一个包丢了,我停下来重传,保证你收到的字节一个不差、顺序不乱"——这是一种强一致的执念。可这套执念放到实时通话里是灾难:你正在说话,第 100 个语音包丢了,TCP 会卡住等重传,等它补回来,对话早就过去两秒了,用户听到的是一段突然的静默加上延迟累积。

实时媒体的选择正相反:那个包丢了就丢了,别等,用后面的包继续放,顶多这一帧画面糊一下、这 20 毫秒的声音缺一点。 丢包并发(concealment)、前向纠错(FEC)、抖动缓冲(jitter buffer)——这些技术全都在做同一件事:在"不完整但及时"和"完整但迟到"之间,坚定地选前者。

这就是 AP 在媒体面的物理体现。用 CAP 的话说:

  • 分区/丢包(P):网络会丢包,这是前提,不是异常。
  • 可用性(A):声音和画面必须持续流动,哪怕带着瑕疵。
  • 一致性(C):放弃"每个包都完整到达且有序"这个强一致目标。

所以当有人问"实时通信要不要选 AP",媒体面根本不需要讨论——它从 RTP 协议被设计出来那天起就选好了。 延迟才是实时通信的命,一致性在这里要给延迟让路。我在另一篇讲 AI 语音客服的文章里说过,电话里超过一秒的静默用户就觉得"卡了";这背后的工程哲学,就是媒体面的 AP。


四、信令面:允许短暂不一致,但最后要对齐

媒体面的 AP 是天生的,真正要动脑筋的是信令面——也就是"谁在房间里、谁是主持人、谁举了手、谁在共享屏幕"这些会话状态(session state)。

这些状态不像媒体包那样丢了就算,它们需要在所有参会者之间最终看起来一致。但"最终"是关键词:分区的那几秒、抖动的那几百毫秒,允许大家看到的不完全一样,只要网络恢复后能自动收敛到同一个结果。

这就是最终一致性(eventual consistency):不保证任何时刻处处相同,但保证"没有新变化之后,所有副本最终达成一致"。

开头那两个坑,坑的就是这里。"两份主持人记录"是脑裂——分区时两边各自认主,恢复后没有和解规则,就僵在那儿了。"名单对不上十几秒"是收敛太慢——方向对,但缺了主动对齐的机制,只能干等。

AP 不是"随它去",而是"先让它跑,再想办法让它对齐"。想对齐,得有机制,这就是下一节。


五、最终一致性怎么落地:四件工具

把"最终一致"从一句口号变成能上生产的东西,我常用的就这四样。它们不是什么高深理论,但缺一个就容易翻车。

1. 幂等:同一个操作重复执行,结果不变

幂等(idempotent) 的意思是:同一条指令执行一次和执行十次,效果一样。这是最终一致的地基,因为网络不可靠,重试是常态,没有幂等,重试就会把数据搞乱。

比如"用户 A 举手"这个操作,网络抖动导致客户端重发了三次,服务端如果每次都 +1,就成了"举了三次手"。正确做法是把它设计成"设置 A 的举手状态为 true"——重发多少次,状态都是 true。把"增量操作"改成"设置目标状态",是让操作幂等最省事的一招。

2. 版本号 / 逻辑时钟:判断谁的数据更新

分区恢复后,两边的数据不一样,到底该信谁?光看"谁后到"不行,因为网络抖动下"后到 ≠ 后发生"。得给每次变更打一个能比较先后的标记。

  • 简单场景用版本号或时间戳:每改一次 +1,合并时取版本高的。
  • 讲究一点用逻辑时钟(如 Lamport clock / vector clock):不依赖各机器的物理时钟(它们本来就对不齐),用逻辑顺序判断因果关系。

主持人脑裂那个坑,根因就是缺了这一层——两边各自改了"主持人",恢复时没有版本可比,系统不知道该信哪个,只能卡住。

3. 冲突合并:两边都改了,怎么和解

版本号能解决"谁新谁旧",但有些场景是两边同时改了不同的东西,不是覆盖关系,而是要合并。

  • 能自动合并的,用类似 CRDT(Conflict-free Replicated Data Type,无冲突复制数据类型)的思路。典型的是"参会者集合":A 机房加了用户 X,B 机房加了用户 Y,合并就是求并集,谁也不覆盖谁,天然收敛。举手列表、在线用户集合,大多能这么处理。
  • 不能自动合并的,定一条明确的和解规则兜底:比如"主持人以账号权限最高者为准""同一字段以版本号大的为准"。规则可以简单,但必须事先定死、代码里写明,不能留给运气。

脑裂不可怕,可怕的是脑裂之后没有和解规则。预设好"打架了听谁的",脑裂就从事故降级成了一次自动收敛。

4. 权威源兜底:总得有个最终说了算的

最终一致不等于没有权威。对那些实在不好自动合并、又比较重要的状态,留一个单一权威源(single source of truth):平时各节点本地快速响应(保 A),但以某个中心服务(或某个被选举出的 leader)的记录为最终准绳,恢复后以它为准做一次对齐。

这是 A 和 C 之间的实用折中:绝大多数时候走本地、图快;真出了分歧,回权威源、图对。

把这四样串起来看,最终一致性的工程套路就清楚了:

用幂等扛住重试,用版本号/逻辑时钟判断先后,用 CRDT 式合并或预设规则化解冲突,用权威源兜最后的底。 四样齐了,"先跑起来、再对齐"才是可控的,而不是碰运气。


六、三条红线:这些场景不许最终一致

讲了半天 AP 的好,得泼盆冷水:AP 和最终一致不是万能钥匙,有些东西一旦最终一致,就是事故。 做实时系统,我给自己画了三条红线。

  • 计费 / 扣款:会议时长计费、增值服务扣款、红包转账——凡是涉及钱的,必须强一致。分区时宁可这个功能暂时不可用,也不能算错账。一块钱变两块,用户投诉、财务对账、监管问询,一样跑不掉。
  • 权限 / 鉴权:谁能进这个会、谁能看这段录制、谁有管理员权限——鉴权判断不能靠最终一致。你总不能让一个没权限的人"先进来,等会儿系统发现不对再踢出去",这几秒钟他可能已经听到了不该听的内容。鉴权要么当场确认,要么拒绝。
  • 录制 / 合规:法律要求的录制、审计日志、合规留痕——这些数据的完整性和准确性是硬要求,不能"最终",只能"确定"。少录一段、时间戳对不齐,出了法律纠纷就是大麻烦。

这三条的共同点:它们的错误不可逆、代价不对称。 看到几秒旧的参会者名单,刷新一下就好了,代价几乎为零;但算错一笔钱、放错一个人进会、漏录一段合规内容,事后补救的成本远远大于当时"拒绝服务"的那点损失。

选 A 的前提,是先把这几块选 C 的红线圈出来。 这不矛盾——一个成熟的实时系统,骨架是 AP,关键关节是 CP。


七、一张取舍清单

设计或者排查实时系统时,我会拿每一类状态过一遍下面这几个问题。它不长,但能逼你把"到底选 C 还是 A"想清楚,而不是默认全选一边。

  1. 这条数据晚几秒对齐,用户损失什么? 损失是钱/权限/合规 → 选 C;损失只是"看到旧数据" → 选 A。

  2. 分区发生时,这个功能该停还是该继续? 能接受停 → CP;必须继续 → AP,并准备好恢复后的对齐机制。

  3. 这个操作幂等吗?重发一次会不会出错? 不幂等就改造:把"增量"改成"设置目标状态"。

  4. 两个副本打架时,听谁的?规则定死了吗? 没定就是埋雷。要么版本号/逻辑时钟判先后,要么预设和解规则,要么回权威源。

  5. 收敛要多久?有没有主动对齐,还是干等? 只靠"下次心跳碰巧带过来"是不够的,恢复后要有主动 reconcile 的动作。

  6. 这块是不是踩了三条红线(计费/权限/合规)? 踩了就别犹豫,选 C。


总结:骨架选 AP,关节守 CP

P 躲不掉,所以 CAP 从来不是三选二,而是分区发生时在 C 和 A 之间二选一。网络交易选 C,音视频会议和实时通信选 A——媒体面天生就是 AP,延迟优先于一致;信令面走最终一致,允许短暂不一致,但要靠幂等、版本号、冲突合并、权威源让它可靠地收敛。

但选 A 的前提,是先把计费、权限、合规这三条红线圈成 CP。一个靠谱的实时系统,是一副 AP 的骨架,配上几个 CP 的关节。 哪里图快、哪里图对,不是拍脑袋,是一条数据一条数据问出来的。

下次再遇到脑裂、名单对不上、状态收敛慢,先别急着骂网络——网络靠不住是前提。该问的是:这块到底该选 C 还是 A?打架了我有没有定好听谁的?

全文思维导图

@startmindmap
<style>
mindmapDiagram {
  node {
    BackgroundColor #F8F9FA
    RoundCorner 10
    Padding 10
    FontSize 13
  }
  :depth(0) {
    BackgroundColor #1E3A5F
    FontColor white
    FontSize 18
    FontStyle bold
  }
  :depth(1) {
    FontSize 15
    FontStyle bold
  }
  :depth(2) {
    FontSize 13
  }
}
</style>

* 再论 CAP
** P 是前提
*** 分区 / 脑裂 / 抖动躲不掉
*** CAP 退化成 C 与 A 二选一
** 怎么选边
*** 问: 晚对齐用户损失什么
*** 钱/权限/合规选 C
*** 旧画面/名单慢半拍选 A
** 媒体面 = 天生 AP
*** RTP/UDP 宁可丢不要等
*** 延迟优先于一致
*** FEC / jitter buffer / 丢包并发
** 信令面 = 最终一致
*** 会话状态允许短暂不一致
*** 最终要收敛对齐
** 落地四件工具
*** 幂等: 设置目标状态
*** 版本号 / 逻辑时钟
*** CRDT 式合并 / 和解规则
*** 权威源兜底
** 三条红线选 C
*** 计费 / 扣款
*** 权限 / 鉴权
*** 录制 / 合规
@endmindmap

再论 CAP - 思维导图


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 欢迎在我的个人网站 https://www.fanyamin.com 访问原文并评论。