后端系统工程 E01:在线状态与分层心跳——一个带误差上界的观察结果
本文属于《后端系统工程:从接口契约到故障恢复》系列。文中提到的演练工程、测试与实验证据位于博客源码仓库的 batch-4/ 目录(examples/、evidence/ 等子目录);业务数据均为合成数据。 “在线”看起来是一个布尔值,实际上是服务端对远端设备的一次推断。设备离开的方式不止一种:主动关闭连接、进入隧道导致连接半开、所在的接入网关整机宕掉。每一种方式被服务端察觉的路径不同,耗时也不同。服务端能承诺的不是“状态永远正确”,而是“从设备事实上离开,到对外发布离线,最多经过多久”。 本篇用受控时钟实现三层心跳:连接层探测、网关到存储的租约续期、用户级视图的去抖发布,并为每种离开方式推导和测量误差上界。隐私粒度作为同一视图的另一个维度一起处理。 1. 需求与规模假设 用户可以有多台设备;任一设备在线,用户即在线。 订阅者只看到被允许的粒度:精确(在线/离线 + 分钟级最近在线)、粗略(在线/最近/很久以前)、隐藏(未知);非联系人与被拉黑者什么都看不到。 每种离开方式都有可计算的“发现离线”上界;正常心跳偶尔丢一次,不应导致误报离线。 设备换网关重连时,旧连接迟到的下...
后端系统工程 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...
业务扩展框架02:注解模型与表驱动
注解把组件看成什么 业务框架读取一个类上的注解时,通常是在构建组件描述信息:这个实现的稳定标识是什么、实现哪个扩展点、归属哪种业务或产品、遵守什么调用契约。Java 对象负责执行代码,描述信息负责让框架管理和选择这个对象。 “被注解的组件”可能对应不同对象: 注解所在位置 可能建模的对象 不能仅凭注解得出的结论 领域接口或抽象类 能力定义、服务契约、流程节点类型 已经存在可执行实例 扩展接口的方法 一个变化点及其输入输出、归约语义 有多少业务实现会被激活 具体实现类 某个能力实现及其归属 本次请求一定执行它 配置加载器、代理工厂 框架本身的可替换基础设施 它属于某个商业产品 服务引用字段 远程服务引用的声明 远端实例已经发现且可用 因此同样叫 @Extension,在不同框架里可能标记扩展点定义,也可能标记扩展实现。理解注解必须同时看 @Target、处理器和注册过程;仅凭名字无法确定语义。 图 1:教学对照:注解所在位置与处理器共同决定被登记的对象;这些分支不代表同一个框架的统一 API。 flowchart TB accTitle: 注...
业务扩展框架01:TMF与美团平台化实践解决什么问题
多业务共用平台以后,复用为什么会变难 一个订单平台同时支持普通零售、预售和团购。三者都有商品、价格、订单与库存,但预售需要校验销售时间和预售额度,团购需要校验成团条件,普通零售通常直接处理现货。把共同步骤提取出来并不难,难的是下一次改预售规则时,怎样确定团购完全不受影响。 最直接的写法是在每个步骤判断业务类型。业务增多后,条件会散落到价格、库存、退款、消息消费等位置。新增业务的代价不只是多写几个分支,还包括找到所有分支、理解组合顺序、协调多个团队发布,以及证明旧业务没有改变。 将分支改成若干 Strategy 类,只解决了代码摆放。若每个策略自行判断是否适用、彼此没有清晰归属,两个策略可能同时生效,运行时仍然需要回答:这次请求应执行谁,为什么,冲突时怎么办。 TMF 一类业务扩展框架要把这些关系显式化:平台定义稳定的业务契约和变化点,业务提供定制实现,配置表达安装关系与冲突决策,运行时按明确的业务上下文执行。它需要的前提是已经存在值得复用的业务模型;框架不会从一堆任意 Java 方法中自动发现一个交易平台。 本系列分为三篇: 本篇:TMF 与美团公开实践的目标、领域模型和架构边...









