可用性、耐久性和正确性是不同质量目标
一个"200 OK 且磁盘有记录"的响应,可能同时是一笔重复扣减、一次会被区域故障抹掉的写入。本页用余额服务把三种保证分开:可用性讨论能否按时得到符合契约的结果,耐久性讨论已确认记录在指定故障下是否保留,正确性讨论不变量是否成立。三者绑定不同的对象、确认点和故障模型,选型之前必须分开承诺。
合成场景:积分账户余额为 100,用户请求扣减 80,Java 服务写 MySQL 后返回。缓存返回旧余额可能很快,却不代表扣减仍合法;数据库中记录持久存在也可能是一笔重复扣减。余额与流量的守恒不变量在事务层的实现见跨资源事务与长流程;恢复目标如何定成契约,见故障域、RTORPO 和切换。
目标必须绑定对象、确认点和故障
"进程崩溃后已成功扣减不丢失"弱于"整个区域丢失后已确认扣减不丢失";"查询可用"也弱于"扣减可用"。本页区分这些目标,再做选型,不能给服务贴一个"高可用"标签后把全部保证混在一起。Google SRE 数据完整性章节讨论备份、恢复及用户视角的数据完整性;它的经验不等于每种存储系统具有同样保证。
确认点、持久化与复制的边界
MySQL 实验基线是 8.0.39。InnoDB redo 的提交刷盘策略与 binlog 的同步策略是不同配置;innodb_flush_log_at_trx_commit=1 与 sync_binlog=1 通常是讨论本机提交持久性的重要条件,但仍依赖操作系统、存储设备正确兑现持久化语义。不要把设置两个参数直接称为跨区零丢失。InnoDB 提交刷盘参数、binlog 同步参数分别定义相应行为。真实 MySQL 8.4.7 的计划与双会话验证见MySQL主题的实验记录。
异步复制中,主库确认后副本可能未收到日志。主机故障、磁盘毁损与区域故障的后果不同:本机恢复或可找回已提交事务,直接提升落后副本则可能遗漏已确认记录。半同步确认也不自动意味着副本 SQL 线程已应用,且超时后的模式变化需要审查。MySQL 半同步复制说明应与实际确认数量、等待点和降级策略共同阅读。
正确性仍要由事务和约束保护。先读余额 100,再让两个请求各写"100−80"会丢失一次更新。可把余额条件放入数据库原子更新,检查受影响行数;账户、业务操作身份和流水放同一事务,异常显式回滚。复制得再可靠,也只是更可靠地复制错误状态,不能修复这个竞争。
一个完整故障时序
设扣减请求操作键 K1。t0 主库开始事务;t1 写账户、K1 结果与流水;t2 本机提交;t3 响应包丢失;t4 客户端超时;t5 客户端重试。此时不知道的是客户端,不是数据库必然未执行。稳定业务键查到 K1 后返回原结果,才能避免重复——UNKNOWN 的通用恢复协议见重复消费、幂等、顺序消息。若同时区域故障且新主没有 K1,新主余额可能回到 100,旧流水的外部通知却已发出;恢复需要核对已确认事实与外部副作用,不只是让端口可连接。
再加入备份恢复:昨天快照包含余额 100,今天日志中有 K1。只恢复快照会丢业务;恢复快照和日志但重新发送 K1 通知又会制造重复。恢复协议要记录日志连续性、业务去重记录保留期、重放水位和下游查询方式。若备份加密密钥不可用,即使备份对象存在也无法恢复,因此恢复演练必须同时验证权限、密钥、兼容版本和业务校验。
可用性计算与相关故障
假设网关可用性 99.99%、账户服务 99.95%、数据库 99.95%,全部必须成功且独立时,路径近似可用性 0.9999×0.9995×0.9995=0.998900349975,约 99.8900%。这是独立串联系统的教学模型,不是月度生产承诺。若三者都依赖同一个区域,单区事故使它们同时失败;简单增加副本并相乘会高估保护。故障域与共享依赖的识别方法见故障域、RTORPO 和切换。
两副本独立且任一成功即可服务时,近似可用性为 1−(1−a)^2。但单主写入要先隔离旧主、选新主、更新路由并验证数据;这些操作期间请求可能失败,也可能因仲裁条件不足无法切换。用"任一副本活着"代替完整写路径,是错误模型。分别列举数据面、控制面、DNS、身份和发布依赖,再判断真正独立的故障域。
两种候选方案与选择依据
方案 A 为同区主库与跨区异步副本,写延迟低、运维较熟悉,但区域丢失时有复制窗口风险。适用于可通过权威来源重建的数据,例如非裁决性的展示投影;若用于积分主账本,必须明确业务能接受的丢失与对账方式。
方案 B 在成功确认前要求规定的跨区复制条件,降低特定故障下的丢失风险,代价是写入受网络 RTT、尾延迟和对端可用性影响。网络断开时如果自动退回本地确认,承诺也随之变化,必须在对外契约和运行信号中体现。不能为了高响应率悄悄降低耐久性。
假设两区 RTT 45ms,而写接口总预算 60ms,本机事务和鉴权已占 25ms,跨区同步可能使原预算不可行。架构师需要调整时限、返回"已受理待确认"、改变部署位置或选择业务降级,而不是同时承诺最低延迟、全域可写和零丢失。金额类正确性通常先于展示可用性,但具体优先级由业务约束决定。
可执行推演与验收
执行 python3 labs/quality_models.py availability,预期输出串联可用性约 0.99890035;脚本仅做独立串联模型演算,完整脚本与主题其他算例共用。然后在纸面记录五个状态:提交前、提交后响应前、副本接收前、副本应用前、通知完成后。对每个状态分别注入进程崩溃、主机损毁和区域丢失,填写"数据在哪、客户端知道什么、能否安全重试"。这份矩阵的预期答案必须区分本地持久化和远端确认,不能全部填写"重试即可"。
再把副本落后设为 4 秒、写入率 1,500 次/秒,暴露事务数量上界的简单估算是 6,000 次,但它依赖落后期间写入均匀、事务大小相近和故障点;真实 RPO 要按已确认而未复制的事务集合核验,秒数和事务数不能互相替代。此处为计算输入,不是实测复制延迟。
交付确认点表、故障矩阵、恢复顺序、已确认业务键集合与恢复后集合差异报告。通过条件是不变量未破坏、数据损失范围与目标匹配、客户端能够查询未知结果。仅有备份截图或数据库启动日志不能证明这些条件成立。
确认点矩阵:五个状态乘三类故障的交付表
"提交成功"是一条时间线,不是一个点。把扣减请求拆成五个状态:提交前、提交后响应前、副本接收前、副本应用前、通知完成后;对每个状态分别注入进程崩溃、主机损毁、区域丢失,填写"数据在哪、客户端知道什么、能否安全重试"。这张 5×3 矩阵是本页要求交付的最小验收物,缺任何一格都不能宣称恢复目标已定义。
预期答案的骨架:提交前崩溃——数据未持久化,客户端未知,可按同一业务键安全重试(幂等条件下);提交后响应前崩溃——数据已持久化,客户端未知,不能当作失败重试,必须先按业务键查询;副本接收前主机损毁——本地可能有记录、远端没有,重试可能重复;副本应用前区域丢失——丢失集合是"已确认而未复制"的事务;通知完成后区域丢失——外部副作用已发生,重试即第二次业务效果。矩阵不能全部填"重试即可",也不能把本地持久化写成远端确认。
已确认未复制集合的上界可以算:副本落后 5 秒、写入 1,500 次/秒,按均匀写入、事务大小相近的假设,上界是 5×1,500=7,500 个事务。它依赖写入均匀与故障点,真实 RPO 要按"已确认且尚未复制"的事务集合核验,秒数与事务数不能互相替代。RPO=0 需要与成功应答契约匹配的确认协议:本地 innodb_flush_log_at_trx_commit=1 与 sync_binlog=1 只覆盖本机持久性;跨区零丢失要么等跨区确认(受 RTT 与对端可用性影响),要么把成功语义改成"已受理待确认"。
执行 python3 labs/quality_models.py durability:脚本内置五状态与三类故障的分类结果,输出各格的重试判定(SAFE_RETRY/QUERY_FIRST/DUPLICATE_RISK)与上界 7,500。它是分类与算术模型,不注入真实故障;区域切换与备份恢复本轮未运行。
两个方案:A 同区主库加跨区异步副本,写延迟低,区域丢失时保留复制窗口风险;B 成功确认前要求规定的跨区复制条件,丢失风险降低但写入受 RTT、尾延迟与对端可用性约束,网络断开时若自动退回本地确认,对外承诺必须同步改写。两区 RTT 45ms、写预算 60ms、本机事务与鉴权已占 25ms 时,B 的同步等待已使原预算不可行——此时应调整时限、返回受理待确认或改变部署,而不是同时承诺最低延迟与零丢失。
架构师变化条件
把读路径与写路径拆开承诺:读 99.99%/80ms,写 99.9%/60ms 且耐久性按区域故障重定义——缓存与只读副本撑起读可用性,不改变写耐久性结论。把副本落后从 4 秒收紧到 0(半同步),预算需容纳跨区 RTT 与超时降级契约;降级时对外承诺如何变化必须在 SLI 中可见。每次变化都重新填写五状态×三故障矩阵,检查哪些格子的答案改变了。
闭卷验收
本页 8 道闭卷题收进了下方自测面板:先回忆,再展开参考答案与评分点,最后给三档自评。