分布式系统双写一致性问题
围绕 AI 平台配额管理里的“双写一致性”问题展开,重点包括:
- 先把业务问题讲清楚:是否应该让 MySQL 中的配额账本 与 K8s 中的真实资源占用 保持实时一致
- 从 FLP、CAP、两将军问题、PACELC 这些经典理论出发,说明“跨两个独立分布式系统做实时强一致双写”为什么在理论上不可达
- 再落到工程实践,分析提交链路、运行期和系统层故障会如何把最终一致方案推向状态机、幂等、补偿、对账和组合爆炸
- 最后结合 Google Borg / Kubernetes / AWS 等业界做法,给出结论:放弃“实时库存式扣减”,采用 准入控制 + K8s 实时用量 的方案
AI 平台租户配额管理方案决策文档
结论先行:放弃"DB 实时库存式扣减"方案,采用业界主流的"准入控制 + K8s 实时用量"方案
前者在分布式系统理论上不可达,工程上代价极高;后者已被 Google、AWS、Microsoft 等所有头部厂商验证
分布式系统里,不存在"修好”,只存在"把不一致的窗口压到业务可接受”
承认这一点之后,工程问题就从"如何消除竞态 “变成"如何选择 SLA + 让残余竞态可观测、可恢复”
一、需求背景
1.1 业务场景
当前 AI 基础平台的资源管理模型如下:
- 租户:每个租户拥有一份资源配额(例如租户 1 = 100 张 GPU 卡)
- 空间:租户下可划分多个空间,配额从租户配额中切分(例如空间 a = 80 卡,空间 b = 20 卡)
- 任务:用户在空间下提交 AI 任务,任务运行需要消耗 GPU
- 执行层:任务真实运行在 K8s 集群(由 Volcano 调度)
1.2 当前提议(待评估)
业务侧提出的"实时库存式扣减"方案:
| 时机 | 动作 |
|---|---|
| 用户提交任务(10 卡) | DB 中:空间a.used -= 10,租户1.used -= 10,然后提交 K8s |
| 任务运行结束 | DB 中:空间a.used += 10,租户1.used += 10 |
核心目标:让 MySQL 中的"已用配额"账本与 K8s 中真实占用的资源实时一致。
1.3 待回答的问题
这个方案是否可行?是否值得做?业界是否有人这么做?是否有更好的替代方案?
二、方案不可行性证明:分布式系统理论限制
想做的"DB 配额账本与 K8s 真实占用实时一致”,本质上是一个跨两个独立分布式系统的实时强一致问题。这个问题在计算机科学中已被证明是不可达的。
2.1 FLP 不可能性定理(1985)
定理陈述:在异步分布式系统中,即使只有一个进程可能崩溃,不存在确定性算法能保证多方达成共识。
来源:Fischer, Lynch, Paterson. Impossibility of Distributed Consensus with One Faulty Process. JACM, April 1985. 论文原文:https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf 该论文获得 2001 年 PODC Dijkstra 奖(分布式计算最具影响力论文奖)。
证明思路(双价配置论证 / Bivalence Argument):
- 假设存在一个确定性的共识算法
- 对于该算法,总能构造出一个"双价初始配置”——即从这个配置出发既可能决定 0,也可能决定 1
- 通过精心安排消息延迟和单一进程崩溃,可以让系统永远停留在"未决定"状态
- 因此原假设不成立 ∎
对当前场景的意义:
- 业务服务与 K8s API Server 之间通过异步网络通信
- 任意一方都可能崩溃或响应延迟
- “DB 完成扣减” 与 “K8s 完成调度” 这两个事件在两个独立系统中发生
- ∴ 两边状态在有限时间内确定性达成一致——理论上不可达
2.2 CAP 定理(Brewer 2000;Gilbert & Lynch 2002 形式化证明)
定理陈述:在分布式数据存储中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)三者不可同时满足。
来源:Gilbert, Lynch. Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services. ACM SIGACT News, 2002. 论文原文:https://groups.csail.mit.edu/tds/papers/Gilbert/Brewer2.pdf
证明思路(反证法):
- 设系统包含两个节点 G1、G2,初始值 v0
- 客户端在 G1 写入 v1
- 此时 G1 与 G2 之间发生网络分区,所有消息丢失
- 客户端从 G2 读取
- 若保证一致性 → G2 必须返回 v1,但收不到更新,只能拒绝服务(违反可用性)
- 若保证可用性 → G2 必须立即响应,只能返回过期的 v0(违反一致性)
- ∴ 在分区场景下,C 与 A 不可兼得 ∎
对当前场景的意义:
- MySQL 集群与 K8s(etcd)集群之间必然存在网络分区可能性
- 想要"DB 扣减"和"K8s 提交"在任意时刻强一致 → 在分区时必须牺牲可用性
- 即整个平台在网络抖动期间将无法接受任何任务提交
2.3 两将军问题(Two Generals’ Problem,1975)
定理陈述:通过不可靠信道通信的双方,无法通过有限次消息交换达成对某个动作的共同知识。
证明思路(归纳法):
- 假设存在 N 步消息协议能达成共识
- 考虑最后一条消息:若它丢失,发送方无法确认接收方是否收到 → 发送方不能依赖它 → 它实际上是冗余的
- 因此 N-1 步就够了
- 对 N-1 步重复同样论证,归纳到底:0 步就能达成共识 → 矛盾 ∎
对当前场景的意义:
- 业务服务调用 K8s API 后,永远无法 100% 确认 K8s 是否真正处理了请求
- 这就是为什么需要 idempotency key、retry、对账——因为绝对的"双方共同知道"在数学上不可能
- 任何"提交后的状态推断"都建立在概率而非确定性之上
2.4 PACELC 定理(Abadi, 2010)
定理陈述:CAP 的扩展——
若发生分区(P),需在可用性(A)和一致性(C)间取舍; 否则(E),需在延迟(L)和一致性(C)间取舍。
对当前场景的意义: 即使在没有故障的"晴天"场景下,想做 DB 与 K8s 的强一致同步,也必须接受高延迟。任务提交 RT 会显著上升,吞吐能力下降。
2.5 理论结论组合推导
前提 1:目标是 DB 配额账本与 K8s 真实占用"实时一致"
前提 2:DB 与 K8s 是两个独立的分布式系统,通过异步网络通信
前提 3:网络可能分区、消息可能丢失(两将军问题)
前提 4:节点可能故障、消息延迟无上界(FLP 模型)
由 两将军问题 → 双方无法在有限步内确认彼此状态
由 FLP → 异步+故障下不存在确定性共识算法
由 CAP → 分区时强一致与可用性不可兼得
由 PACELC → 即使无分区,强一致也要付出延迟代价
∴ "实时强一致的 DB-K8s 双写"在理论上不可达
∴ 退而求其次只能做"最终一致"
∴ 最终一致需要:状态机 + 幂等 + 对账 + 补偿
∴ 这些机制的复杂度与故障空间组合爆炸
⚠️ 诚实的边界:理论证明的是"实时强一致不可达”,并未证明"最终一致不可达”。 但下一节会展示:即便退到"最终一致”,工程代价也远超收益。
三、工程不可行性:异常场景的组合爆炸
即使退而求其次选择"最终一致"路线(业务服务 + Outbox + Informer + 对账系统),仍会面临大量难以根除的异常场景。下面列举生产环境会真实遇到的边界问题:
3.1 提交链路异常(共 8 类)
| # | 场景 | 后果 |
|---|---|---|
| 1 | DB 扣减成功,调用 K8s 时网络超时 | DB 不知道 K8s 是否收到,重试可能双扣 |
| 2 | DB 扣减成功,K8s 提交失败(参数错误等不可重试错误) | 必须触发回滚/退款流程 |
| 3 | K8s 提交成功,但响应在网络中丢失 | DB 状态停留在中间态,依赖重试 + 幂等键兜底 |
| 4 | DB 扣减失败(主从切换、连接池打满) | 配额状态被回滚,但可能已经发起了 K8s 调用 |
| 5 | 业务服务在"DB 扣完"和"调 K8s"之间崩溃 | 任务不存在,但配额已扣 → 配额泄漏 |
| 6 | 业务服务在"调 K8s 成功"和"持久化任务记录"之间崩溃 | 任务在跑,但 DB 没记录 → 孤儿任务 |
| 7 | 同一用户并发提交两个相同任务(重复点击) | 必须设计业务幂等键,否则配额被重复扣 |
| 8 | MySQL 主从切换丢失最后几秒数据 | 已扣的配额被回退,造成实际超卖 |
3.2 运行期异常(共 7 类)
| # | 场景 | 后果 |
|---|---|---|
| 9 | Informer 监听到 Pod 完成事件,但归还配额时 DB 失败 | 配额没归还,依赖对账修复 |
| 10 | Informer 重启期间漏接事件 | 任务结束了配额没回收 |
| 11 | 用户绕过平台直接 kubectl delete 删除任务 |
平台不知道,配额永远占着 |
| 12 | Pod 被 Node Pressure 驱逐 | 是失败还是会重调度?配额怎么算? |
| 13 | 节点宕机,Pod 进入 Unknown 状态 | 配额是占着还是释放?两难选择 |
| 14 | GPU 卡硬件故障,Pod 还在但卡不可用 | 物理资源 ≠ 逻辑资源 |
| 15 | Volcano gang scheduling 部分成功(8 卡任务实际只起了 6 卡) | 配额按 8 算还是 6 算 |
3.3 系统层异常(共 5 类)
| # | 场景 | 后果 |
|---|---|---|
| 16 | K8s 集群短暂 API 不可用 | 所有提交/释放堆积在消息队列中 |
| 17 | 跨可用区网络分区 | DB 在 A 区可写,K8s 在 B 区,谁是真相? |
| 18 | 时钟漂移导致事件顺序错乱 | “释放"事件可能先于"扣减"到达 |
| 19 | 服务重启时正好有任务在中间态(SUBMITTING/REFUNDING) | 状态丢失,需要完整 replay 逻辑 |
| 20 | 配额扩容/缩容时正好有任务进出 | 账本基线在变,扣减计算依据混乱 |
3.4 这些异常带来的工程负担
每一个场景都需要:
- 专门的代码路径
- 单独的单测/集成测试用例
- 独立的监控告警
- 对账脚本兜底
- 长期的运维 runbook
真实的技术债形态:
“线上某个租户配额对不上了,差了 3 张卡,时间不确定,复现不了。”
这种 bug 通常依赖特定的事件交错顺序 + 时间窗口 + 网络状态,无法稳定复现,无法通过测试覆盖,只能靠生产环境的对账脚本去发现,再人肉去修。这种债持续吃工程师时间,没有尽头。
注意!以上只是列出了能想到的场景,可能会存在更多未知的异常场景!
3.5 对账系统本身又是一个新的复杂系统
很多人以为"做个对账兜底就行了”。但对账本身要解决:
- 对账的快照怎么取(K8s 和 DB 不能同时冻结)?
- 对账期间新进的任务算谁的?
- 对账发现差异,自动修复还是人工介入?自动修复的边界在哪?
- 对账脚本本身挂了谁来对账?
对账系统是"治理治理者"的递归问题,复杂度只会上升不会下降。
四、业界头部公司的真实做法
调研了业界主流的资源调度/配额管理系统。没有任何一家头部公司采用"DB 实时库存式扣减"方案。它们的共同选择是:配额作为准入控制策略,使用量从底层调度器实时获取。
4.1 Google Borg(Kubernetes 的前身)
论文:Verma et al. Large-scale cluster management at Google with Borg. EuroSys 2015. 链接:https://research.google/pubs/large-scale-cluster-management-at-google-with-borg/
Borg 论文明确指出:
“Quota is used to decide whether to admit a job (admission control). … Quota-checking is part of admission control, not scheduling: jobs with insufficient quota are immediately rejected upon submission."
关键设计:
- 配额是一个资源向量 + 优先级 + 时间段的策略配置(如"X 团队在 Y 集群可使用 20 TiB RAM 至 7 月底”)
- 配额检查在任务提交时一次性执行,不通过、立即拒绝
- 配额本身不参与扣减/归还
- 真实资源占用由 Borgmaster(调度器)维护,不是配额系统的职责
4.2 Kubernetes ResourceQuota(Borg 的开源继任者)
官方文档:https://kubernetes.io/docs/concepts/policy/resource-quotas/ 设计文档:https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/admission_control_resource_quota.md 控制器源码:https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/resourcequota/resource_quota_controller.go
K8s 原生的 ResourceQuota 同样采用准入控制 + 实时计算用量的模型:
“Users create resources (pods, services, etc.) in the namespace, and the quota system tracks usage to ensure it does not exceed hard resource limits defined in a ResourceQuota."
关键实现:
- ResourceQuota 对象只存储
hard(上限值) - ResourceQuotaController 通过 listwatch Pod 等资源对象,实时计算
used - 创建新资源时,准入控制器检查
used + 新请求 ≤ hard,否则返回 HTTP 403 Forbidden - 没有任何"扣减后归还"的事务逻辑
4.3 AWS Service Quotas
官方文档:https://docs.aws.amazon.com/servicequotas/latest/userguide/intro.html CloudWatch 集成:https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Quotas-Visualize-Alarms.html
AWS 的全局配额服务设计:
- 配额是配置值(如某账号最多能开 100 个 EC2 实例)
- 资源创建时,实时查询当前已用数量(从对应资源服务获取),与配额对比
- 不存在"创建 EC2 时扣减配额表,删除时加回来"的事务
- 用量统计走 CloudWatch,是异步聚合的指标
4.4 Meta / Microsoft
Meta 的 Twine 与 Microsoft 的 Singularity 等内部 AI/资源平台均采用类似设计:
- 配额是 entitlement(权益),不是 inventory(库存)
- 资源争抢由调度器的 fair-share / priority 机制解决
- 没有"扣库存"的概念
4.5 业界共识
| 厂商 | 系统 | 配额模型 | 是否做"DB 扣减+归还”? |
|---|---|---|---|
| Borg | Admission Control | ❌ 不做 | |
| Google/CNCF | Kubernetes ResourceQuota | Admission Control | ❌ 不做 |
| AWS | Service Quotas | Policy + Real-time Usage | ❌ 不做 |
| Meta | Twine | Entitlement | ❌ 不做 |
| Microsoft | Singularity | Entitlement + Fair-share | ❌ 不做 |
业界统一设计哲学:
“Quota is policy, not inventory."(配额是策略,不是库存)
五、推荐方案:准入控制 + K8s 实时用量
5.1 核心思想
重新定义配额:
- ❌ 旧模型:配额是一个需要扣减/归还的库存数字
- ✅ 新模型:配额是一个"上限策略”,使用量从 K8s 实时计算
用户看到的"剩余配额" = 配额上限(DB 中的策略) − 实时使用量(K8s 中的真实数据)
5.2 数据模型
配额策略表(仅存储上限,不存储已用):
|
|
注意:表中没有 used 列,没有 available 列。
5.3 提交任务流程
|
|
5.4 任务结束流程
|
|
5.5 前端展示
|
|
用户看到的画面与"库存式"完全一致:已使用 10 / 80 卡,剩余 70 卡。
5.6 硬限制兜底
为了防止"软校验通过但实际超卖"的极端情况,建议在 K8s 层加第二道闸:
- 把租户/空间映射到 Volcano Queue 或 K8s Namespace + ResourceQuota
- 让 K8s 自身的准入控制器作为最终硬限制
- 即使业务层判断有偏差,K8s 也会拒绝超额任务
5.7 计费/统计
- 不依赖实时账本
- 通过定时任务(每小时/每天)聚合 Pod 用量数据,写入数仓
- 报表、计费、月度封顶等业务通过离线 ETL 实现
5.8 方案对比
| 维度 | 库存式(DB 扣减/归还) | 准入控制式(推荐) |
|---|---|---|
| 一致性问题 | 跨系统双写,理论上不可达 | 单一事实源(K8s),无一致性问题 |
| 任务结束时归还逻辑 | 必须有,且要处理大量异常 | 不需要 |
| Outbox/状态机 | 必须 | 不需要 |
| 对账系统 | 必须 | 不需要 |
| 用户绕过平台直接操作的处理 | 必须专门处理 | 自动反映(K8s 是真相) |
| 服务重启中间态恢复 | 复杂 replay 逻辑 | 无中间态 |
| 配额扩容 | 处理基线变化 | 改一个 limit 即可 |
| 用户体验 | “已用 X / 总 Y” | “已用 X / 总 Y”(完全相同) |
| 初始开发成本 | 2~3 人月 | 1~2 周 |
| 长尾维护成本 | 持续 6~12 个月 bug | 几乎为零 |
| 业界采用度 | 几乎无大厂使用 | 业界通用方案 |
六、最终结论
6.1 一句话总结
配额一致性这件事,不是"做不到”,而是"做了之后会让系统持续地、不可见地变脆弱”。 K8s 已经是资源的真相,再造一个 MySQL 账本去和它实时对账,相当于花 80% 的成本去解决一个 5% 用户能感知到的问题。 把硬限制交给 K8s,把展示和统计放在这一层,这是 Google、AWS、Microsoft、Meta 等所有头部厂商的统一选择,也是更可持续的工程方案。
6.2 核心结论
-
理论层面:FLP(1985)+ CAP(2002)+ 两将军问题(1975)联合证明,DB 与 K8s 之间的"实时强一致"在分布式系统理论上不可达。
-
工程层面:即便退到"最终一致”,需要处理 20+ 种异常场景(可能还有很多未想到的各种异常场景),引入状态机、Outbox、Informer、对账系统等多个组件,长期维护成本极高,且 bug 难以根除。
-
业界层面:从 Google Borg 到 Kubernetes 原生 ResourceQuota,从 AWS Service Quotas 到 Meta Twine —— 没有任何头部厂商采用"DB 实时库存式扣减”。它们的共同选择是准入控制 + 实时用量计算。
-
替代方案:采用"配额策略 + K8s 实时用量"模型。用户体验完全一致,工程复杂度降低一个数量级,硬限制由 K8s 自身机制兜底。
6.3 行动建议
- ✅ 停止"DB 实时库存式扣减"方案的开发
- ✅ 改为"配额策略表 + K8s Informer 实时计算"模型
- ✅ 在 K8s 层使用 ResourceQuota / Volcano Queue 作为硬限制兜底
- ✅ 计费、报表、月度封顶等业务通过离线聚合实现
附录:参考文献
分布式系统理论
-
Fischer, Lynch, Paterson. Impossibility of Distributed Consensus with One Faulty Process. JACM, 1985. https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf
-
Gilbert, Lynch. Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services. ACM SIGACT News, 2002. https://groups.csail.mit.edu/tds/papers/Gilbert/Brewer2.pdf
-
Gilbert, Lynch. Perspectives on the CAP Theorem. IEEE Computer, 2012. https://groups.csail.mit.edu/tds/papers/Gilbert/Brewer2.pdf
-
Abadi. Consistency Tradeoffs in Modern Distributed Database System Design. IEEE Computer, 2012.(PACELC 定理)
业界系统论文与文档
-
Verma et al. Large-scale cluster management at Google with Borg. EuroSys 2015. https://research.google/pubs/large-scale-cluster-management-at-google-with-borg/
-
Kubernetes ResourceQuota Documentation. https://kubernetes.io/docs/concepts/policy/resource-quotas/
-
Kubernetes ResourceQuota Admission Control Design Doc. https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/admission_control_resource_quota.md
-
Kubernetes ResourceQuota Controller Source. https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/resourcequota/resource_quota_controller.go
-
AWS Service Quotas User Guide. https://docs.aws.amazon.com/servicequotas/latest/userguide/intro.html
-
AWS Service Quotas - CloudWatch Integration. https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Quotas-Visualize-Alarms.html
-
Meta. Twine: A Unified Cluster Management System for Shared Infrastructure. OSDI 2020. https://research.facebook.com/publications/twine-a-unified-cluster-management-system-for-shared-infrastructure/