《互联网产品的服务端架构·产品、流量与基础能力》10.2 灰度、开关与回滚
灰度 5% 时错误率已经从 0.4% 翻到 1.8%,却没人看指标,一路放到 100% 才发现。文章把灰度拆成按用户分桶的递增台阶、独立于发布的开关和带最小样本量的回滚阈值,用 Go 实现计划校验、阶段推进与自动回滚判断,并给出表驱动测试与一次真实的运行输出。
tag
灰度 5% 时错误率已经从 0.4% 翻到 1.8%,却没人看指标,一路放到 100% 才发现。文章把灰度拆成按用户分桶的递增台阶、独立于发布的开关和带最小样本量的回滚阈值,用 Go 实现计划校验、阶段推进与自动回滚判断,并给出表驱动测试与一次真实的运行输出。
一次发布混进三个功能、一个索引重建和一份配置,出故障时没人说得清该回滚哪一段。文章把发布拆成代码、配置、schema、数据修复四类可独立观测、独立回滚的变更单元,用 Go 校验组件冲突、不可逆变更的观测信号与纯配置误标,并给出表驱动测试与真实运行输出。
端能力不是布尔值:同一个支付能力可能是原生、polyfill 或完全缺失,同一个功能可能是必需或可选。本节用能力表与降级计划模块,把「原生、polyfill、降级、阻塞」四种策略写成可测试的判定,并说明快照刷新、探测误差、开关实验的边界与排查清单。
提示改版后旧缓存继续返回旧口径,用户注销后摘要仍能被相似问题命中。本节把失效拆成提示换版、模型切换、权限调整与用户删除四类传播范围,说明作用域纪元如何挡住迟到的旧写回,并给出可运行的 TypeScript 失效器、删除权传播与完整隔离测试。
用户付了钱订单却三天待支付,暴露的是重试、死信与对账三层防线同时缺位。文章把重复、丢失、乱序当作常态,说明指数退避为何必须有上限、死信为什么必须有人处理、对账为何是最后一道防线,并给出 Next 与 Reconcile 的可测试实现与边界测试。
点赞事件重复投递让计数先涨两次又掉回去,问题不在消息队列,而在消费者没有幂等账本。文章区分事件与通知的失败语义,说明至少一次投递下如何用事件 ID 去重、用版本门槛拒绝旧格式、用首次处理时间回答最终一致要多久,并附 Apply 的 Go 实现与测试。
从「回声」发帖按钮转圈八秒的事故写起,说明同步与异步的分界线不是操作快慢,而是用户此刻是否在等结果、失败后能否撤回。文章把发帖拆成写库、图片处理、审核与通知四段,给出可测试的 Decide 与 Explain 实现和边界测试,避免把后台副作用塞进用户请求。
从大 V 发帖打满单个分片、整点配置统一过期两次事故写起,说明热 key 与集中失效是两种不同故障,给出访问占比探测、样本门槛与稳定分片的可测试 Go 实现,并讨论大 V 发帖、秒杀与整点刷新三类热点的观测和处置顺序,避免用同一套手段处理两种不同事故。
从头像改完八分钟仍显示旧图的事故写起,说明缓存位置决定一致性窗口,而不是加了缓存就快;拆解本地缓存、Redis 与 CDN 的 TTL 如何叠加,并给出窗口求和与产品预算判断的可测试 Go 实现,以及版本化 URL 和分层失效的处理方式,减少重复上传。
从一个社区首页按 DAU 估算缓存容量、结果命中率只有 22% 的事故写起,说明读多写少不是拍脑袋的常识,而是要用读次数、写次数、独立键数与值大小测出来的事实,并给出读写比、工作集和缓存建议的可测试 Go 实现,避免继续用请求量代替缓存容量。
从「回声」热点文章缓存 TTL 同时到期、98% 命中率仍把源站打挂的事故写起,说明边缘缓存必须配合合并回源、并发上限和等待预算三条保护机制,并给出 singleflight 语义的 Guard 实现与并发测试,解释为什么缓存越生效越危险。
从「回声」网关用字符串前缀匹配,把 /api/user-admin 导流到用户服务、普通会话触发管理导出的事故写起,说明网关只该做连接终结、认证、路由和限流四件事,并给出 Host 优先级、路径段边界匹配与认证检查的 Go 实现和表驱动测试。
从「回声」把个性化推荐错误地放进 CDN、A 用户看到 B 用户推荐的事故写起,说明静态与动态的分界依据是同一份内容对多少人相同,而不是文件后缀;再给出路径分类与缓存策略的 Go 实现和边界测试,并解释为什么 API 前缀必须优先于扩展名判断。
从服务端删掉一个字段导致三个月未升级的 App 白屏的事故写起,说明兼容窗口为什么由客户端版本分布决定而不是服务端想象,梳理新增、改名、删除、收紧校验和主版本升级的处理方式;并附版本解析、兼容判断与退役时间检查的 Go 实现、表驱动测试和真实运行输出。
从大促零点一条把 SQL 报错返回给用户的事故写起,说明错误码为什么是产品契约而不是异常栈导出,如何区分用户可解决、系统可恢复与不该发生的失败,并用可重试标记约束客户端的自动重试行为;附错误码表校验与查询的 Go 实现和边界测试,避免局部故障被重试放大成全站事故。
从「回声」点赞按钮被预取和重试重复执行的事故写起,说明资源查询与命令的本质区别、命令自然键的组成,以及同一动作在 App、Web、小程序三端为什么必须共用一套语义;附 HTTP 方法分类器的 Go 实现与表驱动测试,把幂等和可缓存钉在接口入口。
从「回声」为省一次请求把 2MB 数据塞进首屏接口的事故写起,拆解首屏时间、流量、电量和弱网四个端上约束,说明服务端能做什么、不该做什么,并用一个按优先级裁剪的 Go 预算函数把取舍变成可执行代码,避免首屏被一个大响应拖垮并显著放大故障半径。
从「回声」首页一个下游超时导致整页空白的事故写起,说明 BFF 聚合为什么不是把接口拼成一个 JSON,并把字段来源、失败语义、缓存键和按端裁剪写成一份可测试的契约,附聚合与合并的 Go 实现和边界测试,避免把五个下游的故障绑成一个更大的故障。
从「回声」小程序复用 App 未读模型的事故写起,把 iOS、Android、Web 与小程序的登录态、推送、深链、包体上限差异整理成一张能力表,并用一段可测试的 Go 代码说明 BFF 为什么必须按端裁剪,以及未知端为什么不能默认兜底,避免同一个接口把强端和弱端一起拖死。
从「回声」用户注销后仍能在备份里找回账号的事故说起,说明 pending、deleted、purged 三态注销机如何约束导出、删除、备份清除和审计留痕的时序,给出拒绝提前清除、校验备份窗口的 Go 实现与状态测试,并讨论日志、备份和审计记录里残留个人信息该怎么收尾。