WMS(仓储管理系统)库存预占、仓内作业与对账补偿方案
案例定位:本文是端到端方案演练,不是某次真实上线复盘。旧根与既有材料能证明项目主题和机制知识存在,标为 E2(已有材料映射);本文公式、样例量级、阈值和成本均为 E3(演练证据);真实表结构、峰值、仓数、差异率、恢复时长、组件版本和收益保持 E0(待核对)。本地正文、图源及真实渲染结果可作为 E1(源码与可复现证据),但不能反推生产实现。

正式图源见 case-wms-inventory-warehouse.puml。该图使用 PlantUML(统一建模语言)原生时序引擎,不依赖 Graphviz(图形可视化软件)。设计方法复用 50/01 需求约束、51/00 工作负载证据边界和 52/00 案例合同;库存、事务、消息和履约机制分别回链唯一机制分册,不在本文重复展开底层原理。
1. 需求澄清、事实边界与验收口径
1.1 从客户订单到出库完成的范围与非目标
方案覆盖客户订单、履约单、仓单、库存快照、库存流水、预占任务、波次任务、拣货任务、复核任务、出库任务和异常单。库存口径覆盖实物、可售、预占、分配、拣货、复核和出库七种业务量;主线从支付确认或货到付款放行开始,到仓库出库确认及库存对账收敛结束。上游支付成功与退款账务、下游承运商轨迹、采购入库和财务成本核算属于相邻边界,只消费确定事件或提供对账证据,不在本篇重建机制。
E2(已有材料映射)只支持“WMS(仓储管理系统)、库存防超卖、海外仓履约与补偿是可讲项目主题”。E0(待核对)包括真实订单入口、支付放行规则、仓内状态、预占期限、海外仓查询能力、人工工单权限和审计保留期。验收必须同时看用户结果、业务正确和技术健康:接口成功不等于有货,不等于仓单已创建,也不等于实物已出库。
| 项目 | 本篇定义 | 成功证据 | 失败或未知证据 |
|---|---|---|---|
| 用户结果 | 可下单、可取消、可查询履约进度 | 客户订单与履约单终态可解释 | 长期处理中、重复仓单、错误释放 |
| 业务正确 | 不超卖、不重扣、不丢释放、出库可追溯 | 快照、流水、仓单和任务守恒 | 可售为负、流水缺口、账实不符 |
| 技术健康 | 请求、任务、消息和外部调用可恢复 | 延迟、积压、重试和差异在界内 | 锁等待、消息积压、未知态超窗 |
| 非目标 | 不重写支付、运输轨迹和采购入库机制 | 以真实链接复用相邻分册 | 在本篇复制第二套机制结论 |
flowchart LR
A[客户订单] --> B[履约单]
B --> C[库存预占]
C --> D[仓单]
D --> E[波次与分配]
E --> F[拣货]
F --> G[复核]
G --> H[出库]
C -.差异.-> X[异常单]
D -.未知态.-> X
H -.账实差.-> X
X --> Y[查询、补偿或人工]图解读:客户订单是需求入口,履约单承接可履约责任,仓单承接仓内执行;库存预占、仓单创建和出库都可能产生异常单。正常路径单向推进,失败路径不猜测终态,而是进入查询、补偿或人工接管。
数据演绎 1:范围验收覆盖率
E3(演练证据):设本次评审要求 3 类结果、11 类核心对象、7 种库存量和 4 类边界证据,共 3 + 11 + 7 + 4 = 25 个检查槽位;草案若只覆盖接口、订单、预占、仓单和出库 5 项,覆盖率为 5 / 25 = 20%。状态从“接口方案”变为“端到端合同”;观测信号是每个对象都有主责、终态和异常去向;结论是组件数量不能替代业务闭环覆盖率。
热门面试题
- 问题:为什么库存方案要先区分七种库存量?
- 考点:库存语义和责任阶段。
- 回答思路:先定义实物与承诺,再解释作业状态不能混成一个余额。
- 详细答案:实物回答仓里有什么,可售回答还能承诺多少,预占回答已承诺未作业多少,分配、拣货、复核回答货物处于哪个仓内责任阶段,出库回答实物和所有权是否已离仓。若只保存一个库存数,取消可能释放已出库库存,复核差异也无法定位责任阶段。
- 进阶追问:七种量都要做独立余额列吗?
- 进阶回答:不一定。可按快照加流水投影实现,但每个业务语义、转换条件和可复算来源必须独立。
- 问题:接口返回成功为什么不能作为履约成功?
- 考点:用户结果、业务事实与技术响应分层。
- 回答思路:用受理、预占、仓单和出库四个完成点说明。
- 详细答案:接口成功最多证明请求被受理;预占成功才证明库存承诺成立,仓单成功才证明仓内责任建立,出库成功才证明实物离仓。任何异步、超时或外部仓调用都会使这些完成点分离,因此必须让订单状态和查询结果表达当前阶段及未知态。
- 进阶追问:前端应展示哪个状态?
- 进阶回答:展示面向客户的聚合状态,同时保留内部履约、仓单和异常状态,不能把内部未知态伪装成已失败。
- 问题:没有生产源码时怎样讲这个案例?
- 考点:事实等级与面试诚信。
- 回答思路:逐句区分 E2(已有材料映射)、E3(演练证据)和 E0(待核对)。
- 详细答案:可以说现有材料支持库存和履约主题,随后用 E3(演练证据)公式说明会怎样设计、压测和恢复;对于真实表名、组件、吞吐和收益明确保持 E0(待核对),并给出需要核对的源码、流水、监控和事故记录。这样既能展示架构能力,也不把候选方案说成亲历事实。
- 进阶追问:图已经真实渲染能否升级业务结论?
- 进阶回答:不能。渲染只把图形资产升级为 E1(源码与可复现证据),不证明生产系统采用了图中方案。
2. 量级估算、工作负载与容量输入
2.1 峰值、热点、在途、保留与故障余量
量级必须从业务事件推导,不能先报实例数。输入至少包括峰值客户订单率、每单商品行数、每行候选仓数、预占重试率、取消率、仓内任务放大、热点商品集中度、批量波次大小、消息保留期和审计保留期。真实值全部为 E0(待核对);下面只用 E3(演练证据)说明复算方法。容量输出还要扣除一个故障域和下游仓限额,详见 53/00 稳定性容量。
| 输入 | E3(演练证据)样例 | 计算用途 | E0(待核对)来源 |
|---|---|---|---|
| 峰值订单率 | 每秒 200 单 | 入口和履约单写入 | 网关与订单明细 |
| 平均商品行 | 每单 4 行 | 预占尝试放大 | 订单行分布 |
| 平均候选仓 | 每行 1.5 个 | 路由与失败重选 | 仓网配置 |
| 重试比例 | 5% | 事务与消息放大 | 调用和消费记录 |
| 热点集中度 | 前 1% 商品占 40% | 分片和锁竞争 | 商品访问分位 |
| 审计保留 | 180 天 | 流水与日志容量 | 合规和业务要求 |
sequenceDiagram
participant 业务 as 业务预测
participant 模型 as 容量模型
participant 库存 as 库存事务层
participant 仓内 as 仓内任务层
participant 外仓 as 下游仓
业务->>模型: 订单率、商品行、热点和保留期
模型->>库存: 推导条件更新与流水写入率
模型->>仓内: 推导波次、拣货和复核任务量
模型->>外仓: 核对调用率与并发限额
alt 任一层超过安全利用率
外仓-->>模型: 返回限额或长尾
模型-->>业务: 限流、排队或拆批
else 单故障域后仍有余量
模型-->>业务: 进入阶梯压测与恢复演练
end图解读:业务预测先进入模型,再分别约束库存、仓内和外仓;任何下游限额都能反向限制入口。正常路径进入压测,失败路径先限流或拆批,而不是靠无限重试制造虚假吞吐。
数据演绎 2:预占写入与在途量
E3(演练证据):样例输入为每秒 200 单、每单 4 行、每行 1.5 个候选仓、5% 重试,预占尝试率为 200 × 4 × 1.5 × (1 + 5%) = 1260 次/秒。若 P99(99 分位响应时间)按 120 毫秒演练,则在途约为 1260 × 0.12 = 151.2,向上取 152;单故障域后只剩 75% 容量,则正常容量下界为 1260 / 75% = 1680 次/秒。状态从业务峰值变为资源约束;观测信号是锁等待、连接等待、尾延迟和条件更新失败率;结论不代表生产吞吐。
热门面试题
- 问题:怎样从订单量推导库存写入量?
- 考点:流量放大和单位统一。
- 回答思路:订单率乘商品行、候选仓和重试放大。
- 详细答案:先把每秒订单转换成每秒订单行,再乘路由候选仓次数;若失败重选、超时重试或补偿会再次访问库存,还要分别加入放大因子。最后拆出快照条件更新、流水插入和 Outbox(发件箱)插入,不把一次业务动作误算成一次数据库写入。
- 进阶追问:平均商品行为什么不够?
- 进阶回答:长尾大单和批量订单会决定锁持有与事务大小,必须同时看高分位和最大批次。
- 问题:热点商品为什么可能先于总吞吐成为瓶颈?
- 考点:数据分布与串行点。
- 回答思路:说明同一仓库、货主、商品键上的条件更新会集中竞争。
- 详细答案:总吞吐分散在许多库存键时可以并行,但热点请求会集中到同一快照行、索引页或缓存键,使单键锁等待先饱和。扩应用实例只会增加竞争者,必须通过短事务、稳定锁顺序、请求合并、热点隔离和业务限流共同治理。
- 进阶追问:直接把热点库存放进 Redis(远程字典服务)能否解决?
- 进阶回答:只能削峰或预校验;最终承诺仍要落到可审计权威事实,并处理故障切换、持久化和回补窗口。
- 问题:为什么容量要包含单故障域余量?
- 考点:可用容量与峰值容量区别。
- 回答思路:用节点失效后剩余能力反推正常部署。
- 详细答案:满配环境刚好承受峰值时,任一节点、分区或可用区失效都会立即过载,重试还会继续放大。容量应以失去一个约定故障域后仍满足核心路径为约束,并给非核心批量任务设置暂停和降级开关。
- 进阶追问:余量越大越好吗?
- 进阶回答:不是。余量要与失败成本、扩容时延和成本比较,真实价格缺失时保持 E0(待核对)。
3. 业务不变量、库存等式与状态机
3.1 从余额正确到状态单调和唯一副作用
核心不变量一是 可售 >= 0;二是同一业务键最多产生一次有效预占、一次有效释放和一次有效出库扣减;三是取消不得释放已分配后不可逆的数量,支付成功也不得复活已取消履约;四是库存快照必须能被库存流水重放解释;五是仓单、任务和异常单的终态不能被迟到事件倒退。条件更新承担并发闸门,唯一键承担重复防线,状态机承担合法迁移,流水与对账承担可解释和恢复。事务与锁机制复用 MySQL(关系型数据库)事务隔离与 MVCC(多版本并发控制)和 InnoDB(事务存储引擎)锁与死锁。
| 不变量 | 约束表达 | 直接防线 | 最终复核 |
|---|---|---|---|
| 不超卖 | 可售量不小于申请量 | 带条件更新 | 快照与流水对账 |
| 不重复预占 | 业务键加阶段唯一 | 唯一约束 | 重复请求审计 |
| 不错误释放 | 仅可释放有效且未消耗预占 | 状态条件更新 | 取消与作业流水核对 |
| 状态不倒退 | 迁移表允许的边集合 | 版本和前置状态 | 迟到事件清单 |
| 账可解释 | 期初加流入减流出等于期末 | 同事务流水 | 批次校验和 |
stateDiagram-v2
[*] --> 待预占
待预占 --> 已预占: 条件更新成功
待预占 --> 库存不足: 条件更新失败
已预占 --> 已分配: 波次分配
已预占 --> 已释放: 取消或过期
已分配 --> 拣货中
拣货中 --> 已复核
已复核 --> 已出库
已分配 --> 异常: 缺货或破损
拣货中 --> 异常: 数量差异
异常 --> 已分配: 复核后恢复
异常 --> 人工终态: 不可自动补偿图解读:预占只在条件更新成功后成立;已预占可在未消耗时释放,进入分配后要按作业证据决定恢复或人工终态。迟到取消不能从已出库回跳到已释放。
数据演绎 3:并发防超卖与守恒
E3(演练证据):某库存键期初实物 100,质检冻结 5,已有预占 20,则初始可售为 100 - 5 - 20 = 75。两个事务分别申请 50 和 40,条件为“当前可售大于等于申请量”;先提交的 50 成功后可售变 25,后一个 40 受影响行数为 0,最终 25 >= 0。若随后释放 10,期末可售为 25 + 10 = 35,流水复算也应得到 35。状态从并发竞争收敛为一成一败;观测信号是受影响行数、唯一键冲突和流水余额;结论是普通先查后改无法提供同等原子边界。
热门面试题
- 问题:条件更新为什么能防止并发超卖?
- 考点:检查与扣减的原子边界。
- 回答思路:比较先查后改与单条带条件写入。
- 详细答案:先查后改让两个事务都可能读到相同可售量,再分别扣减;带条件更新把“余量足够”和“扣减”交给同一权威写入原子判断,只有满足条件的事务受影响。调用方必须以受影响行数判定成功,并在同事务写预占流水和业务唯一键。
- 进阶追问:隔离级别提高后能否只用先查后改?
- 进阶回答:不建议依赖隐含锁语义;明确条件更新更容易审计,复杂多行事务仍需稳定加锁顺序和重试边界。
- 问题:为什么唯一键和幂等代码要同时存在?
- 考点:并发竞争的最后防线。
- 回答思路:应用先查可能并发穿透,数据库约束负责最终裁决。
- 详细答案:应用幂等能快速返回已有结果并减少无效事务,但两个首次请求仍可能同时通过查询。数据库唯一约束以业务键和动作阶段阻止重复事实落地;捕获冲突后读取既有结果,不把唯一冲突当成可无限重试错误。
- 进阶追问:只保存请求号够吗?
- 进阶回答:不够,还要绑定业务对象、动作、参数摘要和结果,防止同请求号携带不同数量被错误复用。
- 问题:取消和支付成功并发时如何裁决?
- 考点:跨域竞态与状态前置条件。
- 回答思路:订单与履约分别建状态机,以事务结果和版本决定唯一终态。
- 详细答案:取消只可从允许取消的履约状态迁移,并以版本条件关闭后续放行;支付成功事件按事件键幂等,若订单已合法取消则进入退款或人工策略,不得重新预占。若取消前预占已成立,释放动作也必须引用原预占并确认尚未被分配或出库。
- 进阶追问:能按消息到达先后决定吗?
- 进阶回答:不能。到达顺序不等于业务顺序,应由权威状态、事件时间、版本和合法迁移共同裁决。
4. 总体架构、数据所有权与依赖边界
4.1 模块化单体起步,核心同步与副作用异步
保守起点是模块化单体:订单接入、履约编排、库存、仓内作业、外仓适配、对账补偿和审计在一个部署单元内保持代码边界,但各自拥有表和接口。库存事务库是库存承诺权威源;仓内作业拥有仓单和任务;外仓适配只翻译契约并保存请求响应;消息通道传递可重放事件,不成为唯一事实;Redis(远程字典服务)只承载查询聚合、热点预判和限流。正常关键写路径同步确认预占,后续仓内副作用通过 Outbox(发件箱)异步推进。
| 模块 | 权威对象 | 同步职责 | 异步职责 | 禁止承担 |
|---|---|---|---|---|
| 履约编排 | 履约单 | 决定路由和当前责任 | 接收支付、取消和仓内事件 | 直接改库存余额 |
| 库存 | 快照、预占、流水 | 条件更新与幂等裁决 | 发布库存事实 | 依赖缓存裁决最终库存 |
| 仓内作业 | 仓单、波次、任务 | 合法作业迁移 | 消费履约事件、发布作业事实 | 猜测外仓超时结果 |
| 外仓适配 | 请求、回执、关联号 | 契约转换与查单 | 重试和状态同步 | 代替库存权威源 |
| 对账补偿 | 差异、补偿、人工单 | 冻结与审批 | 扫描、重放和收敛 | 无证据直接改余额 |
sequenceDiagram
participant 订单 as 订单入口
participant 履约 as 履约编排
participant 库存 as 库存模块
participant 发件箱 as 发件箱
participant 消息 as 消息通道
participant 仓内 as 仓内作业
订单->>履约: 提交订单与稳定请求号
履约->>库存: 申请预占
库存->>库存: 条件更新、流水、唯一键
库存->>发件箱: 同事务记录事件
库存-->>履约: 返回预占结果和版本
履约-->>订单: 返回明确结果
发件箱->>消息: 扫描并发布
消息->>仓内: 至少一次投递
仓内->>仓内: 消费幂等后创建仓单图解读:预占结果在同步事务内明确,发件箱与库存事实同事务提交;消息重复由仓内消费幂等吸收。消息不可用时不回滚已经成立的库存事实,而由发布扫描恢复。
数据演绎 4:同步链路与失败放大
E3(演练证据):假设订单校验 20 毫秒、库存事务 60 毫秒、外仓调用 300 毫秒、仓内建单 40 毫秒。若全部同步串联,基础耗时为 20 + 60 + 300 + 40 = 420 毫秒,外仓重试一次变为 720 毫秒且未知态占用请求线程;若同步边界停在预占,入口基础耗时为 20 + 60 = 80 毫秒,后续 340 毫秒进入可恢复任务。状态从长事务耦合变为核心事实同步、副作用异步;观测信号是入口尾延迟、发件箱年龄和仓单创建年龄;结论不是异步更快,而是失败责任更可控。
热门面试题
- 问题:为什么不一开始就拆成多个服务?
- 考点:边界收益与分布式成本。
- 回答思路:先用模块所有权建立边界,再按独立扩缩容和故障隔离证据拆分。
- 详细答案:库存、仓内和补偿确实需要清晰所有权,但服务化会立刻引入网络失败、消息一致性、版本兼容、追踪和运维成本。模块化单体可以先落实表归属、接口和事件合同,待流量、团队或故障域证明需要独立部署时再拆,迁移更可逆。
- 进阶追问:模块化单体如何防止跨库表直连?
- 进阶回答:通过代码包边界、仓储接口、架构测试和表访问审计限制,评审任何跨模块事务。
- 问题:哪些步骤必须同步,哪些可以异步?
- 考点:完成语义与可补偿性。
- 回答思路:用户承诺和不可逆事实同步确认,可恢复副作用异步推进。
- 详细答案:是否接受订单和是否预占成功需要给调用方明确结果,因此同步;仓单创建、通知、缓存失效和部分仓内任务可通过持久任务异步推进。异步不等于忽略结果,必须有状态查询、截止时间、重试预算、对账和人工终态。
- 进阶追问:外仓建单必须同步返回外部单号怎么办?
- 进阶回答:可以同步等待到业务预算,但超时仍标未知并转查单,不能把超时直接改成失败后重建。
- 问题:消息通道为什么不能成为库存唯一事实?
- 考点:传输与权威事实分离。
- 回答思路:消息会重复、延迟和过期,权威事实必须可查询和对账。
- 详细答案:消息适合传播已经提交的事实,但投递至少一次会重复,积压会延迟,保留期和运维误操作也可能造成缺口。库存快照、预占和流水应在事务库持久化;消息丢失可由 Outbox(发件箱)扫描重发,消费结果可由业务表和 Inbox(收件箱)复核。
- 进阶追问:消息发送成功后能删除 Outbox(发件箱)吗?
- 进阶回答:应先记录发布状态并按审计期归档,删除策略必须保证故障恢复和对账窗口,不凭一次发送响应立即物理删除。
5. 数据模型、主键、版本与生命周期
5.1 订单、库存、仓单、任务、异常和事件如何关联
数据模型按“业务身份、当前状态、不可变流水、异步交付”四层组织。客户订单表达购买意图;履约单按仓库或履约策略拆分;仓单是仓内执行主单;库存快照保存当前聚合量;库存流水记录每次预占、释放、分配和出库;任务记录波次、拣货、复核、出库及重试;异常单承接无法自动收敛的对象;Outbox(发件箱)与 Inbox(收件箱)分别保存待发布和已消费证据。任何外部仓调用都保存稳定请求号、外部关联号、请求摘要、结果状态和最后查询时间。
| 实体 | 推荐业务键 | 关键字段 | 生命周期与归档 |
|---|---|---|---|
| 客户订单 | 租户加客户单号 | 支付放行、取消版本 | 按售后窗口保留 |
| 履约单 | 客户订单加履约序号 | 路由仓、状态、版本 | 随订单归档 |
| 仓单 | 履约单加仓库加拆单序号 | 内外仓单号、作业状态 | 按仓库审计期保留 |
| 库存快照 | 货主加仓库加商品加批次 | 七类数量、版本 | 在线聚合事实 |
| 库存流水 | 业务键加动作加序号 | 变化量、前后余额、来源 | 不可变、分区归档 |
| 任务 | 仓单加任务类型加序号 | 租约、尝试、截止时间 | 终态后归档 |
| 异常单 | 对象加异常类型加批次 | 证据、责任人、处置状态 | 关闭后仍可审计 |
| Outbox(发件箱)/Inbox(收件箱) | 事件键加消费者 | 负载摘要、状态、尝试 | 超过重放窗口后归档 |
sequenceDiagram
participant 履约 as 履约模块
participant 快照 as 库存快照
participant 流水 as 库存流水
participant 事件 as 发件箱
participant 仓单 as 仓单模块
履约->>快照: 以业务键申请数量
快照->>快照: 条件更新数量和版本
快照->>流水: 同事务写前后余额
快照->>事件: 同事务写库存事件
快照-->>履约: 返回流水号和版本
事件->>仓单: 投递履约创建事件
仓单->>仓单: 以事件键去重并建单
仓单-->>履约: 回写仓单关联和状态图解读:快照、流水和发件箱同事务提交,返回值携带可追踪流水号与版本;仓单消费按事件键去重。数据之间靠业务键和因果引用关联,而不是靠模糊时间范围猜测。
数据演绎 5:流水重放与快照校验
E3(演练证据):某库存键期初实物 120、可售 100、预占 20;窗口内新增预占 30、释放 8、出库 12、盘盈 2。预占期末为 20 + 30 - 8 - 12 = 30,实物期末为 120 - 12 + 2 = 110;若冻结量仍为 0,则可售期末应为 110 - 30 = 80。在线快照若显示可售 78,差异为 78 - 80 = -2。状态从聚合读数变为可解释差异;观测信号是期初、流水校验和、期末版本和缺失业务键;结论是先定位缺失或重复流水,不能直接把快照改成 80。
热门面试题
- 问题:为什么既要库存快照又要库存流水?
- 考点:在线性能与可审计恢复。
- 回答思路:快照服务当前判断,流水解释变化和重建。
- 详细答案:条件更新需要快速读取当前可售和版本,适合聚合快照;事故排查、对账和重放需要知道每次变化的业务原因、前后余额和幂等键,必须依靠不可变流水。二者同事务写入并定期校验,既避免每次全量汇总,也避免只剩无法解释的余额。
- 进阶追问:流水能否更新?
- 进阶回答:业务金额和数量变化不应覆盖原流水,应新增冲正或修正流水并引用原记录;仅允许受控补充非事实元数据。
- 问题:库存快照的业务键为什么要包含货主和仓库?
- 考点:所有权和可替代性边界。
- 回答思路:同商品在不同货主、仓、批次下不能任意互换。
- 详细答案:库存承诺受货主所有权、仓库位置、商品、批次、效期和质量状态约束。遗漏货主会串货,遗漏仓库会把不可即时履约的远端库存当本地可售,遗漏批次会绕过效期和监管要求。键粒度应由业务可替代规则决定,而不是为减少行数粗暴聚合。
- 进阶追问:键太细导致查询慢怎么办?
- 进阶回答:建立可重建聚合读模型或缓存,最终扣减仍落到细粒度权威键,并保存分配明细。
- 问题:外部仓单号为空时如何防止重复创建?
- 考点:稳定请求号与未知态。
- 回答思路:本地先持久化请求身份,超时后按同一身份查询或重试。
- 详细答案:调用前生成与履约单绑定的稳定请求号并落库,外部仓单号只是结果字段。响应丢失时保持未知态,用请求号或业务键查单;只有明确未创建且仍在重试预算内才复用同一请求号重试。若下游不支持幂等或查询,则风险升级为人工核对和限流准入条件。
- 进阶追问:换一个请求号重试有什么问题?
- 进阶回答:下游可能已经成功,换号会绕过其幂等边界并创建重复仓单。
6. 正常预占、预占过期与支付取消竞态
6.1 条件更新、到期扫描和竞态裁决
正常预占以稳定业务键进入库存事务:先插入或锁定幂等记录,再对指定库存键执行带余量和版本条件的更新,同事务写预占流水与 Outbox(发件箱)事件。预占到期不是按当前时间直接释放,而是以“仍处于可释放状态、到期时间已到、尚未分配或出库”为前置条件;扫描器只领取到期候选,真正释放仍由库存事务二次判断。支付成功、取消和过期三条路径竞争同一履约版本,败者读取胜者结果并执行退款、忽略或人工策略。
| 竞态 | 允许胜者 | 败者动作 | 禁止动作 |
|---|---|---|---|
| 支付成功对取消 | 权威状态机事务胜者 | 退款、拒绝取消或继续履约 | 按消息先后覆盖终态 |
| 支付成功对过期 | 预占有效且放行成功者 | 重新申请或退款 | 复活已释放流水 |
| 取消对分配 | 前置状态与版本胜者 | 转拦截、异常或拒绝取消 | 释放已拣货数量 |
| 扫描器重复领取 | 唯一任务或条件更新胜者 | 幂等读取释放结果 | 重复增加可售 |
sequenceDiagram
participant 支付 as 支付事件
participant 取消 as 取消请求
participant 履约 as 履约状态机
participant 库存 as 库存事务
participant 扫描 as 到期扫描
par 支付与取消并发
支付->>履约: 按事件键尝试放行
取消->>履约: 按版本尝试取消
end
履约->>库存: 胜者提交预占确认或释放
扫描->>库存: 领取到期候选
库存->>库存: 二次检查状态、版本和到期时间
alt 仍可释放
库存-->>扫描: 写释放流水并增加可售
else 已放行、已分配或已释放
库存-->>扫描: 返回既有终态,不改数量
end图解读:支付和取消并发竞争履约状态,扫描器只提供候选;所有数量变化最终由库存事务按当前状态裁决。扫描延迟会延长占用,但不能通过放宽前置条件换取释放速度。
数据演绎 6:到期扫描恢复能力
E3(演练证据):若每分钟新增 6000 条预占,其中 12% 最终过期,则稳定到期率为 6000 × 12% = 720 条/分钟。扫描器每分钟可安全处理 1200 条;停机 20 分钟积压 720 × 20 = 14400 条,恢复净速率为 1200 - 720 = 480 条/分钟,清空约需 14400 / 480 = 30 分钟。状态从无积压到恢复收敛;观测信号是最老到期年龄、释放成功率、状态冲突率和锁等待;结论是扫描能力必须高于持续到期率,样例不代表生产值。
热门面试题
- 问题:预占过期为什么不能只依赖 Redis(远程字典服务)键过期通知?
- 考点:通知可靠性与权威状态。
- 回答思路:过期通知可丢、可延迟,且不能判断库存是否已消耗。
- 详细答案:键过期只说明缓存时间到了,不保证通知持久送达,也不了解数据库中的支付放行、分配和出库状态。正确做法是数据库保存到期时间和状态,可靠扫描或延迟任务触发候选,最终释放执行条件更新并写流水;Redis(远程字典服务)只能加速时间索引。
- 进阶追问:数据库扫描会不会很重?
- 进阶回答:按状态和到期时间建索引、游标分批领取、限制每批事务,并按最老年龄扩缩容;热点时可用分桶时间索引辅助。
- 问题:支付成功晚于预占释放怎么办?
- 考点:跨域迟到事件和用户承诺。
- 回答思路:先确认预占终态,再按业务政策重新申请或退款。
- 详细答案:支付事件按幂等键进入订单状态机,发现原预占已合法释放后不能直接恢复旧流水。可在业务允许时重新申请库存,成功则继续履约;失败则进入退款、缺货沟通或人工策略。全过程关联原支付、原预占和新尝试,避免资金成功但库存承诺被静默丢失。
- 进阶追问:能延长所有预占时间避免吗?
- 进阶回答:会长期占用可售并伤害其他订单,应以支付时延分布、库存稀缺度和业务承诺共同定窗口。
- 问题:取消请求到达时已经拣货如何处理?
- 考点:可逆阶段和仓内拦截。
- 回答思路:按作业状态选择释放、拦截或售后,而不是统一回滚。
- 详细答案:未分配可直接条件释放;已分配未拣货可取消任务并释放;拣货中需仓内拦截并回库;已复核或出库通常进入截单、退货或售后流程。每一步都要由仓内任务证据确认,库存变化通过冲正流水完成,不能由订单服务直接增加可售。
- 进阶追问:拦截超时算取消成功吗?
- 进阶回答:不能。应保持取消处理中或异常态,直到仓内给出明确结果并完成库存或售后动作。
7. 波次、分配、拣货、复核与出库关键路径
7.1 从预占承诺到实物离仓的责任转移
波次把满足仓库、截单时间、承运方式和库区条件的仓单分组;分配把预占量落到具体库位、批次或容器;拣货记录实际拿取;复核确认商品、数量、批次和包裹;出库把实物库存扣减并完成预占消耗。每次状态迁移都携带任务版本、操作者或设备身份和来源单据。批量操作只减少调度开销,不得把多个仓单合并成一个不可拆分的大事务。
| 阶段 | 输入 | 事务结果 | 失败去向 | 关键观测 |
|---|---|---|---|---|
| 波次 | 待作业仓单 | 波次成员与优先级 | 留待下波或异常 | 等待年龄、波次大小 |
| 分配 | 预占和库位库存 | 分配明细 | 缺货、重路由 | 分配失败率、锁等待 |
| 拣货 | 分配任务 | 实拣数量 | 少货、破损、错位 | 任务时长、差异量 |
| 复核 | 商品和包裹 | 复核结论 | 返拣或人工 | 首次通过率、重复扫描 |
| 出库 | 已复核包裹 | 出库流水与事件 | 未知、冲正或拦截 | 出库年龄、重复扣减 |
sequenceDiagram
participant 调度 as 波次调度
participant 仓单 as 仓单
participant 库存 as 库存事务
participant 拣货 as 拣货任务
participant 复核 as 复核工位
participant 出库 as 出库模块
调度->>仓单: 条件领取待作业仓单
仓单->>库存: 将预占转换为库位分配
库存-->>拣货: 生成分配明细和任务
拣货->>库存: 上报实拣数量
alt 数量一致
库存-->>复核: 进入复核
复核->>出库: 提交包裹与复核版本
出库->>库存: 消耗预占并扣减实物
库存-->>仓单: 发布已出库事实
else 少货、破损或错位
库存-->>仓单: 建异常单并冻结相关数量
end图解读:波次领取和仓内任务都使用条件状态,实拣一致才进入复核与出库;差异先冻结相关数量并建立异常单。出库事务引用复核版本,防止迟到重复请求再次扣减。
数据演绎 7:波次拆批与事务边界
E3(演练证据):待处理 2400 个仓单,平均每单 3 行。若单波 600 单,则每波约 600 × 3 = 1800 行,四波完成;若每行处理 2 毫秒且单事务包含整波,理论持锁时间约 1800 × 2 = 3600 毫秒,会放大锁等待。改为每 50 单一个事务,则每批 150 行、理论 300 毫秒,共 12 批;失败只重做 50 单而非 600 单。状态从大事务变为可恢复检查点;观测信号是批次耗时、锁等待、回滚行数和任务年龄;结论是批次大小应由压测校准。
热门面试题
- 问题:波次为什么不能做成一个大事务?
- 考点:锁范围、回滚成本和失败隔离。
- 回答思路:说明调度分组与库存提交不是同一原子目标。
- 详细答案:波次可一次规划很多仓单,但库存分配和任务创建应按有限批次提交。大事务会长时间持锁、放大日志、回滚和主从延迟,一条异常还可能拖回整波。用任务状态和检查点保持批次间可恢复,比追求整波数据库原子更符合仓内作业实际。
- 进阶追问:分批后怎样保证波次完整?
- 进阶回答:波次保存期望成员和每批状态,完成度由成员终态聚合;缺失批次可重跑,不能用一个布尔值代表全部完成。
- 问题:拣货少货时应立即增加可售吗?
- 考点:账实差异与错误承诺风险。
- 回答思路:先冻结差异、复核实物,再决定调整和释放。
- 详细答案:少货说明系统账与实物可能不一致,直接增加可售会扩大错误。应记录实拣差异,冻结相关库位或批次,复核是否错位、漏扫、破损或盘点误差;确认可释放的预占才写释放流水,确认实物缺失则写盘亏或调整并保留审批证据。
- 进阶追问:客户订单怎么办?
- 进阶回答:按替代库存、跨仓重路由、拆单、缺货取消或人工策略推进,并保留原仓失败原因。
- 问题:出库如何防止重复扣减?
- 考点:不可逆副作用幂等。
- 回答思路:以仓单、包裹和出库动作建立唯一键,状态与流水同事务。
- 详细答案:出库请求携带仓单、包裹、复核版本和稳定动作号;数据库唯一约束阻止同动作重复,条件状态只允许已复核进入已出库,同事务写实物扣减、预占消耗和出库流水。重复请求返回原结果,参数不一致则拒绝并告警。
- 进阶追问:设备离线后批量补传如何处理?
- 进阶回答:按原动作号逐条幂等接收,校验事件时间和当前状态;冲突进入异常单,不能按补传到达顺序覆盖终态。
8. 下游仓未知态、查询优先与失败路径
8.1 超时不是失败,明确失败也不等于可以立即释放
外仓创建、取消和出库确认都可能出现“请求已到达但响应丢失”。适配层必须把结果分为明确成功、明确失败和未知;未知态先按稳定请求号、客户单号或外部查询键查单,在查询预算内指数退避并限制总并发。明确未创建后才允许复用原请求号重试;仍未知则保留库存承诺、冻结后续冲突动作并转人工。下游若没有幂等和查询能力,应在接入评审中降低自动化承诺,而不是在代码里假装可恢复。详细机制复用 订单库存与海外仓履约同步补偿。
| 下游结果 | 本地状态 | 库存动作 | 后续动作 |
|---|---|---|---|
| 明确成功 | 已创建并关联外部单号 | 保持预占并推进作业 | 消费后续状态 |
| 明确失败且未创建 | 创建失败 | 按策略释放或重路由 | 原请求号有限重试 |
| 超时或断连 | 未知 | 禁止直接释放 | 先查单、再裁决 |
| 查到已创建 | 从未知转成功 | 保持预占 | 回填关联并恢复 |
| 长期未知 | 异常待人工 | 冻结相关承诺 | 工单、审批和对账 |
sequenceDiagram
participant 仓内 as 仓内作业
participant 适配 as 下游仓适配
participant 外仓 as 外部仓
participant 查询 as 查询补偿
participant 库存 as 库存事务
仓内->>适配: 用稳定请求号创建仓单
适配->>外仓: 创建请求
alt 明确成功
外仓-->>适配: 外部仓单号
适配-->>仓内: 记录成功并推进
else 明确未创建
外仓-->>适配: 可判定失败
适配-->>仓内: 重试、重路由或取消
else 超时或响应丢失
外仓--x适配: 结果未知
适配->>查询: 提交请求号
查询->>外仓: 查询真实结果
alt 查到成功
外仓-->>查询: 返回外部仓单号
查询-->>仓内: 回填并恢复
else 仍未知
查询-->>库存: 保留预占并冻结冲突动作
查询-->>仓内: 转人工接管
end
end图解读:外部超时进入查询,不进入失败;只有明确未创建才允许重试、重路由或取消。长期未知时优先避免重复仓单和错误释放,以人工证据换取最终裁决。
数据演绎 8:查询预算与未知态积压
E3(演练证据):外仓创建率每分钟 3000 次,超时率演练为 2%,则每分钟新增未知 3000 × 2% = 60 单。查询器每分钟最多处理 150 单,其中 20% 需第二次查询,等价负载为 60 × (1 + 20%) = 72 次/分钟,净恢复能力 150 - 72 = 78 次/分钟。若故障 30 分钟形成 1800 个未知,故障恢复后约需 1800 / 78 = 23.08 分钟 清空。状态从未知增长到收敛;观测信号是最老未知年龄、查询命中、外仓限流和人工单数;结论需受外仓真实配额约束。
热门面试题
- 问题:下游创建超时为什么不能直接重试?
- 考点:请求结果未知和重复副作用。
- 回答思路:超时只说明本地没收到响应,不说明下游未执行。
- 详细答案:请求可能已在下游成功,只是响应在网络中丢失;直接用新请求号重试会创建重复仓单,随后可能重复拣货和出库。应保留原请求号,先查询下游结果;若明确不存在才在预算内重试,若已存在则回填关联,仍未知则人工接管。
- 进阶追问:查询也超时怎么办?
- 进阶回答:继续保持未知,按退避和总预算查询,限制并发并告警;不能把多次超时投票成失败。
- 问题:长期未知为什么通常要保留预占?
- 考点:重复履约和超卖的失败成本。
- 回答思路:比较错误释放与暂时占用的损失。
- 详细答案:若外仓实际已创建,释放预占会让同一库存再次承诺给其他订单,之后两个仓单竞争一份实物,可能造成超卖。保留预占会降低短期可售,但风险可见且可人工处置;因此未知窗口内通常选择保守冻结,并设置业务升级时限。
- 进阶追问:所有商品都要同样保守吗?
- 进阶回答:可按稀缺度、可替代性和失败成本分级,但任何自动释放规则都要有证据、上限和审计。
- 问题:如何评审一个不支持幂等和查单的外仓接口?
- 考点:依赖准入与剩余风险。
- 回答思路:识别不可消除的未知态,提出业务限制和人工控制。
- 详细答案:先要求稳定业务键、查询或对账文件等替代证据;若均没有,只能降低自动重试、串行化高风险请求、保存完整脱敏请求响应,并以人工核对后再推进。评审要把重复仓单和错误释放列为已知风险,由业务接受,而不能宣称技术已实现最终一致。
- 进阶追问:加分布式锁能解决吗?
- 进阶回答:不能。锁只能约束本系统并发,无法确认下游是否执行,也无法修复响应丢失。
9. Outbox(发件箱)、消费幂等、对账与补偿
9.1 消息可重复,业务副作用必须唯一且可复算
库存事务与 Outbox(发件箱)记录同库同事务提交,发布器按游标领取未发布事件并保存尝试、最近错误和发布时间;发送响应只说明通道受理,不说明所有消费者完成。消费者先用事件键、消费者名和参数摘要登记 Inbox(收件箱),再在本地事务内执行业务状态迁移;重复消息返回既有结果,乱序消息按业务版本拒绝倒退。对账以库存键和业务时间窗分片,依次比较订单承诺、预占流水、库存快照、仓单、任务和下游回执,差异先分类再补偿。消息可靠性机制复用 MQ(消息队列)可靠性、幂等、顺序与重试和 分布式事务、补偿与消息一致性。
| 差异类型 | 证据判断 | 自动动作 | 人工门槛 |
|---|---|---|---|
| 有预占无仓单 | 发件箱未发或消费失败 | 重发或重放消费 | 超过履约时限 |
| 有仓单无本地成功 | 下游查到已创建 | 回填关联与状态 | 请求身份冲突 |
| 快照与流水不等 | 校验和或业务键缺口 | 重放投影或补流水 | 原始证据不足 |
| 重复出库消息 | Inbox(收件箱)已有结果 | 返回原结果 | 参数摘要不同 |
| 已取消仍作业 | 仓内状态晚于取消 | 拦截、回库或售后 | 已发生不可逆出库 |
sequenceDiagram
participant 库存 as 库存事务
participant 发件箱 as 发件箱
participant 发布 as 发布器
participant 消息 as 消息通道
participant 收件箱 as 收件箱
participant 仓内 as 仓内作业
participant 对账 as 对账补偿
库存->>发件箱: 同事务写业务事实和事件
发布->>发件箱: 分批领取未发布记录
发布->>消息: 发送事件
消息->>收件箱: 至少一次投递
收件箱->>仓内: 首次事件执行业务事务
仓内-->>收件箱: 保存结果和参数摘要
对账->>发件箱: 查未发布和超龄事件
对账->>收件箱: 查未消费和冲突事件
alt 可自动收敛
对账->>消息: 重发或重放
else 证据冲突或不可逆
对账->>仓内: 建异常单并等待审批
end图解读:发件箱保证事实与待发布记录同事务,收件箱保证消费副作用可幂等;对账同时检查两端,不把通道响应当成业务完成。证据冲突或不可逆副作用必须进入异常单。
数据演绎 9:消息积压恢复与对账抽样
E3(演练证据):正常事件流入每秒 800 条,消费者能力每秒 1200 条;停机 15 分钟积压 800 × 15 × 60 = 720000 条。恢复后净消化速率为 1200 - 800 = 400 条/秒,理论清空时间 720000 / 400 = 1800 秒 = 30 分钟。若对账每批 10000 个业务键、差异率 0.2%,则一批期望差异 10000 × 0.2% = 20 个。状态从积压和潜在缺口到收敛;观测信号是最老消息年龄、重复命中、差异分类和补偿成功率;结论还需考虑下游限额和重试放大。
热门面试题
- 问题:Outbox(发件箱)如何解决数据库提交后消息未发送?
- 考点:本地事务与可重放交付。
- 回答思路:业务事实与待发送记录同事务,独立发布器持续重试。
- 详细答案:库存快照、流水和 Outbox(发件箱)记录一起提交,只要事务成功,待发送事实就可查询;发布器失败不会丢业务事件,可按状态和游标再次发送。它不保证恰好一次,消费者仍要幂等,对账还要发现永久发布失败和错误路由。
- 进阶追问:发布器重复发送怎么办?
- 进阶回答:事件键稳定,消费者以 Inbox(收件箱)和业务唯一约束返回既有结果;重复属于正常故障模型。
- 问题:消费幂等为什么不能只用 Redis(远程字典服务)短期键?
- 考点:幂等窗口与业务副作用生命周期。
- 回答思路:缓存键会过期、淘汰和故障丢失,最终仍需业务唯一约束。
- 详细答案:短期键可以拦截瞬时重复,但消息可能在数天后重放,缓存也可能淘汰或切换丢失。仓单创建、释放和出库等副作用必须由数据库事件键、业务唯一键和合法状态迁移兜底,缓存只用于减载,不能改变重复消息的最终结果。
- 进阶追问:幂等记录永久保存吗?
- 进阶回答:至少覆盖消息保留、重放、售后和审计窗口;归档前确认业务唯一约束仍能阻止历史副作用。
- 问题:对账发现差异后为什么不能直接改库存快照?
- 考点:证据链和补偿可逆性。
- 回答思路:差异可能来自漏流水、重复投影、未知仓单或实物差异,动作不同。
- 详细答案:直接改快照会掩盖根因并失去审计。应先冻结差异键,按订单、预占、仓单、任务和下游回执分类;可重放投影就重建,可确认缺失动作就新增带原始引用的补偿流水,涉及实物或不可逆出库则人工复核。补偿本身也要幂等并再次对账。
- 进阶追问:补偿失败怎么办?
- 进阶回答:保留异常单和原差异,按预算重试;达到阈值后升级人工,不能循环生成新补偿。
10. 热点、批量任务、锁竞争、容量与成本
10.1 先保护权威写路径,再优化吞吐和单位结果成本
热点通常集中在仓库、货主、商品、波次或单库位。排查顺序是确认热点键分布、事务持锁时间、死锁环、连接等待和重试放大,再决定短事务、稳定锁顺序、分批、请求合并、限流或热点隔离。Redis(远程字典服务)可做只读聚合、热点预判和令牌闸门,但最终扣减仍由条件更新裁决;分布式锁只保护跨节点调度或缓存重建,不替代库存约束,机制复用 Redis(远程字典服务)缓存一致性与 Redis(远程字典服务)分布式锁。成本用单位有效履约表达,真实单价和账单保持 E0(待核对)。
| 成本因子 | 计量单位 | 主要驱动 | 降本边界 |
|---|---|---|---|
| 事务计算 | 每千次库存动作 | 行数、锁等待、重试 | 不放宽条件更新 |
| 消息与任务 | 每千事件或任务 | 放大、保留、重放 | 不删幂等与重放证据 |
| 数据存储 | 每日新增字节 | 流水、事件、审计期 | 分区归档,不覆盖事实 |
| 外仓调用 | 每千次调用 | 超时、查询、重试 | 查询优先并限制预算 |
| 观测审计 | 每单日志与保留 | 采样、字段、留存 | 高风险动作保留全量证据 |
| 故障成本 | 每个差异或人工单 | 超卖、卡单、账实差 | 与资源成本共同决策 |
sequenceDiagram
participant 批量 as 批量任务
participant 闸门 as 流量闸门
participant 库存 as 库存事务
participant 锁 as 锁等待观测
participant 调度 as 自适应调度
批量->>闸门: 提交仓、货主、商品维度任务
闸门->>库存: 按小批和稳定顺序执行
库存->>锁: 上报等待、死锁和事务时长
锁-->>调度: 返回热点键与饱和信号
alt 锁等待或尾延迟超阈值
调度->>闸门: 缩小批次、限流并暂停低优先级
else 有余量且积压增长
调度->>闸门: 小步提高并发
end
闸门-->>批量: 返回检查点和剩余任务图解读:批量任务先经过闸门,以小批次和稳定顺序进入库存;调度依据锁与尾延迟反馈调节并发。降并发会牺牲吞吐,但能保护在线预占和出库。
数据演绎 10:锁竞争与单位结果成本
E3(演练证据):在线请求每秒 900 次,批量任务每秒计划 600 次;库存安全处理能力演练为每秒 1200 次,则直接叠加利用率为 (900 + 600) / 1200 = 125%,必然积压。将批量限制为每秒 180 次后利用率为 (900 + 180) / 1200 = 90%。若每万次事务资源成本记 2 个演练单位,重试率从 20% 降到 5%,十万次有效动作的尝试数由 100000 / 80% = 125000 降到 100000 / 95% ≈ 105263,成本从 25 降到约 21.05 个单位。状态从过载到有界;观测信号是重试、锁等待、积压和单位有效动作成本;结果不是生产收益。
热门面试题
- 问题:库存热点时为什么扩应用实例可能更慢?
- 考点:共享串行点和竞争放大。
- 回答思路:实例增加只增加请求者,不增加单库存键写能力。
- 详细答案:同一库存快照行仍由数据库串行裁决,更多实例会带来更多并发事务、连接和失败重试,锁队列更长,尾延迟上升。应先定位热点键和持锁逻辑,缩短事务、减少无关查询、稳定锁顺序、限制同键并发,再评估分片或业务拆分。
- 进阶追问:把一个商品库存拆成多个桶可以吗?
- 进阶回答:可作为高风险演进,但要解决路由、余量碎片、跨桶释放、对账和迁移,先用 E3(演练证据)验证。
- 问题:批量波次如何避免压垮在线预占?
- 考点:优先级、背压和检查点。
- 回答思路:资源隔离、有限并发、小批提交和动态降速。
- 详细答案:在线预占和出库设更高优先级与保底容量,批量任务经过独立队列和令牌闸门,按小批次提交并保存检查点。数据库锁等待、连接利用率或尾延迟升高时暂停批量领取,恢复后从检查点继续,而不是让批量客户端自行重试。
- 进阶追问:批量任务延迟会不会违约?
- 进阶回答:需要为任务等待设置服务目标和升级策略;容量不足时由业务在在线承诺与波次时效之间明确取舍。
- 问题:没有真实账单怎样评估成本?
- 考点:成本因子与事实等级。
- 回答思路:列单位、公式、敏感性和取证来源,不给总价。
- 详细答案:先按每千次事务、每千事件、每日存储、每千外仓调用和每个异常单列成本因子,再用 E3(演练证据)流量计算相对方案差异。真实单价、折扣、人工和事故损失保持 E0(待核对),待云账单、采购合同和工单工时核对后才升级结论。
- 进阶追问:最低机器成本就是最佳方案吗?
- 进阶回答:不是,还要计入超卖、补偿、值守、迁移和退出成本,以单位正确履约结果比较。
11. 身份、权限、数据安全与不可抵赖审计
11.1 最小权限、职责分离和高风险动作双重证据
安全从租户、货主、仓库和岗位四层授权。普通客服只能查询脱敏订单;仓内人员按仓和岗位处理任务;库存调整、批量释放、异常关闭、补偿重放和外仓关联修改属于高风险动作,需要 RBAC(基于角色的访问控制)、对象范围、原因码、审批或双人复核。服务间调用使用独立身份和最小数据字段,外仓凭据集中保管、定期轮换且不得写日志。审计记录操作者、授权来源、业务对象、动作前后摘要、请求标识、时间和结果;审计失败时高风险写入应拒绝或进入受控降级。
| 风险动作 | 最小权限 | 附加控制 | 审计证据 |
|---|---|---|---|
| 库存调整 | 指定仓与货主调整权限 | 原因码、审批、额度 | 前后数量与关联盘点 |
| 批量释放 | 异常处置角色 | 双人复核、数量上限 | 原预占和释放清单 |
| 补偿重放 | 运维加业务联合授权 | 演练、分批、停止阈值 | 批次、版本和结果 |
| 修改外仓关联 | 集成管理员 | 查单证据、冲突检查 | 原值、新值和回执 |
| 查看客户信息 | 必需岗位 | 字段脱敏、导出限制 | 查询对象和用途 |
sequenceDiagram
actor 操作者 as 操作者
participant 鉴权 as 权限中心
participant 业务 as 库存与补偿模块
participant 审批 as 审批复核
participant 审计 as 审计存储
操作者->>鉴权: 提交身份、仓、货主和动作
鉴权-->>业务: 返回最小授权范围
业务->>业务: 校验对象、额度和当前状态
alt 高风险动作
业务->>审批: 请求第二责任人复核
审批-->>业务: 返回批准或拒绝
end
业务->>审计: 写动作前后摘要和请求标识
alt 授权、审批和审计均成功
业务-->>操作者: 执行并返回可追踪结果
else 任一关键控制失败
业务-->>操作者: 拒绝动作并建立安全事件
end图解读:授权先限定对象范围,业务再校验状态和额度;高风险动作增加复核,审计成功后才完成。控制链失败时不静默绕过,而是拒绝并记录安全事件。
数据演绎 11:权限覆盖与异常操作发现
E3(演练证据):演练抽取 500 次高风险动作,其中 500 次有身份、498 次有对象范围、495 次有原因码、492 次有前后摘要、490 次有复核。完整审计率为 490 / 500 = 98%,不是 100%;缺口分别为 2、3、3、2 次,且同一动作可能重叠。若阈值要求高风险动作完整率 100%,则本批必须拒绝通过并回查 10 个缺复核对象。状态从“有日志”变为“控制闭环”;观测信号是字段完整率、越权拒绝、异常查询和审批绕过;结论是平均完整率不能抵消任何未审计高风险写入。
热门面试题
- 问题:为什么库存调整需要职责分离?
- 考点:内部风险和不可抵赖性。
- 回答思路:调整能直接改变可售与账实结论,单人全权风险过高。
- 详细答案:发起人提供盘点、破损或业务原因,复核人确认对象、数量和证据,系统再以受控流水调整。这样既降低误操作和舞弊,也让事故后能区分原始差异、审批决策和执行结果。紧急通道也必须限额、到期并事后复核。
- 进阶追问:小额调整可以免审批吗?
- 进阶回答:可按风险分级,但仍需权限、原因和审计;累计额度与异常频率要触发复核。
- 问题:审计日志为什么不能只记录“操作成功”?
- 考点:证据完整性和可复算。
- 回答思路:结果字段无法回答谁、为何、改了什么和依据什么。
- 详细答案:高风险审计至少包含身份、授权范围、业务对象、动作前后摘要、原因、审批、请求标识和结果。敏感字段可脱敏或保存摘要,但必须能关联原始业务流水;否则对账差异出现时无法判断是正常补偿、误操作还是越权修改。
- 进阶追问:审计存储不可用怎么办?
- 进阶回答:高风险动作默认拒绝或写入独立可靠缓冲,恢复后补交并复核;不能只打印本地日志继续执行。
- 问题:外仓凭据和客户信息如何保护?
- 考点:秘密管理与数据最小化。
- 回答思路:凭据集中托管轮换,客户字段按目的最小传输和展示。
- 详细答案:外仓密钥由秘密管理设施托管,服务按身份短期读取,日志和异常单不落明文;客户姓名、电话、地址仅向履约所需接口传输,查询和导出按岗位脱敏并留痕。测试与演练使用脱敏数据,数据保留到期后按合同删除或匿名化。
- 进阶追问:排障需要看原文怎么办?
- 进阶回答:走临时提权与审批,限制对象和时长,水印并全量审计,完成后自动回收权限。
12. 迁移演进、项目口述、排障与取舍
12.1 从模块化单体到服务化、事件化的可回退路线
第一阶段在模块化单体内统一业务键、状态机、库存流水、Outbox(发件箱)和审计;第二阶段旁路建立对账读模型与事件镜像,只读比较旧新结果;第三阶段先拆无权威写的外仓适配和异步任务,再按独立容量与故障证据拆仓内作业;库存权威写最后拆,采用影子读、双写校验但单主写、灰度路由和明确回退点。事件化演进先发布事实事件,不允许多个服务共同修改同一库存表。每阶段都以差异率、未知态年龄、重放结果和回退演练作为门禁,决策记录复用 50/03 ADR(架构决策记录)与可逆性。
| 阶段 | 变更 | 验证 | 回退 | 退出证据 |
|---|---|---|---|---|
| 一 | 单体内模块化和统一键 | 架构测试、流水对账 | 关闭新模块入口 | 旧逻辑仍可运行 |
| 二 | 事件镜像和只读投影 | 新旧读模型逐键比较 | 停止消费并重建 | 差异在门槛内 |
| 三 | 拆外仓适配与任务 | 超时、查单、积压演练 | 路由回单体适配 | 无长期未知放大 |
| 四 | 拆仓内作业 | 仓单与任务双读校验 | 回旧任务执行器 | 作业终态一致 |
| 五 | 拆库存权威写 | 影子读、单主写、灰度 | 切回旧主写并重放 | 快照流水守恒 |
| 六 | 退役旧路径 | 无调用、无回放依赖 | 延迟删除 | 审计期后确认退役 |
sequenceDiagram
participant 旧 as 旧单体路径
participant 镜像 as 事件镜像
participant 新 as 新模块或服务
participant 校验 as 差异校验
participant 门禁 as 灰度门禁
旧->>镜像: 发布带版本的事实事件
镜像->>新: 构建影子状态
新->>校验: 输出影子结果
校验->>旧: 逐业务键比较权威结果
alt 差异、延迟和恢复均达标
校验-->>门禁: 允许小流量单主写切换
门禁->>新: 分仓或分租户灰度
else 差异超阈值或未知态增长
校验-->>门禁: 停止扩面
门禁->>旧: 切回旧主写并重放缺口
end图解读:新路径先做影子状态,不与旧路径争夺主写;只有差异、延迟和恢复都通过才按仓或租户灰度。失败时切回旧主写,并用事件和流水重放新路径缺口。
数据演绎 12:迁移门禁与回退窗口
E3(演练证据):灰度 50000 个库存动作,新旧结果差异 25 个,差异率为 25 / 50000 = 0.05%;若门禁要求不高于 0.01%,当前不通过。事件镜像积压 180000 条,净回放能力每秒 600 条,则理论追平时间 180000 / 600 = 300 秒 = 5 分钟;若回退窗口为 10 分钟,时间上可行,但还要验证幂等和业务守恒。状态从影子比较到停止扩面;观测信号是逐键差异、最老事件年龄、未知态和回退后对账;结论是平均接口成功率不能替代迁移正确性。
热门面试题
- 问题:库存服务化为什么要最后拆权威写?
- 考点:单向门风险和正确性主责。
- 回答思路:先拆可回退副作用,最后迁移最强不变量。
- 详细答案:外仓适配、通知和任务执行失败可以重试或回退路由,库存权威写一旦双主会产生无法简单合并的余额和流水冲突。先统一业务键、事件和对账,再通过影子读、单主写和小范围灰度迁移库存,能把不可逆风险压到最后。
- 进阶追问:双写为何必须单主裁决?
- 进阶回答:双写只用于校验或复制,只有一个路径能确认业务成功;否则部分失败时无法决定哪个余额对用户生效。
- 问题:线上发现可售为负怎么排查?
- 考点:止血、证据链和根因分类。
- 回答思路:先冻结受影响键,再从快照、流水、订单和任务复算。
- 详细答案:先按仓、货主、商品隔离写入和批量任务,保留快照、事务日志与消息位点;再检查条件更新是否被绕过、重复释放或出库、流水缺失、投影口径和人工调整。按业务键重放期初到当前的流水,对照订单、仓单和实物证据;修复采用幂等补偿流水,恢复后分批放量并持续对账。
- 进阶追问:直接把负数改成零能否止血?
- 进阶回答:会掩盖差异并可能继续错误承诺;止血应冻结或限流,修正必须有来源和复算证据。
- 问题:如何用三分钟口述完整方案?
- 考点:结构化项目表达与取舍。
- 回答思路:按事实边界、需求量级、不变量、架构数据、正常失败、容量安全成本、迁移验证顺序。
- 详细答案:先声明真实实现和量级待核对,再说明七类库存量和不超卖、不重复、不错误释放的不变量;随后讲条件更新、流水、Outbox(发件箱)、仓单任务和下游未知先查单;再讲对账补偿、热点限流、批量检查点、权限审计与成本公式;最后说明模块化单体到服务化的单主写灰度和回退门禁。
- 进阶追问:最关键取舍是什么?
- 进阶回答:未知态期间宁可暂时占用库存,也不错误释放制造重复履约;批量吞吐让位于在线正确性和可恢复性。
13. 综合题库与长口述训练
综合题 01:请完整设计一个 WMS(仓储管理系统)库存预占到出库方案
口述答案:我会先声明事实边界:现有材料能支持 WMS(仓储管理系统)、库存防超卖和海外仓履约这个项目主题,但真实仓数、峰值、表结构、组件与收益都待核对,下面量级只做演练。需求上把客户订单、履约单、仓单分层,库存区分实物、可售、预占、分配、拣货、复核和出库,成功不是接口返回,而是每个阶段都有可查询终态。核心不变量是可售不为负,同一业务键只产生一次有效预占、释放和出库,快照能由流水重放,迟到事件不能让状态倒退。架构从模块化单体起步,履约编排只调度,库存模块拥有快照、预占与流水,仓内模块拥有仓单和任务,外仓适配保存稳定请求号与回执,对账模块负责差异收敛。预占用带余量条件的更新,同事务写流水和 Outbox(发件箱);消息至少一次投递,仓内通过 Inbox(收件箱)、唯一键和状态机幂等。正常路径按波次、分配、拣货、复核、出库推进;外仓超时进入未知态,先查单,明确未创建才重试,不能直接释放。容量从订单行、热点、重试和作业放大推导,并预留单故障域;批量任务有检查点和背压。高风险库存调整要最小权限、复核和审计。迁移时先统一键和事件,再拆适配与任务,库存权威写最后以影子读、单主写、灰度和对账迁移。验收同时看用户结果、库存守恒、未知态年龄、积压恢复和差异清零,不能用接口成功率代替业务完成。上线门禁还要实际演练消息重复、缓存全失、外仓超时和节点失效,确认历史副作用也能闭环。
- 追问 1:为什么不直接用缓存扣库存? 直答:缓存只做预校验和削峰,最终承诺必须由可审计的条件更新、唯一键、流水和对账裁决。
- 追问 2:最危险的失败是什么? 直答:外仓已成功但本地超时后释放库存并重复建单,它会同时制造超卖和重复履约。
- 追问 3:最先落地什么? 直答:先统一业务键、状态机、流水和对账,再谈服务化与事件化。
- 追问 4:如何证明恢复? 直答:对账快照、流水、订单、仓单和实物证据,确认历史未知态与积压都收敛。
延伸阅读:需求、约束与量级、订单库存与海外仓履约、稳定性与容量。
综合题 02:并发请求下如何保证库存不超卖
口述答案:我先把“不超卖”写成可执行不变量:对每个货主、仓库、商品及必要批次的库存键,可售量始终不小于零,一次业务预占只能成功一次。请求必须携带稳定业务键和申请数量,库存事务先处理幂等身份,再执行“可售量大于等于申请量且版本符合预期”的条件更新,以受影响行数判定成败;成功时同事务插入不可变预占流水和 Outbox(发件箱)事件,失败时返回库存不足或读取既有幂等结果。这样“检查余量”和“扣减”处在同一权威写边界,避免两个线程都先读到同一个余额再覆盖。数据库唯一约束还要覆盖业务键、动作类型和阶段,因为应用层先查仍可能被并发穿透。对于多商品订单,按稳定键顺序加锁,缩短事务,不在锁内调用外仓或消息;部分行失败时按业务政策选择整单回滚、拆单或补偿,不能留下无主预占。Redis(远程字典服务)可以保存聚合可售用于查询、热点识别和入口预校验,但缓存结果只能提前拒绝,不能绕过数据库批准;缓存旧值导致数据库条件失败时,以数据库为准并失效缓存。线上还要监控条件失败、唯一冲突、锁等待、死锁、事务时长和热点键分布。恢复不能只看可售非负,还要从期初快照加减每条预占、释放和出库流水复算期末,并抽取订单与仓单核对。若真实生产采用何种隔离级别、分片或缓存仍无源码证据,我会保持待核对,不把这个候选实现说成已上线事实。压测必须使用真实热点分布并注入重复请求,平均流量通过不能证明单键正确。
- 追问 1:提高事务隔离级别能替代条件更新吗? 直答:不能自动替代明确的余额条件和受影响行数裁决,还可能扩大锁范围。
- 追问 2:唯一冲突要不要重试? 直答:先按业务键读取既有结果;只有死锁或瞬时错误才在有界预算内重试。
- 追问 3:多商品怎样避免死锁? 直答:统一库存键排序、限制单事务行数、缩短持锁时间并记录死锁环。
- 追问 4:缓存显示有货但数据库失败怎么办? 直答:返回库存不足,删除或刷新缓存,并记录版本差异。
延伸阅读:MySQL(关系型数据库)事务与 MVCC(多版本并发控制)、InnoDB(事务存储引擎)锁与死锁、缓存一致性。
综合题 03:实物、可售、预占、分配、拣货、复核和出库如何建模
口述答案:我不会把七种量压成一个库存字段,因为它们回答不同责任问题。实物是系统认为仓内实际拥有的数量;可售是扣除冻结、有效承诺和业务限制后还能对新订单承诺的数量;预占代表已承诺但尚未落到具体库位的数量;分配代表已绑定库位、批次或容器;拣货代表作业人员或设备已拿取;复核代表商品、数量、批次和包裹已确认;出库代表实物已离仓并完成不可逆扣减。实现上可以用库存快照保存当前聚合量,用不可变流水记录每次阶段转换,并让流水携带业务键、来源单据、前后余额和版本。预占成功时可售减少、预占增加;分配时预占的责任转移到分配明细,但不能再次减少可售;出库时消耗预占或作业占用并减少实物。取消能否释放取决于当前阶段:未分配可直接释放,已分配要取消任务,拣货中要拦截回库,已复核或出库可能进入售后。对账公式不是一条万能等式,而是按同一库存键和窗口复算实物、承诺与各作业阶段,确认总量转换没有凭空增加或消失。读性能可以通过聚合表或 Redis(远程字典服务)读模型提升,但最终放行必须回到细粒度权威键。模型还要覆盖盘盈、盘亏、破损、冻结、调拨在途等相邻动作,本文没有真实仓库规则证据时只保留扩展点。面试时我会强调,字段数量不是重点,重点是每种业务语义、合法转换、可逆边界和证据来源都能说清。每次投影升级还要用同一批流水重放,对比新旧口径,防止模型变化偷偷改写历史库存。
- 追问 1:七种量都必须独立存储吗? 直答:不必须,可由快照与流水投影,但每种语义必须可查询、可复算。
- 追问 2:分配为什么不再次扣可售? 直答:它消耗的是已经成立的预占承诺,再扣会重复减少库存。
- 追问 3:拣货少货怎样处理? 直答:冻结差异、核对库位和实物,再决定释放、盘亏或跨仓补货。
- 追问 4:出库后还能回滚吗? 直答:通常不能覆盖原事实,应通过退货入库或冲正流水表达新业务动作。
延伸阅读:指标事实模型与库存流复算、WMS(仓储管理系统)指标树与护栏、履约同步与补偿。
综合题 04:支付成功、取消和预占过期并发时如何裁决
口述答案:这不是简单比较三个消息谁先到,而是订单、履约和库存三个状态机在同一业务身份下的竞态。支付事件按渠道事件号和订单号幂等,取消请求按订单版本提交,过期扫描只领取候选;真正的裁决必须读取权威履约状态、原预占状态、版本和作业阶段。若取消先合法提交,后到支付成功不能复活订单,应进入退款、重新确认或人工政策;若支付放行先提交,取消只能在允许取消的仓内阶段继续,已经分配、拣货、复核或出库时分别走任务取消、仓内拦截、退货或售后。预占过期也不能凭时间直接增加可售,库存事务要二次检查到期时间、状态、版本和是否已被分配,然后以原预占流水为引用写一次释放流水。支付成功晚于合法释放时,如果业务允许,可以重新申请库存;新申请成功再履约,失败则退款或缺货处置,绝不能把旧预占从已释放改回有效。每条路径都保存原事件、竞态结果和后续动作,重复消息只返回既有结果。容量上还要保证到期扫描的稳定处理能力高于持续到期率,停机后的净恢复率可复算;扫描变慢只会延长占用,不能放宽释放条件。观测重点是支付后无有效预占、已取消仍创建仓单、释放后又分配、最老到期年龄和重复释放冲突。真实支付窗口和取消政策属于待核对业务事实,我会把政策选择与技术正确性分开表达。验收还要回放三种事件的不同排列,证明每种排列都收敛到唯一可解释终态,资金和库存处置不互相遗忘,并保留完整审计。
- 追问 1:能按事件时间决定胜负吗? 直答:事件时间是证据之一,最终仍由权威状态、版本和合法迁移裁决。
- 追问 2:过期扫描重复执行怎么办? 直答:释放动作引用原预占并有唯一键,条件状态只允许一次成功。
- 追问 3:支付成功但重新预占失败怎么办? 直答:按已确认政策退款、缺货沟通或人工处理,不能虚构库存。
- 追问 4:延长预占是否最简单? 直答:会降低可售利用率,应依据支付时延分布、库存稀缺度和承诺成本校准。
延伸阅读:支付订单状态机、取消、退款与对账、延迟任务与限流。
综合题 05:外仓创建请求超时后为什么必须先查单
口述答案:超时只说明本地在预算内没有收到响应,不能证明外仓没有执行。请求可能已经成功落到外仓,只是响应在网络、网关或客户端读取阶段丢失;如果本地直接标失败、释放预占并换新请求号重试,就可能产生两个外仓仓单、两次拣货和一份库存被两个订单承诺。我的做法是在调用前生成与履约单绑定的稳定请求号,持久化请求摘要、目标仓、状态和尝试次数,再调用外仓。结果明确成功就记录外部仓单号并推进;结果明确未创建才允许按原请求号有限重试或重路由;超时、断连和无法解析的响应统一进入未知态。查询任务按稳定请求号、客户单号或外仓支持的业务键主动查单,并使用退避、总次数、总时长和并发限额组成查询预算。查到已创建就回填外部关联并恢复仓内作业;查到明确不存在才进入重试或取消;仍未知则保留预占、冻结冲突动作并创建异常单,由人工结合外仓后台、对账文件或客服证据裁决。这里保留库存会损失短期可售,但比错误释放导致重复履约更可控。监控要看未知态新增率、最老年龄、查询命中、外仓限流、重复仓单和人工积压,而不是只看调用错误率。若外仓既不支持幂等也不支持查单,这不是加锁能解决的技术细节,而是接入准入风险,应限制自动重试和业务规模并明确剩余风险。人工完成裁决后仍要把外部证据、库存动作和仓单终态写回异常单,再由下一轮对账确认没有遗留孤儿对象和重复承诺。
补充验收时还要确认所有查询任务有明确终态,没有孤儿对象和重复承诺。
- 追问 1:查询也一直超时怎么办? 直答:保持未知,限流退避并升级人工,不把多次超时投票成失败。
- 追问 2:为什么复用原请求号? 直答:让下游幂等边界识别同一业务尝试,避免新身份重复建单。
- 追问 3:何时可以释放预占? 直答:外仓明确未创建且本地仓内没有不可逆动作,或人工证据完成裁决后。
- 追问 4:未知态积压如何估算? 直答:用创建率乘超时率得到流入,再用查询能力减持续流入计算净恢复时间。
延伸阅读:履约未知态与补偿、质量属性与失败成本、稳定性恢复边界。
综合题 06:如何用 Outbox(发件箱)和消费幂等保证事件链可恢复
口述答案:我不会承诺恰好一次,而是把“事实不丢、传输可重复、副作用唯一、结果可对账”分别落实。库存预占事务在同一个 MySQL(关系型数据库)事务中更新快照、插入流水和 Outbox(发件箱)记录,因此进程在提交后崩溃也留下待发布证据。发布器按状态和游标分批领取,发送失败记录尝试与错误并退避重试;通道确认只把记录标为已发送,不代表消费者完成,记录按重放和审计窗口归档。MQ(消息队列)至少一次投递可能重复,仓内消费者先用事件键、消费者名和参数摘要登记 Inbox(收件箱),再在本地事务中创建仓单或推进任务;数据库业务唯一键和合法状态迁移是最后防线。重复事件参数相同就返回原结果,参数不同则拒绝并告警;乱序事件携带业务版本,旧版本不能覆盖已出库等新终态。发布器停机、消费者积压和死信都可用到达率、处理率和最老年龄计算恢复,不允许客户端无限重试放大。后台对账同时检查“有业务事实无 Outbox(发件箱)”“Outbox(发件箱)长期未发布”“已发布无消费”“已消费无业务结果”四类缺口,分别重建记录、重发、重放或转异常。Outbox(发件箱)解决的是本地事实与待发送记录的一致性,不解决消费者永久失败、错误事件内容或跨库业务原子性,所以仍需幂等、补偿和人工。真实消息产品、分区与保留期没有运行证据时保持待核对。发布和消费程序升级时还要用历史事件回放兼容性,防止新代码无法解释旧版本事实。
- 追问 1:发送成功后为何还要保留记录? 直答:需要覆盖消费者恢复、重放、对账和审计窗口。
- 追问 2:Inbox(收件箱)能否只放缓存? 直答:不可逆副作用不应只依赖可过期、可淘汰的缓存键。
- 追问 3:消息乱序怎么处理? 直答:同业务键尽量有序,消费者仍按状态机和版本拒绝倒退。
- 追问 4:死信是否代表业务失败? 直答:只代表自动消费预算耗尽,业务可能仍未知,需查询、补偿或人工裁决。
延伸阅读:MQ(消息队列)可靠性与幂等、分布式事务与消息一致性、MySQL(关系型数据库)日志与崩溃恢复。
综合题 07:库存、订单、仓单和实物如何对账与补偿
口述答案:对账先统一对象、窗口和时间语义,再比较数字。对象按货主、仓库、商品和必要批次分片,窗口同时保留业务发生时间与系统处理时间,避免迟到事件被误判为永久差异。第一层做库存内部守恒:期初快照加预占、释放、分配转换、出库、盘盈盘亏等流水应等于期末快照;第二层做订单承诺对预占,一笔有效履约应有唯一有效预占或明确失败;第三层做预占对仓单与任务,已推进履约应能找到仓单和仓内责任;第四层做仓单对下游与实物,外仓回执、拣货复核和出库证据应一致。差异不能统一“修余额”,要分类:有事实无投影就重放投影,有预占无仓单就重发事件或恢复消费,有外仓成功无本地关联就查单回填,重复释放或出库就由唯一键阻止并核对既有结果,实物差异则冻结库位、盘点和人工审批。自动补偿必须引用原业务键与差异单,写新的冲正或修正流水,不能覆盖历史;补偿自身也有唯一键、状态机、重试预算和再次对账。不可逆出库、证据冲突、数量过大或跨账期差异进入人工,审批和前后摘要全量审计。恢复完成的标准是差异清单收敛、历史未知态关闭、快照可由流水重放、业务对象终态明确,而不是定时任务没有报错。差异率、真实盘点结果和人工成本若无证据均保持待核对。对账自身也要保存分片检查点、口径版本和覆盖率,扫描失败的对象不能被统计成零差异,更不能提前关闭批次或隐藏分母。
对账批次关闭后还要抽样复算原始记录,扫描失败对象不能隐藏在分母之外。
- 追问 1:为什么要双时间? 直答:业务发生与系统入库可能不同,迟到事件需要在修订窗口内回填。
- 追问 2:能自动修复所有差异吗? 直答:不能,实物、不可逆出库和证据冲突必须人工裁决。
- 追问 3:补偿为什么用新流水? 直答:保留原错误与修正因果,才能审计和再次复算。
- 追问 4:对账任务失败怎么办? 直答:保存分片检查点和批次状态,幂等重跑,不把未扫描对象当成无差异。
延伸阅读:指标语义、双时间与重算、取消退款与对账机制、履约校验与补偿。
综合题 08:波次和批量任务如何兼顾吞吐、锁竞争与可恢复
口述答案:波次是调度分组,不应等同于一个数据库大事务。我会按仓库、截单时间、承运方式、库区和优先级形成波次,保存期望成员;真正分配库存和创建任务时再拆成有限小批,每批按稳定的库存键顺序处理并提交检查点。这样一条仓单失败只重做当前批,避免整波回滚、长时间持锁、日志膨胀和复制延迟。在线预占、取消和出库属于高优先级,批量波次走独立队列、工作池和令牌闸门,预留在线保底容量;当数据库连接、锁等待、死锁、事务时长或尾延迟超过阈值时,调度器缩小批次、降低并发或暂停领取,恢复后从检查点继续。背压必须传到任务入口,不能让工作者失败后立即重试形成重试风暴。批次大小没有固定答案,需要用商品行高分位、热点集中度、单行耗时和回滚成本做阶梯压测,同时注入节点失效、数据库慢和消息重复,验证单故障域后仍能恢复。任务租约只控制谁执行,业务副作用仍由唯一键和状态机幂等;租约过期的旧工作者不能覆盖新版本结果。容量可用流入率、处理率和净恢复率复算,业务还要给波次最长等待目标,避免为保护在线链路无限延迟仓内作业。成本上比较每个完成仓单的事务尝试、消息、存储和人工异常,不以最大并发作为单一成功标准。真实仓内班次和设备能力属于待核对。恢复演练还要在批次中途杀死工作者,确认已提交批次不重做、未提交批次能重新领取,波次聚合状态不会误报完成或遗漏成员。
恢复演练还要验证取消能打断未执行任务,波次聚合不会遗漏成员。
- 追问 1:如何选择批次大小? 直答:以事务时长、锁等待、回滚量和任务等待的联合压测拐点确定。
- 追问 2:暂停批量会丢任务吗? 直答:不会,任务和检查点持久化,停止的是领取和推进。
- 追问 3:租约能保证不重复执行吗? 直答:不能,网络暂停可能产生双执行,副作用必须另做幂等。
- 追问 4:怎样防止低价值波次挤占资源? 直答:优先级队列、租户和仓配额、在线保底及可暂停开关共同治理。
延伸阅读:工作负载与选型路线、MQ(消息队列)价值与排队、稳定性容量模型。
综合题 09:拣货少货、复核不符和出库重复分别怎么处理
口述答案:三类异常发生阶段不同,不能统一回滚库存。拣货少货说明分配账面与库位实物不一致,我会先记录任务、库位、商品、批次、应拣和实拣数量,冻结相关库存键并建立异常单,检查漏扫、错位、破损、此前盘点和并发任务;确认只是任务未完成可重新分配或跨仓,确认实物缺失则按审批写盘亏或调整流水,原订单再决定拆单、替代或取消。复核不符发生在商品、数量、批次或包裹确认阶段,应阻止出库,保留拣货证据,按差异类型返拣、换货、拆包或人工复核;不能为提高首次通过率自动覆盖扫描结果。出库重复属于不可逆副作用风险,请求必须携带仓单、包裹、复核版本和稳定动作号,数据库唯一键与“已复核到已出库”的条件迁移同事务写实物扣减、预占消耗、出库流水和事件。重复参数一致就返回原结果,参数不同则拒绝并告警;设备离线补传也按原动作号逐条校验,不按到达顺序覆盖终态。任何修正都用新流水引用原动作,对账再验证实物、快照、预占和订单是否收敛。止血时宁可冻结问题库位和暂停同类任务,也不直接增加可售或删除异常记录。观测应区分少货率、复核差异、重复出库命中、返拣时长和异常关闭质量;真实阈值和仓内责任制度需现场证据核对。客户侧还要由履约单聚合展示拆单、等待或缺货处理,不能因内部异常单尚未关闭就长期不给明确预期和处置时限。
异常关闭后还要抽样核对实物与系统结果,并给客户明确处置时限。
- 追问 1:少货后能立即释放预占吗? 直答:先确认实物与替代策略,直接释放可能掩盖账实差并违背客户承诺。
- 追问 2:复核为何引用拣货版本? 直答:防止迟到或重复的旧拣货结果进入新包裹。
- 追问 3:重复出库消息需要报错吗? 直答:参数一致应幂等返回原结果,参数冲突才拒绝并告警。
- 追问 4:如何证明异常关闭正确? 直答:检查调整或冲正流水、任务终态、实物复核和订单处置四类证据。
延伸阅读:WMS(仓储管理系统)仓内指标与护栏、漏斗与仓内效率演练、订单库存履约补偿。
综合题 10:热点商品和热点仓导致锁竞争时如何排查与治理
口述答案:我先从现象和分布取证,不把“数据库慢”当根因。按仓库、货主、商品和库存键统计请求量、条件更新失败、锁等待、事务时长、死锁、连接等待和重试,比较全局与热点高分位;再从慢事务和死锁环检查是否在锁内查询外仓、写大批流水、跨模块访问或以不稳定顺序锁多行。立即止血是限制热点键并发、暂停低优先级波次和批量调整、缩短请求超时并阻断无界重试,保护在线预占和出库。代码层把事务缩到条件更新、流水和 Outbox(发件箱),多键统一排序,减少无关索引更新;调度层按小批提交、保存检查点和动态降速;读侧用 Redis(远程字典服务)聚合、请求合并和热点预热降低查询回源,但最终扣减仍走数据库条件更新。若单行确实成为不可扩展串行点,可评估按业务可替代规则分桶或分片,但必须先解决余量碎片、路由、释放、迁移和对账,不能只把一个余额随意拆成多个不守恒的数。扩应用实例通常会增加竞争者,不会增加单键写能力。恢复验证要看热点等待、重试和尾延迟回落,积压在净处理能力下清空,库存守恒和未知态没有恶化。真实热点比例、数据库配置和收益保持待核对,方案是否采用分桶必须通过压测和架构决策记录审查。若热点只在活动短窗出现,还应优先采用预约容量、预热和业务限流,避免为偶发峰值永久引入复杂分桶模型和长期对账负担。
活动结束后要撤销临时限流与热点副本,复核是否留下长期对账负担。
- 追问 1:分布式锁能解决库存热点吗? 直答:通常只是把数据库排队移到锁服务,还增加租约与故障问题。
- 追问 2:扩读副本有用吗? 直答:可分担读模型,不能提升权威单键写入的串行能力。
- 追问 3:缓存预扣是否可行? 直答:可做削峰候选,但必须有持久化、回补、故障切换和数据库对账边界。
- 追问 4:何时考虑分桶? 直答:短事务、限流和批次治理后仍有可复现单键瓶颈,且业务允许明确拆分规则时。
延伸阅读:InnoDB(事务存储引擎)锁与死锁、缓存热点治理、ADR(架构决策记录)与退出条件。
综合题 11:Redis(远程字典服务)在库存方案中应该承担什么、不承担什么
口述答案:我先确定 MySQL(关系型数据库)中的库存快照、条件更新、唯一业务键和不可变流水是权威事实,Redis(远程字典服务)是可丢、可重建的加速层。它适合缓存按仓库、货主和商品聚合的查询库存,保存热点识别结果,做请求合并、预热、限流令牌和到期候选时间索引;入口可用缓存余量提前拒绝明显无货请求,降低数据库压力。但缓存显示有货不能直接确认预占,因为键可能过期、淘汰、复制延迟、故障切换丢写或收到乱序事件。权威事务成功后通过 Outbox(发件箱)事件删除或刷新缓存,值携带数据库版本和更新时间;删除失败有可靠重试,迟到旧值不能覆盖高版本,缓存全失时按限流回源并分批重建。热点键失效时用单一重建者或请求合并保护数据库,等待方有超时和降级;逻辑过期可以用于允许短时旧读的库存展示,不能用于最终放行。Redis(远程字典服务)分布式锁可保护缓存重建或跨节点任务领取,但不承担库存最终一致性,因为租约过期、网络分区和客户端暂停仍可能双执行,数据库唯一键和状态机必须兜底。预占过期也不能只靠键过期通知,通知可能丢失且不知道预占是否已分配;数据库到期状态和条件释放才是权威。排障时同时看命中率、回源率、单键流量、淘汰、客户端连接、服务端延迟和数据库版本差,不能看到缓存慢就把全部库存逻辑迁入缓存。真实缓存形态和命中率无证据时保持待核对。每次缓存恢复都要抽样比较版本和数量,确认没有把旧快照重新预热成新的错误读模型。
- 追问 1:缓存能否提前拒绝? 直答:可以,但数据库仍是最终裁决;缓存旧值造成误拒绝要有版本和回源策略。
- 追问 2:Lua(脚本语言)原子扣减够吗? 直答:只保证单执行点原子,不能覆盖持久化、切换和数据库落地窗口。
- 追问 3:缓存删除失败怎么办? 直答:可靠事件重试、版本保护、有限过期和后台版本对账共同收敛。
- 追问 4:热点键扩集群能解决吗? 直答:单键仍落到一个主分片,需本地缓存、请求合并、复制或业务拆键。
延伸阅读:Redis(远程字典服务)缓存一致性、Redis(远程字典服务)分布式锁、延迟任务、限流与项目案例。
综合题 12:MQ(消息队列)积压、重复、乱序和死信如何处理
口述答案:我先按业务影响分队列和优先级:库存事实、仓单创建和出库事件属于核心,通知、统计和低优先级批任务不能与其争抢同一恢复能力。积压发生时先冻结消费组、版本、位点和最老消息年龄,比较流入率、成功处理率、重试率、单条服务时间和下游限额,判断是生产突增、消费者变慢、毒消息、数据库锁还是外仓限流。止血时暂停非核心生产或消费,限制重试,把毒消息隔离到死信并保留原事件,不让同一失败对象占满工作线程。扩消费者前先确认分区、数据库连接和下游配额是否允许,否则只会把排队转移到库存库或外仓。恢复按净速率计算清空时间,分批提高并发并观察尾延迟、锁等待、未知态与业务差异。重复消息是至少一次投递的正常结果,消费者用事件键、Inbox(收件箱)、业务唯一键和状态机幂等;乱序则按业务键尽量路由同一顺序通道,同时携带版本,迟到事件不能把已出库改回已拣货。死信只表示自动重试预算耗尽,不等于业务明确失败:外仓创建类消息可能仍处未知态,要先查单;不可逆副作用需要人工裁决。恢复完成还要从 Outbox(发件箱)到 Inbox(收件箱)、仓单和库存流水做对账,验证积压期间没有消息缺口、重复扣减或错误释放。真实消息产品、分区数和吞吐都待核对,我只会给可复算模型与验证方法。若积压逼近消息保留期,还要优先扩展保留或导出原始事件,不能在未建立重放证据时让历史事实过期。
- 追问 1:积压就扩消费者吗? 直答:先找瓶颈和下游余量,分区、数据库或外仓可能先饱和。
- 追问 2:毒消息怎么处理? 直答:有限重试后隔离,保留上下文,修复后按业务键受控重放。
- 追问 3:乱序只能靠单分区吗? 直答:单键有序可减冲突,但消费者状态机和版本校验仍不可少。
- 追问 4:如何证明积压恢复? 直答:最老年龄归界、净积压清零、业务对账通过且下游未被重试压垮。
延伸阅读:MQ(消息队列)可靠性、顺序与重试、MQ(消息队列)容量与积压排查、分布式事务与补偿。
综合题 13:客户订单、履约单、仓单、流水、任务和异常单为什么要分层
口述答案:这些对象的责任人、生命周期和完成语义不同,合成一张大订单表会让状态互相覆盖。客户订单表达客户购买、支付和取消意图,可能跨多个仓和包裹;履约单把客户意图拆成可执行的仓库或路线责任,承接库存预占和路由;仓单是某个仓的执行主单,关联外仓单号和仓内作业;库存流水只记录数量变化事实,不能被仓单状态替代;波次、拣货、复核和出库任务记录谁在何时执行、尝试和截止时间;异常单承接无法自动收敛的差异、未知态和人工审批。分层后,一张客户订单可以拆多个履约单,一个履约单可以因跨仓或重路由形成多个仓单,但每次库存动作仍以稳定业务键关联原承诺,避免拆单后重复预占。状态聚合必须有明确规则:客户看到的“履约中”可以由多个仓单聚合,但内部不能因此丢掉某个仓单长期未知;任务完成也不自动等于库存流水已正确落地。主键设计使用租户与业务号、履约序号、仓库和动作序号,外部仓单号只是结果关联,不作为创建前身份。版本控制合法状态迁移,唯一键保护重复副作用,业务时间和处理时间支持迟到修订。归档按售后、重放、审计和合规窗口确定,不因订单终态就立即删除幂等证据。读模型可把分层对象聚合为查询视图,但每个权威对象仍只有一个写主责。真实拆单政策和保留期属于待核对。设计评审还要为每个实体写清创建者、唯一终态、可逆动作和删除条件,避免对象只增不退、无法归档或无人负责。
- 追问 1:为什么不直接用客户订单号做所有幂等键? 直答:一单可能多仓、多包裹和多动作,需要加入责任对象与动作阶段。
- 追问 2:任务终态能否驱动库存? 直答:可以发起动作,但库存事务仍按业务键、状态和数量独立裁决。
- 追问 3:异常单是否修改原单? 直答:它关联并推动受控状态迁移,不覆盖原始事实和历史证据。
- 追问 4:聚合状态如何防误导? 直答:定义优先级和未知态规则,并能下钻到每个履约单、仓单与任务。
延伸阅读:订单与履约领域模型、指标语义与事实模型、边界与数据所有权。
综合题 14:没有真实量级时怎样做容量设计和 E3(演练证据)说明
口述答案:没有生产证据时我不会报“系统能扛多少”,而是列出待核对输入、给出公式并做敏感性演练。入口输入包括峰值订单率及持续时间、每单商品行高分位、每行候选仓、支付和取消比例、预占重试、热点集中度;仓内输入包括每单任务数、波次大小、拣货复核服务时间;异步输入包括事件放大、保留期和失败重试;外部输入包括仓接口限额、超时率和查单配额。把订单率乘商品行、候选仓和重试可得到预占尝试率,再拆成快照更新、流水和 Outbox(发件箱)写入;用到达率乘高分位服务时间估算在途,用流入率与处理率之差判断积压,用积压除净恢复率估算清空时间。正常部署还要反推失去一个故障域后仍能承受核心峰值,批量任务和非核心消费可在故障时暂停。存储按每日业务对象乘单条字节、索引与副本放大、保留天数估算,审计和重放窗口不能为了省空间随意缩短。压测采用阶梯流量和真实热点分布,退出条件包括可售不变量、锁等待、尾延迟、未知态和对账差异;随后注入节点失效、消息重复、缓存全失和外仓变慢做恢复演练。所有演练输入和结果明确标 E3(演练证据),真实值从网关、订单明细、数据库、队列、外仓合同和账单取证后再校准。这样回答的是“如何建立可信容量承诺”,而不是用虚构数字制造经验。模型还要注明版本、复审日期和触发重算条件,促销结构、仓网或外部配额变化后旧结果立即失效并重新演练。
全量放量前还要用小流量再次核验模型输入和用户结果。
- 追问 1:平均商品行够吗? 直答:不够,大单高分位决定事务行数、锁时长和任务放大。
- 追问 2:为什么用净恢复率? 直答:恢复期间新流量仍持续到达,处理能力减流入才是真正消化速度。
- 追问 3:压测通过就能上线吗? 直答:还需恢复演练、业务守恒、下游配额和灰度门禁。
- 追问 4:如何向面试官说明数字? 直答:逐项声明为演练输入、展示公式,并列真实取证来源和失效条件。
延伸阅读:需求与量级澄清、工作负载证据边界、稳定性与容量评估。
综合题 15:怎样把成本放进库存与仓内方案,而不虚构账单
口述答案:我会比较单位正确履约结果成本,而不是只报机器数。成本模型至少包含库存事务计算与连接、流水和索引存储、消息传输与保留、缓存容量、外仓创建和查单调用、日志链路与审计、备份恢复、人工异常处理、迁移双运行和退出成本;还要加入超卖、重复履约、卡单和账实差的预期失败成本。没有账单时,每项只列单位、公式、相对方案和待核对来源,例如每十万次有效预占的实际事务尝试数会被重试率放大,消息积压会增加存储和恢复时间,盲目提高日志采样可能增加费用,而过度降低审计又会提高未知态定位与人工补偿成本。方案比较必须先满足不变量、恢复和安全硬约束,再在候选中优化成本,不能用低价抵消可售为负或资金与库存失配。热点治理可通过请求合并、短事务和批量背压减少无效尝试,归档可把超过在线查询窗口的流水转低成本存储,但仍保留校验和、索引与可恢复路径。批量任务可在低峰执行,却要计入等待承诺和峰谷迁移风险;外仓重试改成查询优先,既减少重复副作用,也可能降低调用浪费。每次优化后同时看单位成本、用户结果、对账差异、恢复时间和人工量,防止机器费用下降却把成本转给值班和客户。真实单价、折扣、仓人员成本和事故损失全部保持 E0(待核对),只有取得云账单、采购合同和工单记录才能升级。决策还应写清最低消费、锁定期和退役费用,避免便宜试点演变成无法退出的长期负担。
每次成本复审都要核对节省是否转化为新增人工异常与恢复负担。
- 追问 1:归档流水是否影响对账? 直答:可以分层存储,但必须在对账窗口内可检索、可校验和可恢复。
- 追问 2:降低日志采样能省钱吗? 直答:非关键读可优化,高风险库存调整、未知态和补偿证据不能缺失。
- 追问 3:缓存一定降成本吗? 直答:未必,要计入容量、失效、回源、运维和一致性补偿成本。
- 追问 4:成本阈值怎么定? 直答:结合预算、单位正确履约、失败成本和退出条件共同评审。
延伸阅读:质量权衡与失败成本、ADR(架构决策记录)与技术债、稳定性容量与成本。
综合题 16:库存调整、补偿重放和客户数据如何做安全与审计
口述答案:安全边界按租户、货主、仓库、岗位和动作建立,不能只判断用户是否登录。客服只能查看职责范围内的脱敏订单,仓内人员按仓和岗位执行拣货复核,库存调整、批量释放、补偿重放、修改外仓关联和关闭重大异常属于高风险动作,需要 RBAC(基于角色的访问控制)、对象范围、数量额度、原因码、审批或双人复核。服务间使用独立身份和最小权限,库存模块不接受订单服务直接改表;外仓凭据由秘密管理设施托管、轮换并禁止写入日志。每次高风险写入在执行前校验当前状态、版本和权限,执行时写受控业务流水,审计记录操作者、授权来源、审批人、业务对象、动作前后摘要、请求标识、时间与结果。敏感客户字段按履约目的最小传输,查询、导出和临时原文访问都留痕,测试与演练使用脱敏数据。审计不是事后附加日志:若关键审计存储不可用,高风险动作默认拒绝或进入独立可靠缓冲,不能静默继续;紧急通道要限额、限时、自动回收并事后复核。异常检测关注跨仓操作、短时大量释放、同人发起并批准、非工作时段原文查询和重复失败提权。恢复时不仅确认库存结果,还要确认权限未被永久放大、临时凭据已撤销、审计链完整。真实角色矩阵、保留期和合规要求需由组织制度与配置取证,本文只给设计合同。权限变更也要版本化并定期复核闲置账号、跨岗授权和长期例外,防止临时措施悄悄变成永久后门。
离职、转岗和仓库停用应自动触发权限回收与复核,长期例外必须到期。
- 追问 1:小额库存调整能否免双人复核? 直答:可风险分级,但权限、原因、额度累计和审计不能省略。
- 追问 2:运维能直接改库吗? 直答:应走受控补偿工具;紧急改库需审批、备份、脚本评审和前后对账。
- 追问 3:日志如何兼顾排障与隐私? 直答:保留业务标识与摘要,敏感原文脱敏,临时查看走提权和全量审计。
- 追问 4:审计成功是否证明操作正确? 直答:只证明证据完整,仍需状态机、不变量和对账验证业务正确。
延伸阅读:质量属性中的安全与可观测、支付与履约安全审计、指标语义与隐私修订。
综合题 17:如何从模块化单体迁移到服务化和事件化
口述答案:我不会按技术名词一次性拆分,而是先建立可迁移边界。第一步在单体内明确履约、库存、仓内、外仓适配和对账模块的表归属与接口,统一业务键、状态机、库存流水、Outbox(发件箱)和审计,并用架构测试阻止跨模块直连。第二步建立事实事件镜像和只读投影,新路径只消费旧权威事件,逐业务键比较状态、版本和延迟,不参与主写。第三步优先拆外仓适配、通知、扫描和异步任务,因为它们需要独立容量和故障隔离且可以路由回旧模块;随后拆仓内任务执行,但仓单主责和事件合同保持清晰。库存权威写最后迁移:先影子读,再按仓库或租户小流量切换,双写只用于校验,必须单主裁决业务成功,避免两个余额源并存。每阶段定义准入、差异阈值、最老事件年龄、未知态、对账和回退窗口;任一超限停止扩面,路由切回旧主写,并用流水和事件重放新路径缺口。数据库模式变更采用兼容的先扩后缩,消费者支持事件版本,旧字段和旧路径只有在无调用、无回放依赖且审计窗口结束后才退役。服务化收益要由独立扩缩容、发布节奏或故障隔离证据证明,否则模块化单体可能更合适。真实组织边界、部署约束和迁移时长均待核对,方案保留可撤销的阶段门。每次切换前还要实际执行回退脚本,测量路由恢复、事件追平和业务对账时间,而不是只在文档中写“可以回滚”。
退出旧路径前还要证明没有迟到消息、历史补偿和审计查询继续依赖它。
- 追问 1:为什么库存最后拆? 直答:它承载最强不变量,双主冲突最难补偿,迁移风险最高。
- 追问 2:双写为何不能双主? 直答:部分失败时无法决定哪个结果对用户生效,也难以合并流水。
- 追问 3:事件版本怎么兼容? 直答:先增加可选字段,消费者容忍新旧版本,完成回放后再退役旧字段。
- 追问 4:何时停止拆分? 直答:当收益没有超过网络失败、数据一致性和运维成本,或适应度门禁不通过时。
延伸阅读:ADR(架构决策记录)、可逆性与退出、边界与数据所有权、选型迁移与失败回退。
综合题 18:线上发现可售为负,如何从止血到恢复
口述答案:我先把可售为负当作业务正确性事故,而不是先执行一条修正 SQL(结构化查询语言)。第一步冻结时间线、版本和受影响的仓库、货主、商品与批次,暂停这些键的新增预占、批量波次和自动补偿,保留只读查询;若影响扩大则按仓或租户限流。第二步保存库存快照、最近流水、订单与仓单状态、Outbox(发件箱)和 Inbox(收件箱)、消息位点、人工调整审计、死锁与事务日志,避免后续重试覆盖证据。第三步从最近可信期初按业务时间重放预占、释放、分配、出库、盘盈盘亏和冲正流水,比较计算快照与在线快照;同时检查条件更新是否被绕过、同业务键唯一约束是否缺失、重复释放或出库、批量任务旧版本覆盖、缓存值被当权威以及人工直接改库。差异按对象清单分类,能证明是投影错误就重建投影,能证明缺失事实就通过审批新增引用原动作的补偿流水,涉及实物就盘点,证据不足保持冻结并人工裁决,绝不把负数直接改零。恢复先在影子环境或单个键回放,验证幂等和守恒,再分批开放在线预占;持续监控条件失败、锁等待、差异和未知态,确认历史订单与仓单也得到处理。事故后补上绕过路径的架构测试、权限门禁、对账告警和恢复演练。若根因尚不能由证据唯一解释,就明确保持未知,不编造结论。对外沟通还要按确认、推断和未知分层,持续更新受影响订单清单,避免技术恢复先于客户处置被宣布完成。
复盘行动项必须在下一次恢复演练中验证关闭,不能只关闭事故工单。
- 追问 1:为什么先暂停批量任务? 直答:它们会扩大写入和锁竞争,并可能继续消费错误快照。
- 追问 2:缓存需要清空吗? 直答:只处理受影响键并限流回源,盲目全清可能造成数据库雪崩。
- 追问 3:如何处理受影响订单? 直答:按预占、仓单和实物证据列清单,逐单重路由、取消、补货或人工。
- 追问 4:何时算恢复完成? 直答:不仅可售非负,还要流水守恒、历史差异闭环、灰度稳定且补偿可审计。
延伸阅读:MySQL(关系型数据库)事务与并发、锁等待与死锁排查、稳定性恢复与业务核验。
综合题 19:出现重复仓单或重复出库,怎样定位并修复
口述答案:我会先区分重复展示、重复本地记录、重复外仓创建和重复实物出库,因为失败成本与修复动作不同。以客户订单、履约单、稳定请求号、外部仓单号、包裹和出库动作号建立关联清单,冻结相关订单后续自动推进;外仓仍可能执行时先查单,不立即取消或重建。重复本地仓单要检查创建事件是否重复、Inbox(收件箱)是否缺失、业务唯一键粒度是否错误、消费者事务是否在副作用后才登记幂等;重复外仓单要检查超时后是否换请求号、适配层是否未保存未知态、下游幂等窗口和查询是否失效;重复出库要检查设备离线补传、复核版本、出库动作唯一键以及状态条件是否被绕过。修复时保留所有原始记录:重复展示可重建读模型;多余本地仓单在确认无作业后受控关闭;外仓重复创建必须结合哪个单已作业、哪个可取消以及库存承诺决定,不能随机删一个;已重复实物出库属于不可逆事故,需要拦截运输、退货、补货、客户沟通和库存冲正。任何补偿都引用原动作、经过权限审批并再次对账。短期加唯一约束前要先清理历史冲突并评估上线锁;长期统一稳定请求号、Outbox(发件箱)与 Inbox(收件箱)、查询优先和参数摘要。恢复验证包括重复对象清零、库存与实物收敛、下游关联唯一和迟到消息重放不再产生副作用。还要回放原故障时间线,验证同样的超时、重复和离线补传组合现在只会得到一个业务结果。
恢复后还要确认所有临时冻结、人工权限和运输拦截均已撤销或明确接管。
- 追问 1:删除重复数据库行可以吗? 直答:不能直接删,先判断是否已有仓内或外仓副作用,并保留审计关系。
- 追问 2:唯一键加上就结束了吗? 直答:还需处理历史数据、参数冲突、下游幂等和未知态查询。
- 追问 3:哪个仓单保留? 直答:按实际作业、外仓回执、库存流水和业务承诺证据裁决,不按创建时间猜。
- 追问 4:如何防止迟到消息复发? 直答:事件键、参数摘要、状态版本和业务唯一约束共同拒绝旧副作用。
延伸阅读:MQ(消息队列)幂等与重试、履约未知态、查单与补偿、分布式事务消息一致性。
综合题 20:这个方案最重要的架构取舍、证据边界和项目话术是什么
口述答案:最重要的取舍不是选 MySQL(关系型数据库)、Redis(远程字典服务)还是 MQ(消息队列),而是先决定什么事实绝不能错、什么结果可以等待、什么未知必须保守处理。我把库存承诺放在条件更新、唯一键、流水和对账组成的权威事务边界,牺牲热点下的一部分吞吐,也不允许可售为负;把仓单、通知和任务做成可重放异步链,接受至少一次、短时延迟和积压,但要求副作用幂等、状态可查、恢复可算;外仓超时选择未知先查单,宁可短时占用库存,也不错误释放造成重复履约。Redis(远程字典服务)用于查询、热点和限流,不成为最终库存;批量波次在在线预占、出库和恢复面前可降速;高风险补偿宁可人工审批,也不追求全自动覆盖不可逆事实。成本优化以单位正确履约为目标,不能通过删除审计或缩短幂等窗口转移风险。迁移从模块化单体开始,事件镜像和可回退副作用先拆,库存单主写最后迁移。面试口述时我先声明 E2(已有材料映射)能证明项目主题,E3(演练证据)只展示量级公式与方案推理,真实实现、峰值、收益和事故规模保持 E0(待核对);随后按需求量级、不变量、架构数据、正常失败、容量安全成本、迁移验证讲完,并主动给出反例与撤销条件。这样深度来自因果、失败路径和验证,而不是虚构上线数字。若面试官追问真实结果,我会说明需要源码、表结构、监控、流水和复盘记录才能升级具体陈述。
- 追问 1:为什么不追求全部强一致? 直答:通知和任务可恢复,强行同步会扩大故障;库存承诺与不可逆出库才需要强边界。
- 追问 2:什么时候推翻当前方案? 直答:不变量无法满足、恢复窗口超限、成本不可接受或团队无法运维时触发复审。
- 追问 3:没有收益数字会不会显得弱? 直答:不会,诚实给出公式、证据缺口、验证计划和失败边界更可信。
- 追问 4:一句话总结方案? 直答:核心事实同步守恒,可恢复副作用异步推进,未知先查询,差异靠对账补偿收敛。
延伸阅读:质量属性权衡与失败成本、ADR(架构决策记录)与可逆决策、案例索引与事实边界。
14. 复习与审计清单
- 能在三分钟内从事实边界讲到迁移回退,不虚构生产量级和收益。
- 能写出可售不为负、唯一预占、唯一释放、唯一出库和流水守恒五类不变量。
- 能解释支付、取消、过期与仓内阶段竞态,不按消息到达先后猜终态。
- 能画出预占、Outbox(发件箱)、消费幂等、仓内作业、外仓未知查单和对账补偿链路。
- 能用订单率、商品行、候选仓、重试、服务时间和净恢复率复算容量。
- 能区分缓存加速、消息传输和数据库权威事实的责任边界。
- 能从热点键、事务时长、锁等待、批次和重试放大排查性能问题。
- 能说明库存调整、批量释放、补偿重放和客户数据的权限与审计要求。
- 能按影子读、单主写、灰度、对账、回退和退役说明服务化演进。
- 能逐项核对 12 个知识型小节、36 道六字段题、20 道综合题、12 张 Mermaid(图表语法)、12 张表、12 个数据演绎和 1 张 PlantUML(统一建模语言)正式时序图。
