- value-scan: read-only breadth inventory of mechanisms in delivered code, stopping at the human selection gate - value-dig: depth write-up of chosen points (feature list, design review, refactor plan) with templates and mechanical checkers - Add skill-workbench design doc for the pair
9.6 KiB
⟨主题⟩ · 改造方案
背景:现状问题登记在本文第 0 节(缺陷唯一归属地);复盘视角的"承接不住"见《⟨主题⟩-设计思路与取舍.md》第 5 节 目标:⟨把「X」从一条路的设计,变成覆盖全部写接口的事实⟩ 边界:⟨不改省侧协议、不引入分布式事务(改造点全在本服务内);DDL 只列关键字段,完整建表脚本另出⟩ 与其它方案的关系:⟨一致性异常台账复用《⟨其它⟩-改造方案.md》的
⟨表名⟩,不重复建表,只新增⟨列/取值⟩⟩ 依据:全部改造点均对应代码中已核实的缺陷,非推测 取材:深挖链路时发现的结构性缺口,按value-digSKILL 的「机制模式库」(结构性限制 → 可引入机制)比对出改造方向;每个缺陷追问"能否从补漏上升到加机制/重构" 代码讲解要求(必读):本文交付标准是"照着能讲代码"——① §1 先给改造前链路骨架(整体调用关系,用伪代码写清"谁调谁、每一步做了什么",可标类名/方法名,不写文件路径与行号);② 每个改造点必带「关键片段」(伪代码 / SQL,只示形状、不贴源码)。密度基准:docs/order-改造方案.md产出门槛:缺口中至少存在一个②加机制 / ③重构档的改造点(更好设计、重构、引入中间件级)才立本文档;全是①档补漏(加缓存/加判断/加隔离/改配置)的不产出——在设计复盘"重做改什么"小节记录即可
0. 现状问题登记(缺陷唯一归属地)
功能点清单与设计复盘不承载缺陷表;深挖中发现的问题全部登记在此,后文逐条消化。
严重度:🔴 资金/数据错误 · 🟠 静默失败或重复调用 · 🟡 卫生与可维护性
| # | 缺陷 | 证据 | 后果 | 严重度 |
|---|---|---|---|---|
| 1 | ⟨缺陷⟩ | ⟨可核对的事实:行为 / 字段 / 日志⟩ | ⟨后果⟩ | 🔴 |
| 2 | ⟨…⟩ | 🟠 | ||
| 3 | ⟨…⟩ | 🟡 |
归纳:这些不是孤立的 bug,而是 ⟨N⟩ 处结构性缺口:
- ⟨缺口一⟩ —— ⟨…⟩;
- ⟨缺口二⟩ —— ⟨…⟩;
- ⟨缺口三⟩ —— ⟨…⟩。
1. 总体思路
改造前链路骨架(先定位"改的是链路的哪一段"——用伪代码写清谁调谁、每一步做了什么):
⟨入口类#方法⟩()
→ ⟨A类#方法⟩() // 这一步做了什么(对应缺陷 #1)
→ ⟨B类#方法⟩()
→ ⟨C类#方法⟩() // 对应缺陷 #3
所以改造是做 N 件事,而不是调参数:
- ⟨把一个点的分级 → 铺成一张网⟩
- ⟨把「不确定」从日志提升为一等状态⟩
- ⟨把状态推进的时机对齐到权威事实⟩
flowchart TB
subgraph BEFORE["改造前"]
B1["⟨…⟩"] --> B2["⟨…⟩"]
B3["⟨…⟩"] --> B4["⟨…⟩"]
end
subgraph AFTER["改造后"]
A1["⟨…⟩"] --> A2["⟨…⟩"]
A2 --> A3["⟨…⟩"]
A4["⟨…⟩"] --> A5["⟨…⟩"]
end
2. 阶段一 · ⟨阶段主题:先说止血/可见⟩
成本最低、收益最大,优先做。⟨N⟩ 处改动都只动本服务内部。
2.1 ⟨改造点名⟩(P0,最重要)
- 现状:⟨…⟩(对应第 0 节缺陷 #⟨N⟩)。
- 后果(真金白银):⟨…⟩ 这违反了 ⟨不变量一⟩。
- 改造:⟨…⟩
关键片段 · ⟨片段名⟩(伪代码 / SQL,只示形状)
⟨改造后的语句或结构;与改造前的旧写法形成对照⟩
| ⟨对象⟩ | ⟨写/读⟩ | 可逆性 | 处置 |
|---|---|---|---|
| ⟨接口/动作⟩ | 写 | ⟨拒绝可逆、超时不可逆⟩ | ⟨…⟩ |
- 注意:⟨切换后必须同步补"超时不可逆"的判断,否则只是把"归档超时 → 退款"换成"归档超时 → 抛异常 → 同样退款",问题原地不动⟩。
flowchart TD
A["⟨入口⟩"] --> B{"⟨判断⟩"}
B -->|"改造后"| C{"分类"}
B -->|"现状"| Z["⟨旧行为⟩"]
C -->|"⟨可逆⟩"| D["⟨处置⟩"]
C -->|"⟨不确定⟩"| E["⟨处置⟩"]
2.⟨N⟩ ⟨改造点名⟩
- 现状:⟨…⟩(对应第 0 节缺陷 #⟨N⟩)
- 改造:⟨…⟩ 原则:⟨异常类型就是后果分类的载体,不能在中间被抹平⟩。
- 验收:⟨人为造一次 ⟨场景⟩,应出现 ⟨可观测结果⟩⟩。
关键片段 · ⟨片段名⟩
⟨改造后的分支形状,只示形状⟩
2.⟨N⟩ 阶段一验收标准
| 指标 | 目标 |
|---|---|
| ⟨…⟩ | ⟨100% 不产生 ⟨错误动作⟩,转 ⟨正确处置⟩⟩ |
| ⟨人为造 X⟩ | ⟨出现可观测的 ⟨证据⟩⟩ |
3. 阶段二 · ⟨阶段主题:再说可靠/对称/一等状态⟩
阶段一保证"不再做错",阶段二保证"⟨挂起能被收走⟩"。
3.1 ⟨幂等键改造⟩
- 现状:⟨没有幂等键 → 可被重复执行;MQ
maxRetry=3在异常路径上会重复调用外部⟩(对应缺陷 #⟨N⟩) - 改造:⟨唯一索引 · 冲突当幂等命中 · 语义:"同一 X + 同一动作 = 只允许一次对外调用"⟩
- 迁移:⟨先查存量重复,治理后再加索引⟩
关键片段 · 索引 + 冲突即命中
ALTER TABLE ⟨表名⟩ ADD UNIQUE KEY ⟨uk名⟩ (⟨列⟩, ⟨列⟩);
try { ⟨对外调用⟩; }
catch (DuplicateKeyException e) { return; } // 已执行过 → 直接返回,不再调外部
3.2 ⟨挂起态 + 巡检⟩
- 现状:⟨…⟩(对应缺陷 #⟨N⟩)
- 改造:
- ⟨新增挂起态⟩,与"失败""成功"并列,不占用失败的语义;
- 巡检任务(⟨XXL-Job⟩)按"距离挂起时长"分级:⟨<1h 自动重放一次 / 1–24h 告警 / >24h 人工队列⟩;
- 重放走同一套分级链路(不新开代码路径)。
关键片段 · ⟨片段名⟩
⟨挂起态写入 / 巡检取单的形状⟩
flowchart TD
A["⟨不确定⟩"] --> B["⟨挂起 + 台账⟩"]
B --> C["告警"]
B --> D["巡检任务"]
D --> E{"挂起时长"}
E -->|"⟨短⟩"| F["自动重放"]
E -->|"⟨中⟩"| G["升级告警"]
E -->|"⟨长⟩"| H["人工队列"]
关键设计:重放不是"再调一次接口",而是"重新走一遍分级"。 因为对方的状态可能已经变了,重放必须能得出"成功"这个结论。
3.⟨N⟩ ⟨改造点名⟩
⟨anomaly_type⟩ 取值 |
触发场景 | 处置方向 |
|---|---|---|
⟨…⟩ |
- 收益:⟨两侧的失败进同一张台账、走同一套告警和处理台——运维只需要看一个地方。这是"复用"而不是"新建"的价值。⟩
3.⟨N⟩ 阶段二验收标准
| 指标 | 目标 |
|---|---|
| ⟨重复调用外部⟩ | 0 次 |
| ⟨挂起单⟩ | 100% 台账可见 + 100% 被巡检捞到 |
4. 阶段三 · ⟨阶段主题:最后收口卫生问题⟩
| # | 改什么 | 现状证据(缺陷 #⟨N⟩) | 成本 |
|---|---|---|---|
| 1 | ⟨…⟩ | ⟨可核对的事实⟩ | 低 |
| 2 | ⟨…⟩ | 低 |
⟨某条不是文档工作,是防错工作⟩:⟨文档漂移会让后来人按错误的图改代码,成本远高于改文档。⟩
5. 实施顺序与成本收益
| 阶段 | 内容 | 成本 | 解决的根问题 | 关键收益 |
|---|---|---|---|---|
| 一 | ⟨…⟩ | 低 | ⟨…⟩ | 不再出错 |
| 二 | ⟨…⟩ | 中 | ⟨…⟩ | 不确定可运营 |
| 三 | ⟨…⟩ | 低 | 卫生与可维护性 | 可观测与可维护 |
顺序逻辑:先修会花错钱的(阶段一),再修会重复调用外部系统 / 卡住看不见的(阶段二),最后才是卫生(阶段三)。
6. 迁移与灰度
- ⟨逐项切⟩:先切风险最高、问题最实的 ⟨X⟩,观察一周无异常再切 ⟨Y⟩;每次切换只改一个调用点,便于回滚。
- ⟨新增状态先"只记录不流转"⟩:先写台账 + 告警观察一周,确认没有误报,再打开自动重放。
- ⟨索引先查存量⟩:先排查存量重复;加索引后观测冲突命中次数——这个数就是"原设计漏掉的重复调用次数"。
- 回滚:全部改造以"新增状态 + 新增列 + 开关"方式落地,回滚只需关开关/停调度,不动存量数据。
7. 改造后:⟨N⟩ 条不变量由谁保证
| 不变量 | 改造前 | 改造后 |
|---|---|---|
| ⟨不变量一⟩ | ⟨只有一条路成立;某路径会被错退⟩ | ⟨全部写类接口统一分类⟩ |
| ⟨不变量二⟩ | ⟨只有一行日志,库里无痕⟩ | ⟨挂起态 + 台账 + 巡检 + 重放⟩ |
| ⟨不变量三⟩ | ⟨实际只有两态(超时被降级抹平)⟩ | ⟨异常类型不被中间层改写⟩ |
附:与《⟨其它⟩-改造方案.md》的关系
| 项 | ⟨其它侧⟩ | ⟨本文⟩ |
|---|---|---|
| ⟨一致性异常台账⟩ | 建 ⟨表名⟩ |
复用,只加取值 |
| ⟨Outbox⟩ | 建 ⟨表名⟩ |
复用同表,⟨biz_type⟩ 区分 |
| ⟨幂等⟩ | ⟨…⟩ | ⟨…⟩ |
两边的根因是同一个:⟨设计了机制,但没有为"需要人介入"这个信号建自动出口⟩。所以两套改造共用一套台账与告警,是结构上的必然,而不是为了省事。