后端系统工程 38:数据备份、恢复与地域切换——恢复演练要检查数据,不只检查进程
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 “备份成功”是一条日志,“能恢复到指定时刻且数据正确”才是一项能力。两者之间隔着几个容易略过的问题:恢复点到底落在哪里,归档的 WAL 是否完整,恢复出来的实例里账是否平,角色和权限是否还在,切换之后旧主库会不会继续接受写入。 本篇在一台机器上启动多个隔离的 PostgreSQL 16 实例,实际执行基础备份、WAL 归档、按命名还原点的时间点恢复(PITR)、计划内切换和注入复制中断的非计划切换,每一步都用 SQL 检查数据本身。所有演练证明的是脚本与步骤在单机上可执行、检查项能发现错误;它们不证明跨地域网络、存储、DNS 或权限体系的行为。 1. 需求、目标与不变量 被保护的数据是一个合成的双边记账库:account(id, balance) 有 200 个账户,entry(request_id, account_id, amount, batch) 记录每笔转账的借...
后端系统工程 E01:在线状态与分层心跳——一个带误差上界的观察结果
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 “在线”看起来是一个布尔值,实际上是服务端对远端设备的一次推断。设备离开的方式不止一种:主动关闭连接、进入隧道导致连接半开、所在的接入网关整机宕掉。每一种方式被服务端察觉的路径不同,耗时也不同。服务端能承诺的不是“状态永远正确”,而是“从设备事实上离开,到对外发布离线,最多经过多久”。 本篇用受控时钟实现三层心跳:连接层探测、网关到存储的租约续期、用户级视图的去抖发布,并为每种离开方式推导和测量误差上界。隐私粒度作为同一视图的另一个维度一起处理。 1. 需求与规模假设 用户可以有多台设备;任一设备在线,用户即在线。 订阅者只看到被允许的粒度:精确(在线/离线 + 分钟级最近在线)、粗略(在线/最近/很久以前)、隐藏(未知);非联系人与被拉黑者什么都看不到。 每种离开方式都有可计算的“发现离线”上界;正常心跳偶尔丢一次,不应导致误报离线。 设备换网关重连时,旧连接迟到的下...
后端系统工程 39:综合方案评审与交付——沿四条路径检查跨模块保证
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 单个模块的测试证明的是模块自己的承诺:消息服务保证同一个客户端请求只产生一条消息,队列保证至少投递一次,通知服务保证按幂等键推送。用户看到的结果却取决于这些承诺怎样接在一起:消息已经撤回,通知还会不会把正文推出去;队列重投一条事件,用户会不会收到两次推送;配置回退之后,正在进行的撤回按哪个窗口判断;通知服务停了十分钟,恢复后积压能否排空、告警是否如实触发和恢复。 本篇的评审对象是一个专门构造、范围受控的合成系统 Relay,有明确的接口契约和不变量,并沿发送、撤回、配置回退、积压恢复四条路径写成可执行的验收用例。Relay 不是系列 04–35 章实现的汇总。本批次没有取得那些章节的实现,本文不声称审阅或验证了它们;它们与 Relay 的对接点在第 9 节逐项列为待集成项。 1. 评审对象 模块 职责 持久状态 消息 发送、撤回、历史查询 message、c...
后端系统工程 E02:定时调度与日历语义——“每天 09:00”不是每 24 小时一次
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 第 19 篇处理的是单次到期任务:一个确定的 UTC 时刻,到了就领取执行。日历调度多了一层翻译:用户说的是“柏林时间每个工作日 07:00”“每月最后一天”“每月 31 日”,系统要把这些本地时间规则翻译成 UTC 时刻,还要回答几个翻译中会遇到的问题:本地时间不存在怎么办,出现两次怎么办,2 月没有 31 日怎么办,调度器停机三天后恢复要不要补跑。 本篇实现一个 RRULE 子集的展开器和一个基于 SQLite 的调度器,用真实的 IANA 时区规则(本机 tzdata 2026c)验证夏令时跳变、半小时跳变、整天跳过、重复触发和补跑。 1. 需求与不变量 R1 规则以“本地名义时间 + IANA 时区名”保存,执行时刻按需展开,不预先存储 UTC 列表。 R2 跳变日的处理有明确策略,并在实例上留下注记,方便事后解释。 R3 同一个实例 (schedule_id,...
后端系统工程 E03:大群历史的冷热分层——归档之后,删除还要能追上去
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 大群的历史消息有一个典型的访问形状:最近几天的消息被反复翻看,几个月前的消息很少有人读,但必须保留、能检索,并且在用户删除或撤回之后不能再被读到。把旧消息搬到更便宜的存储上,可以大幅降低成本;问题在于搬动本身是一个多步骤操作,任何一步都可能中断,而搬走之后,删除、撤回这些“对历史的修改”还要能追上去。 本篇实现一个热层(SQLite 表)加冷层(按会话、按序号区间切分的压缩段文件)的历史存储,验证归档的中断恢复、跨层分页、段文件损坏检测、删除传播与撤回叠加,并用一个参数化的成本模型说明分层阈值怎样随访问分布变化。 1. 需求、不变量与规模假设 H1 历史分页:按 seq 从新到旧翻页,跨越冷热边界时不重复、不遗漏。 H2 归档在任意步骤中断后可以重跑,读路径在中断期间仍然正确。 H3 冷层数据损坏能被读路径发现,并以错误返回,不能返回错误的内容。 H4 删除对读路径立即...
后端系统工程 E04:消息通道替换与迁移——迟到事件与回退边界
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 替换一条在用的消息通道,比如把自建队列换成托管服务,或者在两个集群之间搬迁,不能靠“周五晚上停写、切换、周一观察”。切换的那一刻,旧通道里还有在途事件,新通道刚开始接收的事件可能比旧通道里的同一事件先到;切换之后,旧通道还会因为长尾延迟吐出几条早该到达的事件。迁移方案要回答四个问题:怎样证明新通道可以接手,切换时同一事件来自两条通道怎么办,切换后旧通道迟到的事件怎么办,以及到什么时候为止还能无损地退回去。 本篇用离散事件模型实现一个四阶段迁移控制器,在同一条合成事件流上注入延迟、重复、丢失和写入失败,验证副作用恰好一次,并用负向对照说明去重窗口和补漏期为什么不能随意缩短。 1. 需求与不变量 事件流是合成的业务通知事件:200 个业务键,每秒 50 个事件,每个事件带全局唯一的 eid、所属 key、在 key 内单调递增的 version 和一个随机载荷。消费者对每个事...
后端系统工程 E05:幂等导出与断点续传——同一个请求,同一份字节
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 “导出我的聊天记录”看起来只是一次查询加一次下载,实际包含几个各自独立的问题。用户点了两次按钮,是一个任务还是两个;导出期间又有新消息、有人退群、有消息被撤回,文件里该有什么;导出跑到一半进程被杀,重跑出来的文件和不中断时是否一致;下载到 80% 断网,能否从断点继续,断点之后的字节是否属于同一个文件;下载链接被转发出去,别人能不能用。 本篇用 SQLite 和一个真实监听本机端口的 HTTP 服务实现这条链路,每个问题对应一组用例。全部数据为合成数据。 1. 需求与不变量 X1 同一用户、同一幂等键、同一请求体只产生一个导出任务;同键不同请求体被拒绝。 X2 导出内容由创建时刻的数据快照和权限快照决定,之后的写入、撤回、成员变化不改变结果。 X3 任务可以在任意分片处中断并重跑;重跑得到的产物与不中断时逐字节相同,已完成且校验通过的分片不重写。 X4 下载支持断点续传...
后端系统工程 E06:配额与多租户公平性——先定义指标,再比较调度
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 多租户共享一组工作者时,“公平”常常被说成一个直觉:大租户不能把小租户挤死。要比较方案,就得把直觉换成可以计算的指标:每个租户应得多少,实际得到多少,偏离多少算不公平,小租户的延迟是否受大租户突发的影响,空闲容量会不会因为限额而白白浪费。 本篇先给出这些指标的定义,再在同一份不均匀的合成租户流量上比较四种准入与调度方案:全局先进先出、每租户令牌桶配额、赤字轮询(DRR),以及 DRR 加配额。仿真只用于比较调度算法,不代表任何真实服务的容量。 1. 场景与规模假设 项 值 工作者 8 个,任务服务时间服从均值 10 毫秒的指数分布,理论容量约 800 个/秒 heavy 租户 平时 400 个/秒;20–40 秒突发为 5 倍,即 2000 个/秒 mid0–mid3 各 60 个/秒 small00–small19 各 5 个/秒 运行时长...
新旧功能的柔性切换:灰度路由、影子比较与读写迁移
新实现已经上线,旧实现仍在服务。只改一个开关,就能让少量请求试用新实现;同一份输入还可以交给另一侧执行,比较结果,却不改变用户看到的响应。试用扩大后,主次可以反过来:新实现负责响应,旧实现继续提供比较样本,并在符合条件时兜底。 这类能力发生在应用内部。需要切换的可能是一套规则、一份配置的读取方式、一个查询接口、一段任务流程,或者整个数据模型。蓝绿、金丝雀和滚动发布可以帮助放量,但发布单元的流量比例没有回答:谁有权返回结果,谁可以写数据,哪种失败允许回退,以及回退的数据是否还可信。 以目录查询为例,旧存储 O 正在服务,新存储 N 逐步接入。迁移先让 N 观察真实查询,再让已完成数据准备的人群使用新读,之后才转移写入权威。停止回退和停止维护旧数据又是两次不同的决定。模板计算和任务处理也可以沿这条路径分析,只是比较合同和副作用边界各不相同。 从一次查询看清四种角色 设旧实现为 O,新实现为 N。请求被路由到 N,不代表最终一定由 N 返回:N 可能失败,系统按照策略使用 O 的结果。与此同时,O 也可能只是被调用来比较,完全没有接管响应的资格。 因此至少需要分别表达主执行、影子执行、...
业务扩展框架03:组合实验与远程绑定
运行一个最小业务组合实验 在博客源码仓库根目录运行: 1bash examples/business-extension/run.sh 示例仅依赖 JDK 8 或更新 JDK,不下载第三方依赖。脚本在临时目录编译,运行后清理编译产物。完整代码位于仓库的 examples/business-extension/ExtensionLab.java,实验说明位于同目录 README.md。 这是自建教学模型,不是 TMF、Lattice 或美团 BPF 的源码。它验证 SPI 发现、注解注册、业务配置激活和本地扩展调用。库存、资格与销售时间仅记录 mock 事件,没有连接真实系统。 在 Java 8 下执行,三个业务输出如下: 12345retail v1 payable=10000 [validate.input, price.list, inventory.audit, inventory.stock(mock)]presale v1 payable=9000 [validate.input, validate.presale-window(mock), price.presale...











