Spring(Java 应用框架)项目排障、事故复盘与综合面试题库
知识图谱编号:
2.3.8。本篇不复制2.3.1至2.3.7的机制正文,而是把容器、代理、事务、请求、安全与持久层串成生产证据链。所有数量和指标均为面试演绎样例,真实面试时应替换为本人项目中可核对的数据。
1. 排障总原则:先证明事实,再解释机制
线上问题不能以“加注解、重启、扩容”结束。统一顺序是:确认影响面,执行可回滚止血,保存制品与现场,建立多个可证伪假设,用日志、指标、链路、线程、连接和数据库最终状态收敛根因,再完成修复、故障注入验证和组织复盘。框架日志只说明某个技术阶段,订单、资金、库存和任务状态才是业务结论。
flowchart LR
A["告警与用户现象"] --> B["确认业务影响和时间窗"]
B --> C["可回滚止血"]
C --> D["冻结制品、配置和现场证据"]
D --> E["提出可证伪假设"]
E --> F["容器、代理、事务、请求、安全、持久层逐层验证"]
F --> G{"证据能解释业务最终状态?"}
G -->|"否"| E
G -->|"是"| H["根因与修复"]
H --> I["回归、灰度和故障注入"]
I --> J["复盘、守门和知识沉淀"]图解:节点是一次事故必须留下的证据阶段;箭头是从现象到可验证结论的推进顺序;前提是止血动作可回滚且不销毁现场;正常路径以数据库和外部渠道最终状态闭环;失败路径返回假设而不是继续猜;业务结论是“服务恢复”不等于“业务数据恢复”。
2. 十二类项目与事故实战
2.1 Bean(对象实例)创建、启动失败与就绪门禁
事故展开:现象是新版本实例反复重启且未进入就绪;影响是发布容量下降,旧实例退出后支付回调积压。证据包括制品摘要、配置版本、完整最内层异常、条件评估报告、容器刷新阶段耗时和已创建线程。假设依次为配置绑定失败、候选冲突、初始化远程调用超时和端口占用。止血是暂停滚动发布并保留旧实例,不把坏实例强行加入流量。根因若是渠道客户端在初始化回调中无界探测外部地址,应改成启动期只校验本地配置与密钥格式,就绪后有界预热;核心依赖失败保持不就绪,可选渠道按能力降级。验证要覆盖缺配置、外部超时、部分渠道失败和关闭,确认完成事件未发布时不会接流量。机制详见容器与 Bean(对象实例)生命周期和启动与自动配置。
flowchart TD
A["启动失败"] --> B["固定制品、配置和启动参数"]
B --> C["读取最内层异常"]
C --> D{"失败阶段"}
D -->|"定义"| E["扫描、导入、条件和覆盖"]
D -->|"实例化"| F["构造器、工厂方法和候选"]
D -->|"初始化"| G["回调、外部探测和线程"]
D -->|"网页服务器"| H["端口、权限和连接器"]
E --> I["最小配置复现"]
F --> I
G --> I
H --> I
I --> J{"修复后完成刷新且就绪?"}
J -->|"否"| C
J -->|"是"| K["灰度并验证资源释放"]| 证据 | 能证明什么 | 不能证明什么 | 处置 |
|---|---|---|---|
| 最内层异常 | 首个失败阶段和直接原因 | 业务数据已经安全 | 保留完整异常链 |
| 条件评估报告 | 自动配置为何命中或后退 | 对象运行一定正确 | 与依赖树和配置交叉验证 |
| 完成与就绪事件 | 容器阶段已经完成 | 所有外部渠道可用 | 核心/可选能力分级 |
| 线程和连接差值 | 失败清理是否泄漏资源 | 后续请求语义正确 | 注入失败并观察归零 |
数据演绎 1:10:00:00 发布 v42,首个实例在 18s 时因渠道探测超时失败;五个实例连续重试使启动线程从 5 增至 47,旧池容量从 20 降至 12,回调积压从 0 升至 860。暂停发布后保留 16 个旧实例;把探测改成 800ms 超时、两次重试并移到就绪后,单实例刷新降到 6.2s。故障注入一个非核心渠道超时,新实例仍就绪但该渠道路由关闭;核心账务库不可达时实例保持不就绪。灰度 30 分钟后积压归零、线程回到基线且无未知支付。
热门面试题
- 问题(基础题):启动失败为什么先看最内层异常而不是最外层异常?
- 考点:异常包装与创建阶段定位。
- 回答思路:从外层失败对象追到首个可操作原因。
- 详细答案:外层通常只是上下文刷新或对象创建失败的包装,最内层才可能指出配置无法绑定、候选缺失、端口占用或初始化超时。仍需保留完整链,因为中间层给出对象名、定义来源和依赖路径;只截最后一行会丢失上下文。
- 进阶追问:最内层异常一定是根因吗?
- 进阶回答:不一定,它是技术直接原因,还要用配置版本、线程栈、网络证据和业务状态证明为什么在该版本出现以及是否解释全部影响。
- 问题(原理题):为什么初始化回调不宜执行无界远程调用?
- 考点:容器刷新原子性和失败放大。
- 回答思路:说明主线程阻塞、半成品清理和多实例重试。
- 详细答案:关键单例初始化失败会取消整个刷新,远程调用若无超时会占住启动线程和连接;编排系统又可能不断重启,形成对故障依赖的探测风暴。启动阶段应做本地、确定、快速校验,远程能力用有界预热和分级就绪表达。
- 进阶追问:所有远程依赖都移出启动会不会接到错误流量?
- 进阶回答:核心依赖应进入就绪门禁但使用短超时和缓存结果;可选依赖按功能降级,不能把“移出初始化”误解为“不校验”。
- 问题(项目题):如何证明一次启动修复真正有效?
- 考点:故障注入与业务验收。
- 回答思路:覆盖正常、缺配置、超时、部分失败和关闭。
- 详细答案:固定制品与配置,分别注入必需配置缺失、核心库不可达、可选渠道超时和进程强杀;检查坏实例不就绪、可选能力可降级、刷新失败资源归零、旧实例仍承载流量,并核对回调、流水和库存无未知状态。
- 进阶追问:只看健康检查够吗?
- 进阶回答:不够,还要核对容器事件、线程连接、路由状态、业务积压和数据对账,健康检查只是一个观测入口。
2.2 循环依赖、早期代理与对象身份事故
事故展开:现象是应用升级后出现循环依赖错误,临时打开兼容开关虽能启动,但部分库存方法没有事务和审计。影响是同一业务对象存在原对象与代理两个身份,条件扣减在异常时未回滚。证据要包括依赖创建链、注入方式、作用域、容器最终引用类型、被依赖方持有的引用类型和通知器链。假设是构造器双向依赖、原型对象参与、初始化中泄露 this 或早期代理与最终代理不一致。止血应回滚版本或关闭危险入口,而不是永久允许循环。根因修复是抽取上层编排、领域事件或第三服务形成单向依赖;必须保留同步返回语义时,由编排器显式组织调用。验证比较引用身份并执行回滚、权限和审计测试。机制详见三级缓存、早期代理与重构边界和代理调用链。
sequenceDiagram
participant C as "容器"
participant A as "库存服务 A"
participant B as "履约服务 B"
participant P as "最终代理"
C->>A: "实例化 A"
C->>C: "登记 A 的早期引用工厂"
C->>B: "A 依赖 B,开始创建 B"
B->>C: "B 请求 A"
C-->>B: "返回 A 的早期代理引用"
C->>A: "完成填充和初始化"
C->>P: "形成最终代理并校验身份"
alt "早期代理与最终代理一致"
P-->>C: "发布同一身份"
else "原对象泄漏或重复包装"
C-->>C: "启动失败或行为不一致"
end| 场景 | 能否依靠早期引用 | 主要风险 | 首选方案 |
|---|---|---|---|
| 单例属性循环 | 某些条件下可兼容 | 半初始化、身份不一致 | 重构依赖方向 |
| 构造器循环 | 不可 | 双方都没有原始实例 | 抽取编排器 |
| 原型循环 | 不可稳定缓存 | 每次创建链不同 | 消除循环 |
| 跨线程初始化 | 不应依赖 | 可见性、等待和死锁 | 生命周期内不启动业务线程 |
数据演绎 2:版本 v17 中 InventoryService 与 FulfillmentService 改为构造器互相依赖,12 个实例全部启动失败。临时改回属性注入后实例启动,但压测 10,000 次失败扣减有 37 次库存未回滚。身份采样显示履约服务持有原对象,而容器导出的是事务代理。回滚止血后抽取 AllocationCoordinator 统一编排,两个领域服务只暴露单向端口;再次压测 100,000 次,异常回滚率 100%,审计切面命中率 100%,依赖图不再成环。
热门面试题
- 问题(基础题):框架能处理循环依赖,为什么工程上仍应消除?
- 考点:能力与设计策略边界。
- 回答思路:区分“能创建对象”和“职责合理”。
- 详细答案:早期引用只在有限的单例属性注入场景帮助完成创建,不能解决构造器、原型、跨线程和任意代理组合;双向依赖还说明业务职责和调用方向不清。即使启动成功,也可能出现半初始化对象、代理身份不一致和测试困难。
- 进阶追问:临时兼容何时可以接受?
- 进阶回答:只在有监控、明确退出日期、完整代理一致性回归且无法立即重构时短期使用,不能成为默认架构。
- 问题(原理题):怎样证明早期引用和最终代理身份一致?
- 考点:代理形成时点。
- 回答思路:比较引用类型、匿名身份和通知链。
- 详细答案:在受控诊断中比较依赖方持有引用与容器最终按名称取得引用的类型、代理类别和匿名身份,检查两者通知器链;再执行事务回滚、权限和审计调用,确认都从外部代理入口经过。不能打印对象全部字段或泄露密钥。
- 进阶追问:行为测试通过就不用比身份了吗?
- 进阶回答:两者互补;行为覆盖有限,身份证据能发现某些分支持有原对象,而行为测试证明关键通知真正执行。
- 问题(项目题):库存和履约双向调用怎样重构?
- 考点:领域边界与编排。
- 回答思路:抽取用例级协调者并保持领域服务单向。
- 详细答案:由应用层协调者先申请库存再创建履约,失败时按状态机释放或补偿;库存服务不反向调用履约,履约只消费明确的库存结果。跨系统步骤使用幂等命令和可靠事件,不能用容器循环引用模拟分布式事务。
- 进阶追问:抽取协调者会不会变成大类?
- 进阶回答:协调者只表达用例顺序和补偿,领域规则仍归各聚合;按用例拆分并用状态机限制职责膨胀。
2.3 AOP(面向切面编程)、事务失效与连接池耗尽
事故展开:现象是批量出库偶发部分提交,同时连接池等待持续升高。影响是订单状态、库存流水和消息记录不一致。证据必须同时证明实际引用是否为代理、调用是否从代理外部进入、事务管理器与数据源是否匹配、线程是否切换、连接何时获取与归还、异常是否传播、数据库最终行状态。假设包括同类自调用、非公开方法、异常被捕获、错误回滚规则、事务内远程调用、REQUIRES_NEW(新建事务传播)嵌套和异步线程越界。止血是关闭批量入口、降低并发并补偿未决记录。根因若是外层事务持有连接等待渠道,再由内层新事务申请第二连接,会形成容量乘法;应缩短本地事务,把远程交互移到状态机和可靠消息,按调用链设计池容量。详见代理拦截边界和事务传播与一致性。
flowchart TD
A["事务失效或连接等待"] --> B["确认调用引用是否为代理"]
B --> C{"是否从外部进入可拦截方法?"}
C -->|"否"| D["定位自调用、私有方法或原对象"]
C -->|"是"| E["确认事务管理器与数据源"]
E --> F["跟踪连接获取、绑定和归还"]
F --> G["确认异常传播与回滚标记"]
G --> H["核对数据库最终状态"]
H --> I{"存在远程等待或嵌套新事务?"}
I -->|"是"| J["缩短事务并改状态机/消息"]
I -->|"否"| K["修复代理、规则或资源绑定"]| 证据层 | 关键问题 | 常见误判 | 验证 |
|---|---|---|---|
| 代理 | 调用是否经过拦截器 | 看到注解就认为生效 | 记录代理类型和调用入口 |
| 事务 | 管理器、传播和回滚标记 | 只看开始日志 | 跟踪事务标识与挂起恢复 |
| 连接 | 获取、绑定、等待、归还 | 只扩池不改长事务 | 对齐池指标和线程栈 |
| 数据 | 最终提交了哪些行 | 日志报错等于已回滚 | 独立连接核对最终状态 |
数据演绎 3:连接池上限 30,批量入口并发 20。每个外层事务先占一条连接,再调用平均 1.8s 的远程运价接口,其中 40% 请求进入 REQUIRES_NEW(新建事务传播)审计并申请第二条连接;峰值需求约 28 至 40,等待 p99 达 4.6s,超时率 7.3%。止血把并发降至 8。修复后远程调用移到事务外,本地更新和 Outbox(发件箱)写入控制在 45ms 内,审计合并同一事务;等待 p99 降至 12ms,连接使用峰值 14,注入渠道超时也不会占用数据库连接。
热门面试题
- 问题(基础题):事务注解存在但不生效,先查什么?
- 考点:代理入口、管理器、异常和最终状态。
- 回答思路:按证据链排查而不是补注解。
- 详细答案:先确认对象是否代理且调用从代理外部进入,再核对方法可拦截性、事务管理器和数据源;随后跟踪连接绑定、线程切换、异常是否被捕获、回滚标记和独立连接看到的最终数据。单看注解或日志都不能证明事务存在。
- 进阶追问:为什么必须查数据库最终状态?
- 进阶回答:应用日志可能在提交前打印成功或在回滚后保留失败信息,只有事务外独立观察才能确认哪些数据真正提交。
- 问题(原理题):
REQUIRES_NEW(新建事务传播)为何可能耗尽连接池?- 考点:挂起资源和容量乘法。
- 回答思路:说明外层连接仍被占用,内层再申请连接。
- 详细答案:外层逻辑事务挂起时,已绑定物理连接通常不会归还;同一线程进入新事务还要申请另一连接。若并发接近池上限,每个请求都需要第二连接,就可能互相等待。容量应按最大嵌套和并发估算,更根本的是缩短事务并减少不必要的新事务。
- 进阶追问:把连接池翻倍能解决吗?
- 进阶回答:只能暂时推迟,长远程等待、嵌套深度和数据库承载仍在;先消除资源占用模型问题,再根据数据库容量定池。
- 问题(项目题):支付渠道调用为什么不应放在数据库事务里?
- 考点:未知结果和长事务。
- 回答思路:区分本地原子性与跨系统状态机。
- 详细答案:远程超时不能判断对方未执行,数据库事务既无法回滚渠道,又会长期占锁和连接。应先持久化本地意图,再发起渠道调用,根据明确结果或主动查单推进状态,使用唯一流水、幂等、补偿与对账闭环。
- 进阶追问:那如何保证消息不丢?
- 进阶回答:业务状态和 Outbox(发件箱)在同一本地事务提交,由发布器重试,消费者幂等,死信和对账处理长期失败。
2.4 支付回调重复、本地事务消息与资金一致性
项目话术:背景是支付渠道会重复通知、乱序通知和超时重试,且本地订单、资金流水、余额和下游履约不能共享一个数据库事务。约束是回调响应要快、验签必须在状态变更前、金额币种不可相信请求体、渠道超时可能已成功。设计上以渠道、商户、渠道事件号建立唯一幂等键,保存原始报文摘要和验签版本;在同一本地事务中锁定支付单、校验合法状态迁移、写不可变资金流水与 Outbox(发件箱),提交后快速应答。发布器将事件投递 MQ(消息队列),消费方以业务事件号幂等;未知支付由查单,对账按渠道账单、支付单和资金流水三方核对。取舍是接受跨系统最终一致而不伪造全局原子性。事故时先暂停自动履约、保留收款入口和对账证据,再按状态补发或人工闭环。机制链接事务与 Outbox(发件箱)和请求绑定与异常处理。
sequenceDiagram
participant P as "支付渠道"
participant W as "回调入口"
participant D as "本地数据库"
participant O as "Outbox 发布器"
participant M as "MQ(消息队列)"
participant F as "履约服务"
P->>W: "重复或乱序回调"
W->>W: "验签、商户、金额、币种校验"
W->>D: "唯一事件号 + 锁定支付单"
D->>D: "状态迁移 + 资金流水 + Outbox 同事务"
D-->>W: "提交成功"
W-->>P: "快速成功响应"
O->>D: "扫描未发布事件"
O->>M: "至少一次投递"
M->>F: "可能重复消费"
F->>F: "业务事件号幂等履约"stateDiagram-v2
[*] --> CREATED
CREATED --> PROCESSING: "发起渠道"
PROCESSING --> SUCCEEDED: "明确成功或查单成功"
PROCESSING --> FAILED: "明确失败"
PROCESSING --> UNKNOWN: "超时或响应不可判定"
UNKNOWN --> SUCCEEDED: "查单/对账确认成功"
UNKNOWN --> FAILED: "查单确认失败"
SUCCEEDED --> REFUNDING: "退款申请"
REFUNDING --> REFUNDED: "退款确认"| 控制点 | 唯一键/状态 | 失败窗口 | 恢复方式 |
|---|---|---|---|
| 回调接收 | 渠道+商户+事件号 | 验签后进程崩溃 | 渠道重试或主动查单 |
| 本地提交 | 支付单版本+流水号 | 提交结果未知 | 按唯一键查询,不盲重做 |
| 事件发布 | Outbox(发件箱)事件号 | 提交后未投递 | 发布器重试 |
| 履约消费 | 业务事件号 | 消费成功但确认失败 | 幂等重放 |
数据演绎 4:渠道在 3s 内发送同一事件 E9001 三次,并在 30s 后补发一次。第一请求验签通过,将支付单 P77 从 PROCESSING 改为 SUCCEEDED,写流水 L88 与事件 O99;第二请求命中唯一键直接返回已处理,第三请求发现状态已终态且金额一致,也返回成功。发布器首次投递后在确认前崩溃,消息被再次投递;履约以 O99 唯一键只创建一个出库单。最终统计回调四次、本地状态变更一次、资金流水一条、消息投递两次、履约一次,金额守恒。
热门面试题
- 问题(基础题):支付回调幂等只用缓存锁够吗?
- 考点:持久化唯一性和故障恢复。
- 回答思路:锁用于并发收敛,唯一约束用于最终事实。
- 详细答案:不够。缓存可能过期、故障转移或被误删,锁也不能证明历史事件处理结果。必须用数据库唯一键、合法状态迁移和不可变流水形成持久化幂等;锁可降低热点竞争,但即使锁失效,唯一约束仍阻止重复资金动作。
- 进阶追问:唯一键怎么选?
- 进阶回答:优先渠道稳定事件号并带商户和渠道命名空间;若渠道不保证,组合支付单、事件类型和业务阶段,同时保存原始摘要供审计。
- 问题(原理题):为什么本地事务加 Outbox(发件箱)能防止业务提交后消息丢失?
- 考点:同库原子提交与至少一次发布。
- 回答思路:业务状态和待发事件同成同败。
- 详细答案:业务更新与事件记录使用同一本地事务,因此不存在业务已提交而事件完全没有持久化的窗口。提交后发布器可反复扫描未发布记录;确认丢失会重复投递,所以消费者必须幂等。它解决的是可靠记录与重试,不保证跨系统瞬时一致。
- 进阶追问:发布成功后更新状态失败怎么办?
- 进阶回答:事件会再次发布,依赖消费者幂等;发布状态可用版本条件更新,并通过积压、重试和对账监控闭环。
- 问题(项目题):支付回调事故如何止血而不扩大资金风险?
- 考点:收款、履约和对账分层。
- 回答思路:保留证据,暂停不可逆下游动作。
- 详细答案:优先保持验签和回调落库,暂停自动履约、退款或余额变更等不可逆动作;冻结相关事件号和时间窗,保存报文摘要、密钥版本、流水、状态历史与消息轨迹。修复后先查单和对账,再按幂等命令补发,不能直接改终态。
- 进阶追问:渠道要求很快应答怎么办?
- 进阶回答:把验签、幂等和本地持久化做成短事务,提交后立即应答;耗时履约、通知和对账异步执行。
2.5 WMS(仓储管理系统)库存并发、防超卖与事务边界
项目话术:背景是多仓、多货主库存同时被下单、取消、波次和人工调整,热点商品峰值并发高。约束是不能只依赖应用锁,多实例、重试和消息重复都存在;也不能在数据库事务中调用仓外系统。设计以库存台账为权威事实,条件更新 available >= quantity 保证不为负,受影响行数决定成功;请求号和业务类型建立唯一流水,预占、确认、释放使用状态机,重复命令返回既有结果。批量分配按稳定顺序锁定库存行,缩短事务并避免死锁;跨服务通过可靠事件推进,超时订单由扫描与对账释放。取舍是热点行吞吐有限,必要时分仓、分桶或排队,但守恒不下放给缓存。详见事务边界和MyBatis(持久层框架)条件更新。
flowchart LR
A["预占命令和请求号"] --> B["幂等流水唯一检查"]
B --> C["条件扣减可用库存"]
C --> D{"受影响行数为 1?"}
D -->|"是"| E["写预占明细和 Outbox(发件箱)"]
D -->|"否"| F["库存不足或并发冲突"]
E --> G["事务提交"]
G --> H["异步通知订单/履约"]
H --> I{"后续成功?"}
I -->|"成功"| J["确认预占"]
I -->|"取消/超时"| K["幂等释放"]| 方案 | 一致性依据 | 优点 | 风险与边界 |
|---|---|---|---|
| 应用内锁 | 单进程对象 | 简单 | 多实例无效 |
| 分布式锁 | 外部租约 | 收敛热点 | 锁过期、脑裂,仍需数据约束 |
| 条件更新 | 数据库原子行更新 | 直接守住不为负 | 热点行竞争 |
| 串行队列 | 分区顺序 | 平滑峰值 | 分区热点、积压和恢复 |
数据演绎 5:商品 S1 可用量 10,同时收到请求 R1=7、R2=6、R1 重试。R1 首次条件更新成功,可用变 3 并写预占流水;R2 因 3<6 影响行数为 0,返回不足;R1 重试命中唯一流水,返回第一次结果而不再扣减。订单随后取消,释放命令以原预占号只执行一次,可用恢复 10。压测 50,000 个含 20% 重复的命令,最终可用、预占、已占用之和始终等于期初加调整量。
热门面试题
- 问题(基础题):库存防超卖为什么不能只用分布式锁?
- 考点:协调机制与数据不变量。
- 回答思路:锁可能失效,数据库必须守底线。
- 详细答案:分布式锁受租约过期、暂停、网络分区和故障转移影响,而且旁路程序可能不加锁。库存不为负属于数据不变量,应由条件更新、版本或约束保证;锁只用于降低竞争或协调复杂流程,失败也不能突破底线。
- 进阶追问:条件更新失败如何区分不足和版本冲突?
- 进阶回答:在同一事务内读取当前量或通过返回版本判断;对外可统一为需重试或库存不足,但内部指标要区分以便容量治理。
- 问题(原理题):批量分配为何要按稳定顺序锁行?
- 考点:死锁等待图。
- 回答思路:所有事务使用同一资源顺序破坏循环等待。
- 详细答案:若事务甲先锁商品 A 再锁 B,事务乙反向,就可能各持一把锁等待另一把。按仓、货主、商品稳定排序后,所有事务以同一顺序申请,消除循环等待条件;仍需短事务、超时和死锁重试。
- 进阶追问:死锁重试会不会重复扣减?
- 进阶回答:事务回滚后以同一幂等请求号重试,唯一流水和状态机确保只形成一次业务结果。
- 问题(项目题):如何证明库存守恒?
- 考点:台账、快照与对账。
- 回答思路:定义守恒公式并覆盖重复、取消和超时。
- 详细答案:按商品仓维度核对期初、入库、出库、调整与期末,确保可用、预占、已占用的状态和等于台账推导值;故障注入重复命令、消息重放、事务回滚和进程强杀,最终由扫描和对账收敛。
- 进阶追问:发现不守恒能直接改库存吗?
- 进阶回答:不能直接覆盖快照,应先冻结影响范围,定位缺失或重复流水,通过可审计调整单修正并保留前后证据。
2.6 跨境物流轨迹、面单接口与上下游同步
项目话术:背景是面单和轨迹接入多个承运商,不同字段、状态码、时区和重试语义不一致。约束是外部接口有配额、超时可能成功、轨迹会乱序补传且部分节点缺失。设计把供应商协议隔离在适配器,内部统一运单、节点和原始载荷摘要;创建面单使用业务请求号与供应商单号双向映射,超时进入未知状态并主动查询,不立即重建。轨迹以承运商、运单号、节点标识或规范化指纹幂等,保存原始发生时间、接收时间和时区,状态机只允许合法推进,迟到明细保留但不倒退主状态。同步采用游标或水位、分页检查点、限流、熔断和退避;失败进入可重放队列。请求机制详见Spring(Java 应用框架) MVC(网页模型视图控制器)请求链,持久化详见MyBatis(持久层框架)映射与批处理。
sequenceDiagram
participant J as "上游订单"
participant L as "物流应用服务"
participant A as "承运商适配器"
participant C as "承运商"
participant D as "轨迹存储"
J->>L: "创建面单,携带业务请求号"
L->>D: "登记请求和 PROCESSING"
L->>A: "转换内部模型"
A->>C: "供应商协议请求"
alt "明确成功"
C-->>A: "供应商单号和面单"
A-->>L: "规范化结果"
L->>D: "保存映射并置成功"
else "超时未知"
L->>D: "置 UNKNOWN"
L->>C: "按业务号主动查询"
end| 边界 | 内部统一模型 | 外部差异 | 防错措施 |
|---|---|---|---|
| 面单创建 | 请求号、运单、标签版本 | 同步/异步、返回格式 | 未知状态后查单 |
| 轨迹节点 | 代码、发生时间、地点 | 时区、状态码、补传 | 原始值+规范化值 |
| 批量同步 | 游标、水位、检查点 | 分页、配额、窗口 | 重叠窗口+幂等 |
| 下游通知 | 内部事件号 | 订阅方能力差异 | 至少一次+消费幂等 |
数据演绎 6:同步任务水位为 12:00,以五分钟重叠窗口拉取 11:55—12:10 共 1,200 条。去重后新增 980,重复 210,无法映射 10 条进入隔离表。运单 T9 先收到 DELIVERED,后补传两小时前的 IN_TRANSIT;明细都保存,但主状态不从已签收倒退。面单创建超时后查单发现供应商已生成单号,因此绑定原请求,不再次购买。下一水位只在所有分页落库成功后推进到 12:10。
热门面试题
- 问题(基础题):外部接口超时为什么不能直接重试创建面单?
- 考点:未知结果和外部副作用。
- 回答思路:超时不等于失败,先查单再决定。
- 详细答案:请求可能已经到达供应商并创建面单,只是响应丢失。直接重试会重复购买和生成多个运单。应保存业务请求号与未知状态,优先用供应商幂等键或查询接口确认;只有明确未创建且重试策略允许时才重发。
- 进阶追问:供应商没有幂等接口怎么办?
- 进阶回答:内部仍以业务请求号串行化,超时先按订单特征查询或人工核对;无法确认时保持未知,不能用自动重建掩盖风险。
- 问题(原理题):轨迹乱序怎样既保留事实又不倒退主状态?
- 考点:事件时间、接收时间和状态机。
- 回答思路:明细不可变,投影按合法迁移计算。
- 详细答案:每个节点保存供应商发生时间、接收时间、原始代码和规范化代码;明细幂等写入后,由状态优先级和合法迁移更新主状态。迟到节点可补全历史,但终态除纠错流程外不自动倒退。
- 进阶追问:状态优先级能代替状态机吗?
- 进阶回答:不能完全代替,退件、改派和异常签收可能不是单调序列,需要按业务语义定义迁移和纠错审批。
- 问题(项目题):分页同步如何避免漏数?
- 考点:水位、重叠窗口和检查点。
- 回答思路:水位只在完整批次成功后推进。
- 详细答案:按稳定排序分页,保存批次、水位和页检查点;使用时间重叠窗口吸收延迟数据,依靠节点幂等去重。任一页失败不推进最终水位,恢复后从检查点或整个重叠窗口重放,并监控供应商总数与本地入库数差异。
- 进阶追问:页码分页期间数据插入有什么问题?
- 进阶回答:可能漂移导致重复或遗漏,优先使用游标、更新时间加唯一键的稳定排序;只能页码时扩大重叠并做对账。
2.7 Spring(Java 应用框架) MVC(网页模型视图控制器)异步导出与 OOM(内存溢出)
事故展开:现象是大客户导出百万行订单时进程频繁 Full GC(完全垃圾回收)并最终 OOM(内存溢出),接口线程和异步线程都堆积。影响是同实例其他查询超时,导出文件有时半成品。证据包括请求参数、任务号、堆使用趋势、堆转储、对象直方图、线程池队列、数据库游标、响应是否仍持有、文件写入字节和任务状态。假设包括一次性加载全量列表、工作簿对象保留所有单元格、无界线程池、请求上下文被闭包捕获和失败后临时文件未清理。止血是关闭同步大导出、限制行数和并发,保留小查询。修复为任务化导出:请求只校验并创建任务,执行器按主键游标分页读取、流式写临时对象存储,周期更新检查点,成功后原子发布下载地址;失败可幂等续跑并清理。详见异步请求与文件传输和JVM(Java 虚拟机)排障。
flowchart LR
A["导出请求"] --> B["参数、权限和额度校验"]
B --> C["创建唯一任务"]
C --> D["有界队列调度"]
D --> E["按主键游标分页读取"]
E --> F["流式写临时文件"]
F --> G["更新检查点和统计"]
G --> H{"还有数据?"}
H -->|"是"| E
H -->|"否"| I["校验行数与摘要"]
I --> J["原子发布下载地址"]
J --> K["通知用户并设置过期清理"]| 风险 | 证据 | 错误方案 | 正确控制 |
|---|---|---|---|
| 全量驻留 | 堆中列表和单元格对象 | 增大堆掩盖增长 | 游标分页、流式写 |
| 并发失控 | 活跃线程和队列无界 | 每请求新线程 | 有界池、租户配额 |
| 半文件 | 字节数与任务状态不一致 | 直接写正式地址 | 临时对象+原子发布 |
| 上下文丢失 | 异步线程无租户/用户 | 复制整个请求对象 | 显式最小上下文快照 |
数据演绎 7:旧方案导出 1,200,000 行,查询列表占 1.4GB,工作簿对象增至 2.8GB,4GB 堆在 11 分钟后 OOM(内存溢出);队列中另有 17 个任务。止血限制单租户一个任务。新方案每页 2,000 行,内存稳定在 620—780MB,每 20,000 行保存检查点,写入临时对象 tmp/T88。第 360,000 行进程强杀后,新实例从已提交检查点恢复,最终行数与查询快照同为 1,200,000,摘要一致后才发布正式地址。
热门面试题
- 问题(基础题):异步导出为什么仍可能 OOM(内存溢出)?
- 考点:线程切换不改变内存算法。
- 回答思路:异步只释放请求线程,不自动流式处理。
- 详细答案:若异步任务仍一次加载全量数据、构造完整工作簿或进入无界队列,内存占用只是换了线程,甚至因并发更多而更差。必须控制单任务工作集、并发、队列、临时文件和上下文持有。
- 进阶追问:增大堆有没有价值?
- 进阶回答:可作为短期止血争取时间,但若对象量随行数线性增长,只会延后失败并拉长垃圾回收;根因是流式算法和容量控制。
- 问题(原理题):主键游标分页为何优于深分页?
- 考点:稳定顺序与扫描成本。
- 回答思路:以上一页最后主键作为下一页边界。
- 详细答案:深分页需要扫描并丢弃大量前序行,页数越深成本越高;主键游标利用有序索引从边界继续。还需固定查询快照或截止条件,使用主键作为同时间值的稳定次序,避免并发插入导致漂移。
- 进阶追问:恢复时会重复写一页怎么办?
- 进阶回答:检查点只在页写入和持久化成功后推进;分片文件带页序号与摘要,恢复可覆盖未确认分片或在合并时幂等去重。
- 问题(项目题):如何验收导出文件不是半成品?
- 考点:任务状态和文件发布原子性。
- 回答思路:临时写、完整校验、最后发布。
- 详细答案:任务记录期望快照、已写行数、分片摘要和检查点;文件先写临时地址,全部分页完成后校验行数、列数、摘要和可读取性,再更新任务成功并发布正式地址。失败任务不可产生可下载链接,过期或取消要清理临时对象。
- 进阶追问:数据库数据在导出期间变化怎么办?
- 进阶回答:明确语义是某个截止时间或快照版本,查询条件固定该边界;不能一边翻页一边把后来数据混入而仍声称是同一报表。
2.8 Spring Security(安全框架)越权、令牌与异步上下文事故
事故展开:现象是普通仓库账号可通过修改路径参数读取其他仓库订单,另有一批刷新后的令牌被旧实例拒绝。影响是数据越权与集中登录失败。证据包括脱敏主体标识、租户与仓库声明、请求路径、过滤器链、认证结果、方法授权决策、资源归属查询、密钥版本、签发与校验时间、时钟偏差和发布实例。假设是只校验登录未校验资源归属、方法安全被自调用绕过、租户上下文在异步线程丢失、密钥轮换不同步或时钟漂移。止血是关闭高风险批量接口、按租户网关限流、撤销受影响令牌并保留审计。修复采用入口认证、方法级权限和数据行归属三层校验;令牌按密钥标识支持新旧密钥短暂并存,敏感操作使用服务端会话版本或撤销表。异步任务只携带签名后的最小主体快照并在执行前重新授权。详见认证授权与过滤器链和请求入口。
flowchart LR
A["请求到达"] --> B["过滤器认证主体"]
B --> C{"令牌签名、时间和会话版本有效?"}
C -->|"否"| D["拒绝并记录脱敏原因"]
C -->|"是"| E["方法权限判断"]
E --> F{"角色/能力允许?"}
F -->|"否"| D
F -->|"是"| G["查询资源租户和仓库归属"]
G --> H{"主体可访问该资源?"}
H -->|"否"| D
H -->|"是"| I["执行业务并写审计"]| 防线 | 解决问题 | 典型失败 | 验证 |
|---|---|---|---|
| 入口认证 | 你是谁 | 伪造、过期、错误密钥 | 签名、时间、发行者和受众 |
| 方法授权 | 能做什么 | 角色扩大、自调用旁路 | 外部代理入口和拒绝测试 |
| 数据授权 | 能操作哪条数据 | 改路径参数越权 | 查询强制租户/仓条件 |
| 审计与撤销 | 事后追踪和紧急失效 | 日志泄密、令牌长期有效 | 脱敏标识、会话版本 |
数据演绎 8:租户 A 的用户持有仓库 W1 权限,请求路径改为 W2。旧接口只检查 ROLE_OPERATOR,返回 200 和 37 条其他仓数据。止血关闭批量查询并在网关阻断跨仓参数。修复后仓库归属进入查询条件,方法权限仍作为第一层;回归矩阵覆盖 A/W1 成功、A/W2 拒绝、租户 B/W2 成功、管理员受审计。密钥轮换期间新密钥 K2 签发,校验端同时保留 K1 到旧令牌最长寿命结束,拒绝率从 18% 恢复到基线。
热门面试题
- 问题(基础题):认证通过为什么仍可能越权?
- 考点:认证、功能授权和数据授权。
- 回答思路:主体有效不代表可访问任意资源。
- 详细答案:认证只确认令牌代表哪个主体;角色或能力决定能否调用某类操作,资源归属还要验证该订单、仓库或租户是否属于主体范围。只检查“已登录”或通用角色,攻击者修改资源标识仍可能读取他人数据。
- 进阶追问:数据权限只放网页控制器可以吗?
- 进阶回答:不够,任务、消息和内部调用可能绕过控制器;核心查询与命令必须强制携带租户和资源范围,入口层只做第一道防线。
- 问题(原理题):令牌密钥轮换怎样避免集中失败?
- 考点:密钥标识和兼容窗口。
- 回答思路:先发布校验能力,再切签发,最后退役旧密钥。
- 详细答案:令牌带密钥标识;所有校验实例先加载新旧公钥并验证就绪,再让签发端使用新密钥。旧密钥至少保留到既有令牌最长有效期或完成主动撤销,期间监控未知密钥标识和时钟偏差。私钥不能分发到校验端。
- 进阶追问:令牌被盗如何立即失效?
- 进阶回答:短有效期配合刷新机制;高风险场景使用会话版本、撤销列表或账号状态在线检查,在安全与可用性间取舍。
- 问题(项目题):异步任务如何携带用户权限?
- 考点:上下文边界和执行时重新授权。
- 回答思路:不复制线程本地变量,持久化最小主体快照。
- 详细答案:任务保存主体、租户、授权范围、发起时间和策略版本的最小快照并签名,执行时重新检查账号状态和资源归属;日志只记录脱敏标识。不能把整个请求或安全上下文对象塞进线程池,因为会丢失、泄漏或跨请求串用。
- 进阶追问:系统任务没有用户怎么办?
- 进阶回答:使用明确的服务主体和最小权限,区分“代表用户”与“系统维护”,所有操作仍需租户范围和审计来源。
2.9 MyBatis(持久层框架)慢 SQL(结构化查询语言)、批处理与缓存事故
事故展开:现象是轨迹批量入库后数据库负载升高,接口 p99 从 180ms 升至 3.2s,部分页面读到旧状态。影响是同步积压和履约延迟。证据包括映射语句标识、脱敏参数形态、执行计划、扫描行数、返回行数、锁等待、批次大小、提交频率、会话范围、缓存命中与清理时点。假设是动态条件漏索引、循环单条写、批量执行未刷新、一级缓存返回旧对象、二级缓存跨事务失真或插件改写漏租户条件。止血是降低批次和并发、关闭高风险缓存、暂停异常计划。根因修复以真实选择性建联合索引,批次按参数和事务容量控制,写后清理正确命名空间;核心库存和资金不依赖二级缓存判定。验证比较执行计划、逻辑读、锁时间和最终行数,并用多会话交错测试缓存。详见MyBatis(持久层框架)执行与缓存。
flowchart TD
A["慢 SQL(结构化查询语言)或脏读"] --> B["定位映射语句和参数形态"]
B --> C["获取实际执行计划"]
C --> D["核对扫描行、返回行和索引选择性"]
D --> E["核对锁等待和事务时长"]
E --> F["核对批次、刷新和提交"]
F --> G["核对一级/二级缓存作用域与清理"]
G --> H{"插件是否改写租户、分页或权限条件?"}
H -->|"是"| I["审计改写前后语句和参数"]
H -->|"否"| J["索引、语句或批次修复"]
I --> J| 问题 | 关键证据 | 常见根因 | 修复验证 |
|---|---|---|---|
| 慢查询 | 实际计划、扫描/返回比 | 索引不匹配、隐式转换 | 生产分布参数回放 |
| 批处理慢 | 每批行数、刷新、提交 | 伪批量、事务过大 | 吞吐与锁时间共同观察 |
| 旧数据 | 会话和命名空间清理 | 一级缓存复用、二级缓存滞后 | 双会话交错测试 |
| 租户泄漏 | 改写前后语句 | 插件漏条件或别名误判 | 越权用例和语法树审计 |
数据演绎 9:轨迹表 80,000,000 行,查询按 tenant_id + tracking_no,旧索引只有 tracking_no,某公共单号前缀扫描 240,000 行返回 12 行。增加以租户开头的联合索引后扫描 14 行。入库原来循环 5,000 次单条提交耗时 18s;改为每批 500、每 2,000 行提交后为 2.6s,锁等待未显著增加。二级缓存曾使另一会话在更新后 30s 仍读旧轨迹,核心查询关闭该缓存并以版本事件刷新页面投影。
热门面试题
- 问题(基础题):排查慢 SQL(结构化查询语言)为什么要看真实参数分布?
- 考点:选择性和执行计划。
- 回答思路:同一语句不同参数成本可能完全不同。
- 详细答案:测试参数往往选择性高,而生产热点租户、时间范围或状态值可能扫描大量数据;还可能因类型不一致触发隐式转换。应脱敏保留参数形态和分布,用实际计划、扫描行数与返回行数验证,不能只看语句文本。
- 进阶追问:加索引后就结束了吗?
- 进阶回答:还要观察写放大、索引大小、锁时间、计划稳定性和其他查询退化,灰度后比较业务延迟与数据库负载。
- 问题(原理题):一级缓存为什么可能让同一会话读到旧对象?
- 考点:会话范围和清理时点。
- 回答思路:同一会话按语句和参数复用结果。
- 详细答案:一级缓存属于会话,若外部系统或另一会话更新数据,本会话在未清理时重复相同查询可能返回缓存对象。正常写操作会触发相关清理,但跨线程、手工会话和错误生命周期会放大问题;事务边界应与会话边界一致。
- 进阶追问:关闭全部缓存最安全吗?
- 进阶回答:核心强一致查询可以关闭,但应先修复会话误用;缓存是性能选择,需按数据新鲜度、失效能力和读成本决定。
- 问题(项目题):批次越大吞吐一定越高吗?
- 考点:网络摊销、内存、日志和锁。
- 回答思路:存在最优区间而非无限增大。
- 详细答案:增大批次能摊薄往返和解析,但也增加参数内存、事务日志、锁持有、失败回滚成本和单次超时风险。应按行宽、数据库限制和冲突率压测,设置批次、提交间隔、失败隔离和可重放检查点。
- 进阶追问:批处理中一行失败怎么办?
- 进阶回答:记录批次和业务键,回滚该批后可二分或逐行隔离坏数据;已提交批次依靠幂等键不重复,不能从头盲重跑。
2.10 Runner(执行器)调度、租约、重复执行与优雅停机
项目话术:背景是 Runner(执行器)处理报表、同步和补偿任务,多实例部署且会滚动发布。约束是进程可能强杀、任务时长不确定、数据库与 MQ(消息队列)只能提供各自边界。设计以任务状态机和租约领取:使用条件更新把待执行任务变为运行中,记录执行实例、租约截止和尝试次数;执行期间周期续租并保存检查点,副作用使用任务号加步骤号幂等。停机先从就绪和调度器摘除,停止领取新任务,给在途任务有界时间完成;超时则保存检查点、停止续租,让新实例在租约过期后恢复。取舍是至少一次执行而非假装恰好一次,业务副作用必须可重入。机制详见启动、就绪与优雅停机和事务边界。
stateDiagram-v2
[*] --> PENDING
PENDING --> RUNNING: "条件领取 + 租约"
RUNNING --> RUNNING: "续租 + 检查点"
RUNNING --> SUCCEEDED: "全部步骤完成"
RUNNING --> RETRY: "可恢复失败"
RETRY --> PENDING: "退避到期"
RUNNING --> DEAD: "超过重试或不可恢复"
RUNNING --> PENDING: "租约过期后接管"| 阶段 | 必须动作 | 失败风险 | 恢复依据 |
|---|---|---|---|
| 领取 | 条件更新状态和租约 | 多实例重复领取 | 受影响行数和版本 |
| 执行 | 步骤幂等、续租、检查点 | 强杀、长暂停 | 任务号+步骤号 |
| 停机 | 摘流、停领、有界等待 | 强杀前未完成 | 检查点和租约过期 |
| 接管 | 校验旧租约和状态 | 双执行窗口 | 栅栏版本与幂等副作用 |
数据演绎 10:任务 J100 租约 60s,每 20s 续租,每 10,000 行保存检查点。实例 A 执行到 35,000 行时收到停机信号,停止领取并在 25s 宽限内提交到 40,000 行后退出;若第 15s 被强杀,租约到期后实例 B 以更高栅栏版本接管,从已提交 30,000 行恢复。第 30,001—35,000 行可能重做,但步骤幂等键阻止重复通知和重复扣费。最终任务成功一次,执行尝试两次。
sequenceDiagram
participant R as "注册与流量入口"
participant S as "调度器"
participant W as "工作线程"
participant D as "任务存储"
R->>S: "停机通知"
S->>R: "置为不就绪"
S->>S: "停止领取新任务"
S->>W: "通知在途任务收口"
W->>D: "保存检查点与最终状态"
alt "宽限内完成"
W-->>S: "完成"
else "宽限到期"
S->>W: "中断可中断步骤并停止续租"
D-->>S: "租约到期后允许新实例接管"
end热门面试题
- 问题(基础题):优雅停机能保证任务只执行一次吗?
- 考点:强杀边界和至少一次。
- 回答思路:停机降低重复窗口,不能消灭故障。
- 详细答案:不能。进程可能在副作用完成但状态提交前强杀,网络也可能使结果未知。优雅停机通过停领、排空和检查点缩小窗口;最终仍要靠租约接管、幂等步骤、状态查询和补偿承受重复执行。
- 进阶追问:为什么不无限等待在途任务?
- 进阶回答:会阻塞发布和故障恢复,任务本身也可能卡死;必须有业务可接受的截止时间,超时转为可恢复状态。
- 问题(原理题):租约和普通锁有什么区别?
- 考点:时间边界、接管和栅栏。
- 回答思路:租约允许持有者失联后恢复。
- 详细答案:普通互斥只表达当前持有关系,持有者崩溃需要外部释放;租约带截止时间并需续租,过期后可接管。由于旧持有者可能暂停后恢复,关键副作用还需栅栏版本拒绝旧执行者,不能只比较本地时间。
- 进阶追问:时钟偏差怎么处理?
- 进阶回答:租约判断尽量使用权威存储时间,续租留安全余量;跨系统副作用仍靠幂等和状态确认,不把时钟当唯一正确性依据。
- 问题(项目题):Runner(执行器)停机如何验证?
- 考点:摘流、在途、强杀和恢复。
- 回答思路:同时验证正常停机与宽限期强杀。
- 详细答案:持续投放任务时触发停机,确认实例先不就绪且不再领取,在途任务完成或保存检查点;再在不同步骤强杀,等待租约后由新实例接管。核对任务终态、步骤副作用、重复率、连接线程释放和发布时长。
- 进阶追问:检查点保存得越频繁越好吗?
- 进阶回答:不是,频繁保存增加数据库写和锁竞争;应按重做成本、任务时长和存储容量选择间隔。
2.11 IoT(物联网)报警风暴、背压与隔离
项目话术:背景是设备断网或阈值配置错误时,成千上万设备同时上报告警,通知、规则和工单链路被放大。约束是关键安全告警不能丢,重复心跳类告警不应淹没系统;单租户异常不能拖垮其他租户。设计在入口只做认证、格式校验和持久化接收,按租户与设备分区;以设备、规则、时间窗形成去重键,采用抑制、聚合、迟滞和恢复事件降低抖动。消费者有界并发,队列水位触发背压和分级降级:先暂停报表和低级通知,关键告警保留独立容量。规则版本写入事件,重放时按原版本解释;通知使用幂等键,工单状态机防重复创建。事故证据覆盖入口速率、分区倾斜、队列年龄、拒绝数、规则版本、通知成功和数据库池。请求边界详见Spring(Java 应用框架) MVC(网页模型视图控制器),持久化边界详见MyBatis(持久层框架)。
flowchart LR
A["设备事件"] --> B["认证与格式校验"]
B --> C["按租户/设备分区"]
C --> D["原始事件持久化"]
D --> E["规则判断和版本记录"]
E --> F["时间窗去重、抑制和聚合"]
F --> G{"队列水位"}
G -->|"正常"| H["通知与工单"]
G -->|"高"| I["背压并暂停低优先级"]
I --> H
H --> J["幂等副作用和恢复事件"]| 层次 | 守护指标 | 风暴策略 | 不能牺牲的事实 |
|---|---|---|---|
| 入口 | 每租户速率、拒绝率 | 配额与背压 | 原始关键事件可追溯 |
| 规则 | 命中率、版本分布 | 去重、迟滞、聚合 | 解释使用的规则版本 |
| 队列 | 深度、最老年龄、倾斜 | 分区扩容和优先级 | 关键告警独立容量 |
| 副作用 | 通知、工单成功率 | 幂等、重试、死信 | 同一告警不重复建单 |
数据演绎 11:正常入口 2,000/s,错误规则发布后升至 45,000/s,租户 T7 占 82%,最老消息年龄 160s,数据库连接使用 95%。止血回滚规则并限制 T7 为 8,000/s,暂停低级短信。去重窗口将 15 分钟内同设备同规则的 1,800,000 条事件聚合为 62,000 个告警,关键安全类单独通道无丢失;恢复事件逐一关闭工单。修复加入规则灰度、命中倍数守门和租户隔离,演练 60,000/s 时关键队列年龄低于 5s。
热门面试题
- 问题(基础题):报警去重为什么不能简单丢弃重复事件?
- 考点:原始事实与告警投影。
- 回答思路:保留可追溯事实,聚合副作用。
- 详细答案:重复上报次数、持续时间和恢复时点本身可能有业务价值。入口可保存原始事件或可审计计数,告警层用时间窗聚合通知和工单;关键事件不能因缓存失效而完全不可追溯。
- 进阶追问:原始事件量太大怎么办?
- 进阶回答:按级别设置存储周期、压缩和归档,保留摘要、计数和关键样本;丢弃策略必须可配置、有指标并符合审计要求。
- 问题(原理题):背压和限流有什么区别?
- 考点:容量反馈与入口拒绝。
- 回答思路:限流是控制输入,背压是下游容量反馈上游。
- 详细答案:限流按固定或自适应配额接受请求;背压根据队列水位、处理延迟和资源使用降低拉取、暂停低优先级或要求上游减速。风暴治理需要两者配合,并按租户和级别隔离,避免全局限流误伤关键告警。
- 进阶追问:队列深度低就代表安全吗?
- 进阶回答:不一定,消费者可能快速取出后阻塞数据库;还要看最老消息年龄、在途数、处理延迟、连接池和副作用成功率。
- 问题(项目题):规则发布如何防止再次制造风暴?
- 考点:灰度、倍数守门和自动回滚。
- 回答思路:离线回放加小流量发布。
- 详细答案:新规则先用历史数据回放,比较命中率和租户分布;灰度少量设备并设置相对基线的命中倍数、队列年龄和通知率阈值,超过即自动停止或回滚。事件记录规则版本,便于解释和重放。
- 进阶追问:回滚后已生成告警怎么办?
- 进阶回答:按规则版本和时间窗识别受影响告警,停止后续副作用;已建工单通过可审计批量操作关闭或标记误报,不能直接删记录。
2.12 统一事故指挥、证据矩阵与复盘守门
一次成熟事故处理要把“谁在什么时候基于什么证据做了什么”记录清楚。现象负责描述用户可见结果,影响负责量化订单、资金、库存、时延和租户范围;假设必须可由某项证据证伪。止血和根因修复分开:前者追求快速、可回滚和减少损失,后者改变机制并通过故障注入。证据分为发布事实、运行事实、调用事实、资源事实和业务事实;任何单层证据都不足以结束事故。复盘不追责个人,而是寻找为什么错误能进入生产、为什么监控未早发现、为什么恢复依赖人工记忆,并转化为配置校验、发布门禁、容量守门、演练和所有者。综合机制入口见路线与版本边界。
flowchart TD
A["事故指挥"] --> B["影响与优先级负责人"]
A --> C["止血执行负责人"]
A --> D["证据与时间线记录"]
A --> E["业务数据核对负责人"]
B --> F["订单、资金、库存、租户影响"]
C --> G["回滚、限流、降级、隔离"]
D --> H["制品、配置、日志、指标、链路"]
E --> I["最终状态、对账和人工闭环"]
F --> J["统一事实表"]
G --> J
H --> J
I --> Jflowchart LR
A["复盘根因"] --> B{"哪道防线缺失?"}
B --> C["设计:边界和不变量"]
B --> D["实现:幂等、事务和资源"]
B --> E["发布:配置、灰度和回滚"]
B --> F["观测:指标、日志和业务对账"]
C --> G["自动化守门"]
D --> G
E --> G
F --> G
G --> H["演练并验证防再发"]| 证据域 | 关键字段 | 用途 | 保留原则 |
|---|---|---|---|
| 发布事实 | 制品摘要、配置版本、实例批次 | 关联变更 | 不记录明文密钥 |
| 调用事实 | 请求号、链路号、主体、代理路径 | 还原执行链 | 标识脱敏且可关联 |
| 资源事实 | 线程、连接、队列、堆、锁 | 解释容量与等待 | 与同时间业务量对齐 |
| 业务事实 | 状态历史、流水、事件、对账 | 判断最终正确性 | 不可变、可审计 |
数据演绎 12:14:03 告警显示支付回调错误率 22%;14:06 确认仅 v58 新实例受影响,覆盖 3 个商户、1,240 次回调。14:08 回滚并停止自动履约,14:12 错误率恢复。证据表发现配置中心密钥版本已更新,但新制品缓存旧公钥;86 笔已成功验签并入账,273 笔渠道成功但本地未知,其余明确失败。主动查单和对账在 15:10 收敛未知项,无重复入账。防再发加入密钥标识兼容窗口、发布前双版本验证、未知支付告警和季度演练,所有行动项均有所有者和截止日。
热门面试题
- 问题(基础题):止血和根因修复为什么必须分开?
- 考点:事故阶段目标。
- 回答思路:先减少损失,再改变机制。
- 详细答案:事故中时间和信息都不足,止血要选择已知、可回滚、影响小的动作,如回滚、限流、隔离和关闭不可逆下游;根因修复需要完整证据、设计评审和回归。把临时开关当永久修复会积累风险,在高压下直接改机制也可能扩大事故。
- 进阶追问:重启算止血吗?
- 进阶回答:只有能解释为何有效、不会销毁现场且有业务恢复方案时才算;盲重启可能清除证据、重复任务并制造流量抖动。
- 问题(原理题):什么叫可证伪假设?
- 考点:证据驱动排障。
- 回答思路:每个猜测都要对应观察和否定条件。
- 详细答案:例如“事务因自调用失效”应预测实际对象是代理但调用未经过拦截器,外部调用能回滚而同类内部调用不能;若调用链显示已经过事务拦截且回滚标记存在,该假设就被否定,应继续查异常处理或数据库边界。
- 进阶追问:多个假设如何排序?
- 进阶回答:按影响解释力、近期变更、验证成本和操作风险排序,先用只读证据排除大类,不在生产随意改配置试错。
- 问题(项目题):面试中怎样讲事故才不像背模板?
- 考点:具体证据、取舍和结果。
- 回答思路:给出时间、量级、错误方案、决策依据和反事实。
- 详细答案:明确业务影响和自己职责,按时间线说为什么先做某个止血,展示两三个被证据排除的假设,再解释根因机制、修复取舍、故障注入和量化结果。主动说明仍存在的边界,比泛泛说“优化性能、加强监控”更可信。
- 进阶追问:没有真实重大事故怎么办?
- 进阶回答:可讲预发演练或小范围故障,但必须诚实标注环境与影响,不把模拟数据包装成生产战绩。
2.13 章节收口与题库使用说明
上面的十二个知识小节到此结束。下面的综合题库跨分册复述,不再计入章节级六字段题;每道题只保留一个权威口述答案,并链接到对应机制分册。
3. 跨章节综合口述题库
以下题目用于三至五分钟完整复述。每道题按结论、约束、机制链、失败边界、项目证据和验证闭环组织;追问给出直接答案,避免只留下关键词。
问题(综合题):Bean(对象实例)的实例化和初始化有什么区别,完整创建链路如何影响线上排障?
考点:容器生命周期、失败阶段、代理和资源清理。
口述答案:结论是实例化只解决“原始对象怎样出现”,初始化解决“对象怎样变成满足框架契约、可被代理并可对外工作的成品”,二者之间还有依赖填充和感知回调,不能混为一个
new。容器先从 BeanDefinition(Bean 定义)选择构造器、工厂方法或 FactoryBean(工厂对象)产品并创建原始实例;随后按候选规则填充依赖,执行 Aware(感知接口)回调,再依次经过初始化前对象后置处理器、初始化回调、初始化后对象后置处理器,事务、安全或审计代理通常在后处理阶段形成。约束是构造器和初始化都不应进行无界远程调用或启动失控线程,因为上下文尚未完成刷新;初始化失败会取消关键单例创建,并要求清理本轮已建立的连接和线程。失败边界还包括原型对象销毁不由容器自动兜底、懒加载把错误推迟到首请求、初始化中泄露this导致原对象绕过代理。支付项目中,渠道客户端构造阶段只接收不可变配置,初始化阶段校验密钥格式和路由唯一性,远程能力放到有界预热;核心账务客户端失败时实例不就绪,非核心渠道可按能力降级。排障时我会先由最内层异常定位定义、实例化、填充、初始化还是代理阶段,再比较容器最终引用、资源差值和就绪事件。验证覆盖缺候选、错误密钥、远端超时、代理命中及关闭,证明失败实例不接流量且资源归零。进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:构造器执行完对象就能被业务使用吗?直接回答:不能,依赖填充、初始化回调和代理可能尚未完成,此时暴露会得到半成品。
- 追问 2:初始化回调有哪几类?直接回答:常见有注解回调、初始化接口和定义中的初始化方法,具体顺序与版本应以机制分册和项目验证为准。
- 追问 3:为什么要区分失败阶段?直接回答:不同阶段证据和修复完全不同,候选冲突不能用延长超时解决,初始化阻塞也不能靠调整扫描范围解决。
- 关联专题:Bean(对象实例)完整生命周期
问题(综合题):singleton(单例作用域)是否线程安全,怎样设计支付、库存类避免共享状态事故?
考点:作用域、共享可变状态、线程上下文和并发边界。
口述答案:结论是 singleton(单例作用域)只保证一个容器和名称下通常复用同一实例,不提供任何线程安全承诺。网页请求、消息消费、调度任务和异步回调会同时调用这个对象;若字段保存当前订单、租户、临时列表、非线程安全格式化器或可变渠道选择结果,就会发生覆盖、串租户和可见性问题。正确设计是让应用服务保持无状态:请求数据放方法参数和局部变量,不可变配置在构造后安全发布;确需共享的统计或缓存使用有明确并发语义的组件,并把原子范围、失效和容量写清。ThreadLocal(线程本地变量)只能表达线程绑定上下文,线程池会复用线程,必须在统一入口设置并在
finally清理,而且异步切换不能假设自动传播。支付路由表在启动时由渠道集合构建成不可变映射,交易状态只进数据库;WMS(仓储管理系统)库存扣减依靠数据库条件更新和幂等流水,不能把数量缓存在单例字段。排障时会用并发请求携带不同租户和请求号,检查日志关联、字段快照、线程本地变量残留和最终数据;再通过线程安全检测与压力测试观察错误是否随并发出现。边界是“无状态”不等于没有依赖,连接池、缓存和客户端本身也是共享资源,需有界容量、线程安全说明和关闭协议。验证不仅看无异常,还要证明租户隔离、金额守恒、库存守恒以及滚动发布后上下文不串用。为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:给方法加同步锁能让单例设计正确吗?直接回答:只能串行化该实例上的部分访问,不能解决多实例、旁路调用和持久化不变量,还会压低吞吐。
- 追问 2:不可变对象为何更容易线程安全?直接回答:构造后状态不再变化,正确发布后各线程不必协调读写,推理范围更小。
- 追问 3:请求作用域能代替无状态设计吗?直接回答:它隔离一次请求,但消息、调度和异步场景仍需显式上下文,也会增加对象和代理管理成本。
- 关联专题:作用域与线程安全
问题(综合题):BeanFactoryPostProcessor(Bean 工厂后置处理器)和 BeanPostProcessor(Bean 后置处理器)有什么区别,误用会造成什么事故?
考点:定义阶段、实例阶段、过早创建和代理生成。
口述答案:结论是前者修改“对象配方”,后者参与“每个对象从原始实例到最终引用”的过程。BeanFactoryPostProcessor(Bean 工厂后置处理器)在普通单例创建前读取或修改 BeanDefinition(Bean 定义),适合占位符解析、定义校验和程序化注册;它不应为了业务逻辑主动获取普通对象,否则会在对象后置处理器链尚未完整时造成过早实例化。BeanPostProcessor(Bean 后置处理器)围绕实例初始化前后工作,依赖注入、生命周期注解、事务和其他代理能力都可能通过它或其扩展完成,因此顺序和适用范围直接影响最终对象身份。失败边界包括定义处理器偷偷访问数据库导致启动阻塞、对象处理器自身依赖普通业务对象造成链不完整、多个代理处理器重复包装、返回不同实例却未保持早期引用一致。项目中,支付渠道配置的重复标识和必填属性应在定义或启动校验阶段失败,而交易规则不应由定义处理器调用远端加载;审计代理只包围带明确标记的应用服务,不对所有对象盲目拦截。排障时先导出处理器名称与顺序,确认目标对象创建时间是否早于完整链注册,再比较原始类型、最终代理类型和通知器;若只有部分方法缺事务,要同时检查对象是否从静态注册、监听器或初始化回调中逃逸。验证用最小上下文断言定义修改结果、代理链、失败清理和多处理器顺序,不能只凭启动成功判断扩展点正确。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么定义处理器不能随意调用
getBean?直接回答:这可能提前创建普通对象,使其漏过尚未注册完整的对象后置处理器和代理链。 - 追问 2:对象后置处理器能替代领域服务吗?直接回答:不能,它适合通用生命周期和横切能力,复杂业务状态机放进去会隐藏调用和事务边界。
- 追问 3:怎样处理多个代理能力的顺序?直接回答:明确安全、事务、重试、审计等先后语义,记录通知器链并以失败场景验证,不能只依赖偶然注册顺序。
- 关联专题:容器扩展点与对象后置处理器
- 追问 1:为什么定义处理器不能随意调用
问题(综合题):Spring(Java 应用框架) Transaction(事务)的异常回滚规则是什么,为什么线上仍会出现“报错但已提交”?
考点:代理、异常传播、回滚标记、提交时点和数据库最终状态。
口述答案:结论是回滚不是由“日志里出现异常”触发,而是事务拦截器在代理调用边界观察到异常,并按配置决定提交或回滚;最终还要看物理事务是否成功提交。常见默认规则会对未检查异常回滚,对检查异常需显式配置,但项目不能死记默认值,应按实际版本和注解核对。出现“报错但已提交”通常有五类原因:调用根本没经过代理,例如同类自调用或原对象逃逸;方法使用了错误事务管理器或数据源;异常在事务方法内部被捕获并正常返回;异常类型不命中回滚规则;异步线程在原事务之外失败。还有一种相反情况是内层参与事务后标记仅回滚,外层捕获异常继续提交,最终在提交时得到意外回滚。排查必须记录实际代理、事务标识、传播行为、连接绑定、异常离开代理的形态、回滚标记和提交日志,再用独立连接查询最终行状态。支付回调中,验签和参数错误应在事务前拒绝;状态更新、资金流水和 Outbox(发件箱)同一事务,任何不变量失败都抛出明确业务异常并命中回滚。修复不能只“再加注解”,而要调整调用入口、异常契约或事务边界。回归覆盖正常、检查异常、未检查异常、提交阶段失败、同类自调用和异步切换,并核对支付单、流水和事件三者同成同败。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:捕获异常后手工标记回滚可以吗?直接回答:技术上可以,但会增加隐藏控制流;优先让异常契约清晰地穿过事务边界,确需转换时保留原因并显式测试。
- 追问 2:日志打印“回滚”就证明数据没提交吗?直接回答:不能,可能是另一事务或提交前日志,必须用事务外独立查询和流水对账确认。
- 追问 3:检查异常为什么常被误用?直接回答:开发者以为所有异常默认回滚,实际规则可能不同;应按业务原子性显式配置并测试,而非按异常继承体系猜。
- 关联专题:事务代理、回滚规则与最终状态
问题(综合题):AOP(面向切面编程)适合解决什么问题,为什么不能承载复杂领域流程?
考点:横切关注点、代理边界、顺序和可观测性。
口述答案:结论是 AOP(面向切面编程)适合把多个明确调用点都需要、且不改变核心领域语义的横切能力集中起来,例如事务入口、授权、审计、指标和受控重试;它不适合隐藏支付状态迁移、库存补偿或履约编排。代理式实现只拦截通过代理引用进入的可拦截方法,同类自调用、私有路径、构造阶段、原对象引用和异步线程都可能绕过;多个通知还存在顺序问题,例如重试包在事务外会每次建立新事务,包在事务内可能在同一已失败事务里重复。若把复杂业务放切面,调用者看不到副作用和失败契约,测试与事故还原都会困难。项目中我会让审计切面记录脱敏主体、请求号、方法结果类别和耗时,但支付金额校验、状态机和对账由显式应用服务完成;安全切面负责能力判断,资源归属仍进入查询条件。排障先确认目标对象代理类型、通知器链和真实调用入口,再比较成功与失败路径是否都经过切面,检查通知内部是否发起远程调用、吞异常或记录敏感数据。设计评审要求切面幂等、快速、有界并可关闭,明确顺序和失败策略。验证包含代理命中、自调用旁路、通知异常、多个通知顺序、异步切换和性能开销,业务最终状态仍由领域数据证明,不能用“切面日志存在”代替正确性。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:日志切面失败应不应该阻断业务?直接回答:普通观测通常降级并告警,强审计按合规要求可能阻断;策略必须显式且有容量隔离。
- 追问 2:怎样解决同类自调用?直接回答:优先把事务或授权边界提升到另一个协作对象的公开入口,不建议到处从容器取自身代理。
- 追问 3:AOP(面向切面编程)和过滤器有什么边界?直接回答:过滤器位于网页请求入口,代理围绕容器方法;消息和调度没有网页过滤器,核心能力应放正确边界而非重复实现。
- 关联专题:代理链、通知顺序与适用边界
问题(综合题):三级缓存解决循环依赖需要哪些前提,为什么仍可能出现代理身份不一致?
考点:单例属性循环、早期引用、代理时点与不可解边界。
口述答案:结论是所谓三级缓存不是通用循环依赖算法,而是单例创建流程中管理完整对象、早期引用和早期引用工厂的协作机制。它要求至少先产生某一方原始实例,所以构造器双方互相依赖时无对象可暴露;原型对象没有稳定的单例缓存生命周期,也不能依赖该机制。属性循环中,容器实例化 A 后登记早期引用工厂,填充 A 时创建 B,B 再请求 A 才触发工厂返回早期引用;若 A 将来需要代理,工厂要尽量返回与最终发布一致的早期代理。身份不一致通常来自自定义后置处理器重复包装、初始化中把原始
this注册到外部、处理器过早创建对象,或早期代理与最终代理使用不同通知链。即使容器允许循环,B 看到的 A 也可能尚未完成全部初始化,所以调用其业务方法存在半成品风险。WMS(仓储管理系统)项目中库存与履约双向依赖应抽取分配协调者;若旧系统短期兼容,必须比较依赖方引用与容器最终引用的代理类别和匿名身份,执行事务、安全、审计三类命中测试,并设置移除期限。排障保留最深创建链,确认注入方式、作用域、版本开关和自定义处理器,再查静态集合、监听器与线程是否泄露原对象。最终验收是依赖方向单向、代理身份唯一、异常回滚和关闭资源正确,而不是仅把应用启动起来。进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:二级缓存为什么不能直接存原对象?直接回答:目标对象若需要代理,直接暴露原对象会让依赖方绕过横切能力;延迟工厂允许在真正需要时生成早期代理。
- 追问 2:属性循环一定能解决吗?直接回答:不一定,还受作用域、版本策略、异步创建和自定义代理处理器影响,不能写成绝对能力。
- 追问 3:打开循环引用配置是不是修复?直接回答:通常只是兼容止血,必须补身份验证、监控和明确重构计划。
- 关联专题:三级缓存、早期代理和不可解边界
问题(综合题):自动配置如何提供默认实现,又如何被业务安全覆盖?
考点:候选导入、条件评估、后退规则、配置绑定和版本边界。
口述答案:结论是自动配置是“在明确条件成立且业务未提供替代对象时注册默认能力”,不是启动后神秘修改对象。启动阶段先根据版本对应的注册元数据导入候选配置,再评估类路径、配置属性、已有对象、网页应用类型等条件;条件成立后注册 BeanDefinition(Bean 定义),常见后退条件让用户定义对象优先。安全覆盖要求类型、限定标识和注入点语义一致,多个候选要明确主候选或集合路由,不能依赖名称碰巧覆盖。失败边界包括把另一主版本的注册方式和配置键套入项目、业务对象创建得太晚导致后退条件已命中、配置绑定成功但账号语义错误、同名覆盖掩盖两个来源,以及测试环境类路径与生产不同。支付项目中默认渠道路由器只在不存在自定义路由器时注册;业务提供多渠道路由时,以渠道标识建立不可变映射,重复标识启动失败。排障从依赖树锁定 Spring Boot(快速开发框架)小版本,查看条件评估报告、候选配置来源、已注册对象名称、绑定结果和最终注入引用,不以“加排除注解”作为第一反应。验证用最小上下文覆盖依赖存在/缺失、属性启用/禁用、用户对象存在、多个候选和错误配置,确认默认实现恰好在预期条件下出现,升级时重新核对官方稳定文档和运行结果。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:业务对象为什么可能没有让默认实现后退?直接回答:其定义注册时机、类型或条件不匹配,条件评估时看不到符合要求的候选;应查报告和定义来源。
- 追问 2:直接排除整个自动配置有什么风险?直接回答:可能连同必要基础设施一起移除,升级时还会失去默认修复;优先在最小范围提供替代对象或调整条件。
- 追问 3:怎样避免版本知识混用?直接回答:先读项目依赖树锁定小版本,再核对对应官方文档、配置键和条件报告,用运行证据落地。
- 关联专题:启动、自动配置和条件评估
问题(综合题):为什么不建议在数据库事务中调用支付、物流等远程系统,正确设计是什么?
考点:长事务、未知结果、状态机、可靠事件和补偿。
口述答案:结论是本地数据库事务只能原子控制绑定的本地资源,无法把第三方支付或承运商接口纳入同一提交;把远程调用塞进事务既没有获得全局原子性,反而延长连接和锁持有,并制造超时未知结果。典型失败是请求已被渠道执行但响应丢失,本地因超时回滚;若随后盲目重试,可能重复扣款或购买面单。正确做法先定义状态机和业务唯一键:在短本地事务中保存操作意图、请求号和待发事件,提交后调用远端;明确成功、明确失败和未知结果分别推进状态。未知状态优先主动查询,长期未决进入对账与人工闭环;远端若支持幂等键必须传稳定业务号。下游通知通过 Outbox(发件箱)和 MQ(消息队列)至少一次投递,消费者以事件号幂等。支付项目中支付单从创建、处理中、未知到成功或失败,资金流水只在确认成功时生成且唯一;物流面单超时后按业务号查单,不再次购买。排障要对齐本地事务时间、连接占用、远程请求号、渠道流水、状态历史和消息轨迹,不能看到本地回滚就假定渠道失败。验证注入远端超时、响应丢失、重复回调、发布确认丢失和消费者重启,证明金额、订单和履约最终一致且连接池不被远程延迟拖垮。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:先调用远端再开本地事务可以吗?直接回答:仍有远端成功而本地提交失败窗口,必须用可查询状态、幂等和补偿闭环,而非调整顺序假装原子。
- 追问 2:什么情况下可以同步等待远端?直接回答:用户体验需要时可以同步获得尝试结果,但不能长时间持有数据库事务,并要保留未知状态处理。
- 追问 3:补偿等于回滚吗?直接回答:不等于,补偿是新的业务动作,可能失败、产生费用或需要审批,必须可审计和幂等。
- 关联专题:本地事务、传播与跨系统一致性
问题(综合题):Spring(Java 应用框架)进程内事件和 MQ(消息队列)有什么区别,如何选择?
考点:进程边界、可靠性、事务阶段、重复消费和解耦成本。
口述答案:结论是进程内事件用于同一进程内的生命周期通知或弱耦合协作,MQ(消息队列)用于跨进程持久传递、削峰和独立恢复;二者都不是天然恰好一次。普通进程内事件通常与发布线程和进程同生共死,进程在监听前后崩溃都可能丢失,多个实例也各有自己的事件空间;事务阶段监听只能控制本进程回调相对本地事务的时点,提交后进程立即崩溃仍可能没有执行。MQ(消息队列)通过代理持久化、确认和重投提高可靠性,但会带来重复、乱序、积压、模式演进和运维成本,消费者必须幂等。选择依据是失败是否需要跨重启恢复、是否跨服务、是否需要削峰与独立扩缩容。支付状态变更不能只发布进程内事件触发履约,应在同一本地事务写 Outbox(发件箱),由发布器投递 MQ(消息队列);容器刷新完成后的本地缓存预热通知可用进程内事件,但需有界且失败不阻塞核心就绪。排障时确认事件发布阶段、线程、监听器异常策略和实例边界,MQ(消息队列)还要看事件号、代理确认、消费位点、重试与死信。验证包含提交前失败、提交后强杀、重复投递、监听器异常和消息积压,最终用订单、资金和履约状态闭环,而非只看“发布成功”日志。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:异步进程内事件是不是就等于 MQ(消息队列)?直接回答:不是,它通常仍没有跨进程持久化、确认、重放和消费位点,只是换到本地线程池执行。
- 追问 2:事务提交后监听就绝对可靠吗?直接回答:不可靠,提交后到监听完成之间仍可崩溃;可靠业务事件应先持久化。
- 追问 3:所有解耦都上 MQ(消息队列)好吗?直接回答:不好,同进程强一致协作会增加延迟和运维复杂度,应按恢复与容量需求选择。
- 关联专题:请求、事件与异步边界
问题(综合题):请完整设计一个支持网页请求、消息消费和 Runner(执行器)任务的优雅停机流程。
考点:就绪摘流、在途排空、资源关闭、租约恢复和强杀边界。
口述答案:结论是优雅停机不是执行销毁回调这么简单,而是一套“先拒绝新工作、再处理在途、最后释放资源”的有界协议,并且必须承认宽限期到达后可能强杀。第一步把实例置为不就绪并从服务发现摘除,等待网关和客户端连接传播;网页服务器停止接受新请求但允许短请求完成。消息消费者暂停拉取,只有业务状态和消费确认都安全时才确认;Runner(执行器)停止领取新任务,在途任务完成当前可提交单元、保存检查点并停止续租。随后按依赖反向顺序关闭业务线程池、消息客户端、网页服务器和数据库连接池,所有等待都有截止时间和指标。失败边界包括长连接仍把请求送来、线程忽略中断、远程结果未知、任务副作用完成但状态未提交,以及强杀发生在任何一步。支付服务对未知渠道请求交给查单和对账,WMS(仓储管理系统)库存命令靠幂等请求号重放,Runner(执行器)由租约过期和更高栅栏版本接管。验证要在持续流量、消息和长任务下滚动发布,并在不同阶段强杀;核对停机后新领取数为零、在途曲线下降、连接线程归零、重复动作被幂等吸收、积压可恢复。只证明进程退出码正常不够,最终订单、资金、库存和任务终态必须正确。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:摘流后为什么还要等待?直接回答:注册中心、网关、客户端缓存和长连接传播有延迟,立即关闭会切断仍在途的请求。
- 追问 2:宽限时间怎样设?直接回答:基于请求和任务分位耗时、检查点间隔和发布目标设上限,极长任务必须可分段恢复,不能无限延长。
- 追问 3:销毁顺序为什么重要?直接回答:先关连接池会让仍运行的业务任务失败,通常应先停入口和工作线程,再释放其依赖资源。
- 关联专题:运行生命周期、就绪与优雅停机
- 问题(综合题):一个支付回调从进入 Spring(Java 应用框架) MVC(网页模型视图控制器)到数据库提交,完整调用链和失败边界是什么?
考点:过滤器、参数绑定、校验、控制器、代理、事务、映射和响应提交。
口述答案:结论是支付回调不是“控制器收到参数后更新订单”一条线,而是入口安全、请求解析、业务代理、本地事务和响应协议共同组成的边界。请求先经过网页过滤器完成链路号、原始报文限长和必要安全控制,随后由 DispatcherServlet(前端控制器)选择处理方法,参数解析器读取报文;验签必须使用渠道规定的原始字节或规范化规则,不能先反序列化再随意重排。完成商户、时间窗、金额币种和事件号校验后,控制器只把规范化命令交给应用服务。应用服务必须从外部进入事务代理,在短事务内用渠道事件唯一键幂等,锁定支付单,校验合法状态迁移,写资金流水和 Outbox(发件箱);MyBatis(持久层框架)会话应与同一事务和连接绑定。提交成功后才按渠道协议快速应答,履约通过可靠消息异步推进。失败边界包括过滤器提前读取并耗尽请求体、异常解析器把验签失败误返回成功、同类自调用绕过事务、数据库提交结果未知、响应写出后再抛异常,以及渠道因超时重复通知。排障要串联请求号、渠道事件号、代理与事务标识、连接、映射语句、提交日志、响应状态和渠道重试。验证覆盖报文篡改、重复、乱序、提交失败、响应丢失和进程强杀,证明回调可重放、流水唯一且履约不重复。
进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么验签常需要原始请求体?直接回答:部分渠道签名覆盖原始字节顺序和编码,反序列化再序列化可能改变空格、字段顺序或数字格式。
- 追问 2:提交成功但响应失败怎么办?直接回答:渠道会重试,同一事件号命中持久化幂等后返回既有成功结果,不能再次入账。
- 追问 3:控制器可以直接写多张表吗?直接回答:不建议,应用服务应表达事务和状态机,控制器只处理协议,便于消息和任务入口复用同一业务规则。
- 关联专题:Spring(Java 应用框架) MVC(网页模型视图控制器)请求生命周期
- 问题(综合题):参数校验、业务校验和数据库约束应该怎样分工,为什么三层都需要?
考点:协议边界、领域不变量、并发竞态和错误响应。
口述答案:结论是三层解决不同时间和可信度的问题。参数校验位于入口,检查格式、长度、必填、枚举、时间和批量大小,目标是尽早拒绝不可解析或明显危险的请求,并返回稳定错误协议;它不能判断当前库存、支付状态或资源归属。业务校验在应用或领域服务中结合当前状态判断合法迁移,例如已退款支付不能再次成功、已取消订单不能确认出库;但“先查询再判断”在并发下存在检查后被修改的窗口。数据库条件更新、唯一约束、外键或检查约束负责最终竞态底线,例如事件号唯一、库存不为负和版本匹配。三层不应复制同一逻辑:入口给友好错误,领域表达语义,数据库守住并发不变量;数据库异常还要转换为稳定业务结果而非泄露表名。WMS(仓储管理系统)预占请求先校验数量为正和批次上限,再判断订单状态,最后用 available >= quantity 的条件更新与请求号唯一流水提交。支付回调先校验报文和签名,再判断状态机,最后依靠事件唯一键。排障要确认请求是否进入控制器、校验组是否生效、异常由哪个解析器处理、条件更新影响行数和最终约束错误。验证使用边界值、并发交错、重复请求、越权资源和数据库冲突,保证返回语义一致且数据不变量不破。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:有数据库约束还需要业务校验吗?直接回答:需要,约束通常无法表达完整状态机,也不能提供足够友好的错误和补偿决策。
- 追问 2:业务校验通过后数据库仍失败正常吗?直接回答:正常,并发可能改变状态;必须把影响行数或约束冲突当成预期竞争结果处理。
- 追问 3:校验异常统一返回
200好吗?直接回答:应遵循明确协议;对外渠道可按其要求应答,但内部监控必须区分业务拒绝和系统失败,不能被状态码掩盖。 - 关联专题:请求绑定、校验与异常解析
- 问题(综合题):异步执行为什么容易丢失事务、安全和租户上下文,怎样建立正确边界?
考点:线程切换、ThreadLocal(线程本地变量)、事务资源和最小上下文快照。
口述答案:结论是事务、请求属性和安全主体常与当前线程或当前调用栈绑定,任务提交到另一个线程后不会天然继承;即使复制 ThreadLocal(线程本地变量),线程池复用也可能造成残留和串租户。数据库事务把连接绑定在发起线程,异步线程另取连接就是新事务,原线程提交或回滚不会覆盖它。正确设计先划清事务边界:在本地事务中持久化任务意图和必要业务快照,提交后由异步执行器以任务号重新读取;需要主体信息时只保存用户、租户、授权范围和策略版本的最小签名快照,执行时重新验证账号与资源归属。链路号可以通过受控装饰器传播,但必须在 finally 清理;不能传递整个请求、响应、会话或可变安全对象。异步错误要写任务状态、重试次数和最终失败原因,不能只依赖线程日志。导出项目中请求线程完成权限和额度校验后创建任务,执行器按任务租户查询,生成文件后再次核对任务所有者;支付资金更新不放到普通异步线程,而用持久事件驱动。排障比较提交线程与执行线程、上下文快照、事务标识、连接和任务状态,检查拒绝策略是否让任务静默丢失。验证覆盖线程复用、队列满、账号撤销、事务回滚、进程重启和重复执行,确保无串租户、无未记录任务且副作用幂等。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:使用可继承线程本地变量能解决吗?直接回答:在线程池中不可靠,子线程创建时继承与池复用时机不匹配,还会传播过多可变状态。
- 追问 2:怎样处理任务提交成功但事务回滚?直接回答:不要在提交前把任务只放内存队列,应同事务持久化任务或事件,提交后再调度。
- 追问 3:链路号丢了会影响正确性吗?直接回答:通常不直接改变业务,但严重削弱排障;业务幂等必须依靠持久业务键而非链路号。
- 关联专题:异步请求、上下文与任务化设计
- 问题(综合题):怎样从调用链而不是经验值设计数据库连接池容量?
考点:并发、事务时长、嵌套传播、数据库承载和背压。
口述答案:结论是连接池上限不是越大越好,也不能只按网页线程数等比例配置;它取决于需要数据库的并发、每次持有时间、事务嵌套和数据库真正能承受的活跃会话。先画调用链:哪些入口会开事务,何时获取连接,是否在事务里等待远程、锁或队列,是否使用 REQUIRES_NEW(总是新事务)申请第二连接,多数据源是否各有独立池。用吞吐和连接平均持有时间估算基础并发,再用压测观察活跃、空闲、等待、超时、数据库中央处理器、输入输出和锁等待,保留恢复余量。若连接等待高而数据库负载不高,常见根因是长事务、连接泄漏或池过小;若数据库已饱和,扩大池只会增加排队和上下文切换。支付回调应把本地事务压到几十毫秒,把渠道调用移出;导出按页短查询并限制并发;Runner(执行器)不能让大量任务同时持有连接等待外部结果。还要设置获取超时、泄漏诊断和入口背压,让容量不足快速、可观测地失败,而非线程无限堆积。验证在正常峰值、远程变慢、数据库锁等待和单节点故障下观察连接曲线与业务错误,并确认停机后连接归零。最终以业务吞吐和数据库稳定为目标,不以“池使用率越低越好”作结论。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:连接使用率持续百分之百一定要扩池吗?直接回答:不一定,要看等待、数据库饱和和持有时间;可能应减少长事务或入口并发。
- 追问 2:连接获取超时怎样设置?直接回答:应小于整体请求预算并能触发降级,结合正常等待分位设置,避免长时间占住网页线程。
- 追问 3:多实例扩容会怎样影响数据库?直接回答:总连接上限随实例数相乘,发布和自动扩缩容必须有集群级预算,否则应用扩容可能压垮数据库。
- 关联专题:事务资源、传播和连接池
- 问题(综合题):REQUIRED(需要事务)、REQUIRES_NEW(总是新事务)和 NESTED(嵌套事务)如何选择?
考点:逻辑事务、物理资源、挂起、保存点和业务语义。
口述答案:结论是传播行为应从业务原子性与失败隔离出发,不应为了“保证写入”随意选择。REQUIRED(需要事务)让内外逻辑共享同一物理事务,任何参与者标记回滚都会影响整体,适合必须同成同败的订单、流水和事件记录。REQUIRES_NEW(总是新事务)挂起外层并建立独立事务,内层提交后外层回滚也不会撤销它,适合真正独立、失败策略明确的记录,但会额外占连接,且不能把不一致称为原子。NESTED(嵌套事务)通常基于同一物理事务的保存点,内层可回滚到保存点,最终仍依赖外层提交;是否支持取决于事务管理器和资源。支付主状态、资金流水和 Outbox(发件箱)应使用同一 REQUIRED(需要事务),不能把资金流水放新事务导致订单回滚但流水存在。普通审计若必须独立,可写新事务或更稳妥地异步持久化,但要评估连接和合规语义。批量导入可用保存点隔离单项的前提是允许部分成功且数据库支持,否则按批次独立事务更清晰。排障要记录传播决策、挂起恢复、连接数、回滚标记和最终行状态。验证覆盖内层失败被捕获、外层失败、连接池接近上限和提交阶段异常,确保传播名称与业务结果一致。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:新事务能保证日志一定写入吗?直接回答:不能,数据库和连接仍会失败;它只与外层提交隔离,还可能造成连接等待。
- 追问 2:嵌套事务等于新事务吗?直接回答:不等于,常见实现共享物理事务并使用保存点,外层最终回滚时内层结果也消失。
- 追问 3:怎样处理意外回滚?直接回答:查哪个参与者设置仅回滚、异常在哪里被捕获;不要让外层在已不可提交的事务中继续返回成功。
- 关联专题:事务传播、保存点与回滚标记
- 问题(综合题):如何把过滤器认证、方法授权和数据权限组合成不易旁路的安全体系?
考点:多层防御、代理边界、资源归属和内部入口。
口述答案:结论是三层分别回答“是谁、能做什么、能操作哪条数据”,必须组合而不能互相替代。过滤器链在网页入口验证令牌签名、时效、发行者和受众,构造受控主体;方法授权围绕应用服务能力,拒绝没有某项操作权限的主体;数据权限把租户、仓库、商户或资源归属强制进入查询与更新条件,防止合法角色修改资源标识越权。约束是方法授权通常依赖代理,原对象、自调用或非容器对象可能旁路;消息、Runner(执行器)和内部调用没有网页过滤器,所以必须携带明确服务主体或用户快照,并复用应用层与持久层约束。MyBatis(持久层框架)插件可辅助追加租户条件,但不能成为唯一防线,复杂子查询、别名和手写语句可能漏改写;关键仓储接口应显式要求租户范围。排障从脱敏主体、过滤器匹配链、认证结果、方法决策、实际映射语句和资源归属逐层验证,区分未认证与已认证但禁止。支付回调使用渠道主体和商户映射,普通用户令牌不能调用;WMS(仓储管理系统)批量导出在创建任务和下载时都重新检查所有者。验证矩阵覆盖不同租户、仓库、角色、异步和内部入口,还要测试代理旁路和令牌撤销,最终审计能关联谁在何时访问了哪项资源。
进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:管理员是否可以跳过数据权限?直接回答:应使用显式、可审计的管理能力和范围,不能靠全局布尔开关静默绕过。
- 追问 2:网关已鉴权,服务内还需要吗?直接回答:需要,内部调用、配置错误和绕过网关都存在;服务必须验证可信主体和自身资源边界。
- 追问 3:权限缓存怎样失效?直接回答:短时缓存配版本或变更事件,高风险操作可在线核验;撤权延迟必须成为明确安全指标。
- 关联专题:认证、授权、方法安全与数据边界
- 问题(综合题):令牌轮换导致大面积登录失败时,如何止血、定位和修复?
考点:密钥标识、发布顺序、时钟、撤销和审计。
口述答案:结论是先区分签名密钥不认识、令牌过期、发行者或受众不匹配、会话被撤销和实例时钟偏差,不能把所有失败归为“令牌失效”。影响确认按实例版本、密钥标识、租户、签发时间和错误码聚合,日志只记录摘要而不输出令牌。止血优先恢复旧公钥验证能力或回滚签发端,保留新旧密钥短暂兼容;若怀疑密钥泄露则不能简单恢复旧密钥,应撤销并强制重新认证。正确轮换顺序是先让所有校验端加载新公钥并通过就绪验证,再切换签发端使用新私钥,观察新密钥成功率,最后在旧令牌最大寿命或撤销完成后退役旧公钥。实例时间必须同步并设置合理时钟容差,但不能用无限容差掩盖过期。Spring Security(安全框架)排障要导出实际过滤器链、密钥集合版本、令牌头中的密钥标识、签发与校验时间以及会话版本。支付和仓储管理端还需检查高风险接口是否错误降级为匿名。修复后回放旧、新、过期、未来签发、错误受众和已撤销令牌,进行滚动发布与单实例隔离测试;业务验收是正常用户恢复、非法令牌仍拒绝、审计连续且没有跨租户放行。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么先发布校验端?直接回答:若先用新私钥签发,尚未加载新公钥的实例会集中拒绝新令牌。
- 追问 2:公钥可以长期保留吗?直接回答:可保留到兼容窗口结束,但无用密钥会扩大管理面;应有明确退役和审计流程。
- 追问 3:时钟容差越大越好吗?直接回答:不是,容差过大会延长过期或未来令牌可接受窗口,应修复时间同步并保持最小必要值。
- 关联专题:令牌校验、轮换与安全事件
- 问题(综合题):MyBatis-Spring(Spring 集成模块)怎样让 SqlSession(SQL 会话)参与 Spring(Java 应用框架) Transaction(事务),误用会怎样?
考点:会话代理、连接绑定、提交所有权和跨线程边界。
口述答案:结论是集成模块把 Mapper(映射器)调用取得的 SqlSession(SQL 会话)与当前 Spring(Java 应用框架) Transaction(事务)同步,使同一事务内的语句复用绑定资源,并由事务管理器统一提交、回滚和关闭;业务代码不应手工提交容器管理的会话。调用 Mapper(映射器)时,会话模板按当前会话工厂和执行器类型获取会话,若事务已激活且数据源匹配,就把会话及底层连接绑定到事务同步上下文;方法结束只释放引用,物理提交由事务完成。失败边界包括 Mapper(映射器)来自另一个会话工厂或数据源、事务管理器管理的不是该数据源、手工打开会话导致独立连接、异步线程不继承事务、批量执行器和普通执行器在同一事务混用,以及手工提交破坏统一回滚。WMS(仓储管理系统)库存更新、流水和 Outbox(发件箱)必须通过同一会话工厂和事务管理器;报表只读库则明确使用独立边界,不能假设主库事务覆盖。排障要记录事务标识、会话工厂、数据源、连接匿名标识、执行器类型和提交者,核对日志中是否出现非事务会话。验证让第二条写入失败,确认第一条和事件都回滚;再构造错误数据源与异步线程,证明测试能检测边界越界,而不是碰巧在单库环境通过。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么不应调用会话的手工提交?直接回答:提交所有权属于事务管理器,手工提交会让后续异常无法回滚已经持久化的部分。
- 追问 2:只读事务能阻止所有写吗?直接回答:通常是优化或提示,具体数据库和驱动行为不同,业务仍需权限与代码边界,不能当绝对安全约束。
- 追问 3:跨线程怎样处理数据库操作?直接回答:把异步步骤当新事务并用持久任务或事件衔接,不能把原线程会话传过去。
- 关联专题:MyBatis-Spring(Spring 集成模块)事务与会话绑定
- 问题(综合题):MyBatis(持久层框架)一级缓存和二级缓存有哪些边界,核心业务为何要谨慎使用?
考点:作用域、清理、提交、跨服务更新和陈旧数据。
口述答案:结论是一级缓存主要服务于同一 SqlSession(SQL 会话)内重复查询,二级缓存跨会话按映射命名空间共享;它们优化读成本,却不能替代业务一致性协议。一级缓存键通常包含语句、参数和分页等信息,写操作会触发相关清理,但若会话被错误长时间复用,另一会话或外部服务更新后,本会话仍可能看到旧对象。二级缓存只有在会话提交等条件后才对其他会话可见,更新会清理相应命名空间;跨命名空间关联、其他服务直接写库、缓存节点不一致或自定义序列化都可能造成陈旧。库存可用量、支付状态和权限决策需要基于当前权威数据与条件更新,不应以二级缓存结果决定扣减、入账或放权;缓存更适合变化慢、可容忍短暂旧值且有明确失效的查询投影。排障先确认实际会话生命周期、是否命中缓存、查询与更新属于哪个命名空间、事务是否提交、是否存在跨服务写入,再用两个独立会话交错重现。修复可能是缩短会话、关闭核心查询缓存、统一更新入口或建立带版本的外部投影,而不是到处手工清缓存。验证要覆盖同会话重复读、另一会话提交、回滚、批量更新和跨服务事件延迟,并用业务版本号证明读取语义。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:查询返回对象被修改会影响缓存吗?直接回答:一级缓存可能返回同一对象引用,业务修改会污染后续读取;不要把持久对象当任意可变共享对象。
- 追问 2:二级缓存为什么按命名空间清理仍可能不准?直接回答:联表结果依赖多个命名空间或外部写入,单命名空间更新未必覆盖全部依赖。
- 追问 3:关闭缓存后性能下降怎么办?直接回答:先优化索引和查询,再按业务新鲜度建立可观测、可失效的专用缓存,不以隐藏缓存换取未知一致性。
- 关联专题:会话缓存、提交与失效边界
- 问题(综合题):如何设计可恢复、可观测的 MyBatis(持久层框架)批处理?
考点:执行器、刷新、提交、坏数据隔离、幂等和检查点。
口述答案:结论是批处理的目标不是把所有数据塞进一次事务,而是在网络往返、内存、日志、锁持有和失败重做之间找到可控单元。先定义稳定业务键和幂等约束,再按行宽、索引成本与数据库限制选择批次;批量执行器可能将语句与参数暂存到刷新时发送,必须明确何时刷新、何时得到受影响行数以及异常对应哪个批次。每若干批次提交并保存检查点,已提交部分重跑时由唯一键或版本条件吸收,未提交批次可从检查点恢复。坏数据不应让百万行全部回滚,可在整批失败后按二分或逐行缩小并进入隔离表,记录业务键、错误类型和原始摘要。跨境轨迹入库按承运商、运单和节点指纹唯一,每批五百行、每四批提交;库存资金类批量更新还需稳定锁顺序和更小事务。排障要收集批次大小、刷新耗时、提交耗时、参数内存、锁等待、日志增长、成功行数和检查点,区分“伪批量循环发送”和真正驱动批量。验证覆盖中间坏数据、网络断开、提交结果未知、进程强杀和重复重放,最终行数、摘要与水位一致。容量测试同时观察吞吐和单批失败成本,不能只追求每秒行数。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:怎样确定批次大小?直接回答:用接近生产的行宽、索引和冲突率压测多个档位,同时看吞吐、内存、锁时间、日志和回滚成本。
- 追问 2:提交结果未知如何恢复?直接回答:按业务唯一键查询已提交范围,从最后确认检查点重放,不能根据客户端异常直接假定未提交。
- 追问 3:为什么需要隔离表?直接回答:让可恢复数据继续推进,同时保留坏数据证据和人工修复入口,避免静默跳过。
- 关联专题:执行器、批处理与插件边界
- 问题(综合题):某个自动配置对象在生产缺失,你会怎样定位而不是盲目加注解?
考点:版本、候选导入、条件报告、定义来源和最终注入。
口述答案:结论是先证明对象应该在当前版本和配置下出现,再沿“候选是否被导入、条件为何不匹配、定义是否注册、是否被覆盖或后退、注入是否选到它”逐层定位。第一步固定生产制品摘要和依赖树,锁定 Spring Boot(快速开发框架)与 Spring(Java 应用框架) Framework(核心框架)实际小版本,因为不同主版本的注册元数据和配置键可能不同。第二步读取条件评估报告,检查类路径、属性、已有对象、应用类型和资源条件;同时确认配置来源与优先级,避免环境变量覆盖。第三步导出相关 BeanDefinition(Bean 定义)的名称、类型和来源,查看业务自定义对象是否触发默认实现后退,或多个候选造成歧义。若定义存在但注入失败,再检查限定标识、主候选、泛型和对象创建异常。支付渠道客户端缺失时,还要区分“根本没注册”和“初始化失败后上下文取消”,最内层异常与报告要结合。止血可回滚制品或恢复已知配置,不应临时开启同名覆盖。修复后用最小上下文矩阵验证依赖有无、属性开关、用户替代对象、错误配置和不同环境,并把关键条件做成启动测试。最终验证的是目标对象类型、配置指纹和业务能力,不只是应用能启动。
进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:条件报告显示未匹配就一定是配置错误吗?直接回答:不一定,可能是设计上的正常后退、依赖缺失或已有用户对象,需要结合期望能力判断。
- 追问 2:为什么不直接定义一个同名对象?直接回答:可能掩盖版本和条件问题,产生覆盖顺序依赖;应明确类型和后退契约。
- 追问 3:生产不方便开详细调试怎么办?直接回答:通过受控管理端点、启动报告归档或最小复现获取必要条件,避免长期输出敏感配置。
- 关联专题:自动配置候选、条件与后退
- 问题(综合题):Liveness(存活)、Readiness(就绪)和业务能力健康应该如何划分?
考点:重启、摘流、降级和故障放大。
口述答案:结论是 Liveness(存活)回答“进程是否陷入无法自愈、需要重启”,Readiness(就绪)回答“当前实例能否接收主要流量”,业务能力健康回答“某项渠道、仓库或报表能力是否可用”;把三者混在一起会制造重启风暴或无谓损失容量。数据库短暂抖动、单个支付渠道失败通常不应让存活失败,否则所有实例同时重启并加重依赖压力;核心账务库长期不可用可让支付实例不就绪,避免接收无法完成的交易;非核心承运商失败应只关闭对应路由并暴露业务指标。检查本身必须快速、有界、可缓存,不能每次探针都发真实支付或扫描大表。启动阶段只有配置、核心对象、安全链和必要资源验证完成才置就绪;停机先撤就绪再排空。排障要对齐探针返回、容器生命周期事件、依赖指标、服务发现路由和业务错误,确认是依赖失败还是检查实现拖垮实例。WMS(仓储管理系统)核心库存库失败时不接扣减流量,低频报表库失败不影响出库;Runner(执行器)任务存储失败时停止领取但进程仍可存活等待恢复。验证注入短抖动、持续故障、慢检查和单渠道故障,观察是否出现错误重启、流量抖动或能力未降级。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:数据库不可用时存活应失败吗?直接回答:通常不应直接失败,重启无法修复外部数据库;更适合不就绪或业务降级,除非进程自身已不可恢复。
- 追问 2:就绪检查可以很重吗?直接回答:不可以,频繁重型检查会占用资源并成为故障源,应使用本地状态和有界探测。
- 追问 3:能力降级怎样让调用方知道?直接回答:路由层避免选择不可用能力,接口返回稳定业务状态,并暴露按渠道或仓库的健康指标和告警。
- 关联专题:运行生命周期、健康与就绪
- 问题(综合题):请按事故模板讲一次支付回调重复导致重复履约的排查与改造。
考点:幂等、事务消息、止血、对账和量化复盘。
口述答案:现象是同一渠道事件触发两个出库单,影响集中在一次发布后的四十分钟,共发现二十三个重复履约候选。第一步暂停自动履约但保留验签和回调落库,避免丢失渠道事实;按渠道事件号、支付单、消息标识和订单建立冻结清单。现场显示回调入口只用了十秒缓存锁,数据库没有事件唯一约束;第一请求提交支付成功后发布消息,确认丢失导致再次投递,第二次渠道回调又绕过过期锁,最终消费者按消息标识而非业务事件幂等。假设通过数据库流水、代理事务日志、MQ(消息队列)投递轨迹和履约唯一键逐项验证,确认不是渠道重复扣款而是本地下游重复。修复在回调表增加渠道、商户、事件号唯一键,支付单状态迁移、资金流水与 Outbox(发件箱)同事务;发布允许重复,履约以业务事件号和订单类型唯一,冲突返回既有结果。未知支付主动查单,历史数据通过渠道账单、支付流水和履约三方对账,重复出库进入人工拦截或逆向流程。回归注入四次重复回调、发布确认丢失、消费者崩溃和缓存失效,保证状态变更一次、流水一次、履约一次。复盘新增幂等键评审、重复率和未知状态告警,灰度后两周重复候选为零。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么保留回调落库而暂停履约?直接回答:前者保存外部事实并可幂等,后者是更难逆转的副作用,分层止血能减少损失。
- 追问 2:缓存锁还有价值吗?直接回答:可降低热点并发,但正确性由持久唯一约束和状态机保证,不能作为唯一幂等事实。
- 追问 3:重复出库怎样补偿?直接回答:先确认是否已拣货或发运,按履约状态执行取消、拦截或逆向单,所有动作可审计而非直接删数据。
- 关联专题:本地事务、消息一致性和未知状态
- 问题(综合题):请按事故模板讲一次 WMS(仓储管理系统)库存超卖问题。
考点:数据不变量、并发竞态、事务、锁和守恒对账。
口述答案:现象是促销期间某热点商品可用库存出现负数,订单承诺量比实物多十二件;影响限定在一个仓和一个商品,但后续波次均受阻。止血先关闭该商品下单和自动波次,冻结相关请求号与库存流水,不直接把数量改回。证据显示旧实现先查询可用量再在应用内判断,随后执行无条件更新;两个实例并发都读到十件并分别扣七件,应用内锁只覆盖单实例。消息重试又重复执行释放,说明幂等也不完整。根因不是数据库行锁“失效”,而是检查与更新不在一个原子条件中,且库存快照缺少不可变流水。修复使用带 available >= quantity 的条件更新,以影响行数判断成功;每个预占命令用请求号和业务类型唯一,预占、确认、释放形成状态机,重复命令返回既有结果。批量分配按仓、货主、商品稳定顺序更新,短事务内同时写库存流水和 Outbox(发件箱);跨服务取消通过幂等事件。历史修复以期初、入库、出库、调整和流水重建守恒,生成审计调整单。回归用多实例、重复请求、死锁重试、消息重放和进程强杀压测十万次,证明可用不为负,快照与流水守恒。指标新增条件冲突率、重复命中率、死锁和对账差异。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:悲观锁能解决吗?直接回答:可以串行关键行,但仍需幂等和状态机;热点吞吐与死锁顺序要评估,条件更新通常更直接。
- 追问 2:为什么不能直接修正快照?直接回答:会掩盖缺失或重复流水,失去审计;应通过可追踪调整单恢复守恒。
- 追问 3:条件冲突率高怎么办?直接回答:先确认是真实热点,再按仓分散、排队或分桶;任何优化仍由权威数据约束守底线。
- 关联专题:MyBatis(持久层框架)条件更新与事务绑定
- 问题(综合题):请按事故模板讲一次跨境物流轨迹同步积压与状态倒退问题。
考点:外部协议、分页水位、乱序、幂等、批处理和降级。
口述答案:现象是一个承运商轨迹延迟两小时,部分已签收运单又显示运输中;影响约八千票,客服和下游结算同时受影响。止血暂停该承运商主状态覆盖,但继续保存原始节点;限制同步并发,保留其他承运商容量。证据包括供应商分页响应总数、游标、水位、原始发生时间与接收时间、规范化状态、批次提交和数据库计划。调查发现任务在每页成功后就推进全局水位,第三页入库超时却仍跳到下一时间窗,造成漏数;补偿重拉时又按接收顺序直接覆盖主状态,迟到的运输中节点让已签收倒退。根因还包含轨迹表联合索引缺少租户前缀,批次变慢放大超时。修复将水位只在整个窗口全部分页提交后推进,每页保存检查点并使用五分钟重叠窗口;节点以承运商、运单和供应商节点号或规范化指纹幂等,明细保存双时间,主状态按合法状态机推进,迟到明细不自动倒退终态。索引改为租户、运单和发生时间,批次五百行、每四批提交,坏数据进入隔离表。恢复从旧水位重放并与供应商总数对账,已签收主状态保持,历史节点补全。验证注入页超时、重复页、乱序、时区错误和进程强杀,确认水位、行数、摘要与主状态一致。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么继续保存原始节点?直接回答:它是外部事实和后续纠错依据,暂停投影不应丢失可恢复数据。
- 追问 2:时间重叠窗口会不会产生重复?直接回答:会,因此必须以稳定节点键幂等;重复是可吸收成本,漏数通常更难恢复。
- 追问 3:已签收状态永不允许变更吗?直接回答:正常自动链路不倒退,确有供应商纠错应走带证据和审计的专门迁移。
- 关联专题:请求适配、异步同步与异常边界
- 问题(综合题):请按事故模板讲一次异步导出 OOM(内存溢出),并说明为什么“改成异步”没有解决。
考点:内存工作集、有界并发、流式写、检查点和文件发布。
口述答案:现象是百万行订单导出触发频繁 Full GC(完全垃圾回收),最终实例 OOM(内存溢出),同实例普通查询也超时;影响是十七个导出任务和约三成在线请求。止血先关闭大导出入口、限制单租户一个任务并保留小报表,不立即重启所有实例,以便保存堆转储和线程现场。证据显示控制器虽把工作提交到异步线程池,但任务仍一次查询全部行并构造完整工作簿;无界队列同时保留请求条件和用户对象,单任务峰值近三 GB(吉字节)。因此异步只是释放网页线程,没有改变随行数线性增长的内存算法,还扩大并发。修复将接口改为任务化:请求只做权限、参数和额度校验,持久化唯一任务;Runner(执行器)按主键游标每两千行查询,流式写临时对象,每两万行提交检查点和摘要。有界线程池按租户配额调度,队列满返回可重试状态;成功时校验快照行数、文件摘要和可读取性,再原子发布下载地址。失败和取消清理临时文件,强杀后从最后确认检查点恢复,步骤以任务号和分片号幂等。验证用相同百万行数据、多个租户并发、队列满、数据库变更和不同阶段强杀,证明堆稳定、普通请求不受拖累、文件不是半成品且恢复不重不漏。
进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么不能直接流式写 HTTP(超文本传输协议)响应?直接回答:小导出可以,大导出连接长、断线恢复困难且占用资源,任务化更适合检查点和下载重试。
- 追问 2:如何固定导出数据语义?直接回答:记录截止时间、快照版本或最大主键,后续分页都使用同一边界,避免混入新增数据。
- 追问 3:怎样证明没有内存泄漏?直接回答:多轮任务后堆在垃圾回收后回到稳定基线,任务、请求上下文和工作簿对象不持续增长,临时文件也按状态清理。
- 关联专题:异步请求、流式传输和任务边界
- 问题(综合题):请按事故模板讲一次多租户越权,并说明如何验证没有其他入口旁路。
考点:认证、方法授权、数据范围、异步与内部调用。
口述答案:现象是仓库操作员修改路径中的仓库标识后读取了其他仓订单,影响经审计确认涉及两个租户、四十七次请求。止血立即关闭批量查询,按租户限制相关路由,撤销受影响会话并保留访问日志;同时避免在日志中输出完整令牌和订单隐私。证据链显示令牌签名、时效和角色都合法,过滤器认证没有问题;控制器只检查了通用操作员角色,Mapper(映射器)查询按请求中的仓库标识执行,没有校验该仓是否属于主体,说明这是数据授权缺失而非未认证。进一步检查消息、Runner(执行器)和内部接口,发现它们也可直接调用仓储服务,因此只修控制器会留下旁路。修复采用三层:入口认证构造可信主体,应用服务的方法授权检查操作能力,仓储端口强制接收租户和仓库范围并把它们放进查询条件;管理越权使用独立、可审计的授权。异步任务保存最小主体快照,执行和下载时重新检查;MyBatis(持久层框架)插件仅作守门和告警,关键语句显式条件。回归矩阵覆盖同仓、跨仓、跨租户、管理员、消息和调度入口,并尝试同类自调用绕过代理。最终用数据库审计证明拒绝请求没有返回数据,合法请求计划仍命中索引,历史访问完成通知和处置。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么角色检查不足?直接回答:角色说明可执行某类操作,不说明可操作任意租户或资源,必须核对对象归属。
- 追问 2:插件自动加租户条件不是更统一吗?直接回答:可辅助,但复杂语句、别名和旁路可能漏改写;关键仓储接口应显式携带范围并有越权测试。
- 追问 3:如何发现历史越权?直接回答:用主体、租户、资源和时间窗关联审计与数据库访问,重放策略判断,必要时通知和轮换凭证。
- 关联专题:Spring Security(安全框架)方法安全与数据授权
- 问题(综合题):请按事故模板讲一次慢 SQL(结构化查询语言)导致连接池耗尽的排查。
考点:执行计划、参数分布、锁、连接持有和容量闭环。
口述答案:现象是订单查询 p99 从两百毫秒升到四秒,连接池等待超时,同时数据库中央处理器只中等偏高。止血先限制高成本筛选条件和导出并发,保留创建订单等核心路径;固定制品、配置和慢查询时间窗。证据把网页线程栈、连接池活跃与等待、映射语句标识、脱敏参数、实际执行计划和锁等待对齐,发现某大租户按状态和时间查询扫描二百四十万行只返回三百行;新发布的动态语句对时间列做了函数转换,使联合索引无法利用,慢查询长期占住连接,后续请求即使语句很快也拿不到连接。根因不是单纯“池太小”,扩大池会让更多慢查询并发进入数据库。修复把时间边界在应用层计算,语句直接比较原列,联合索引按租户、状态和时间的选择性设计;深分页改为稳定游标。接口增加最大时间窗和结果上限,报表任务化;连接获取超时小于总请求预算并触发明确降级。灰度使用真实参数分布比较扫描行、返回行、逻辑读、连接持有和数据库负载,还检查新增索引写放大。故障注入热点参数和锁等待后,池等待仍有界,普通交易不被报表拖垮。复盘增加计划基线、扫描/返回比和高成本查询发布守门。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么数据库中央处理器不满也会连接耗尽?直接回答:连接可能在锁、输入输出或网络上等待,仍被应用占用;需看持有时间和等待类型。
- 追问 2:只加查询超时够吗?直接回答:能止损但不修计划和入口容量;还要确保取消真正传到数据库并释放连接。
- 追问 3:索引上线如何控制风险?直接回答:评估建索引资源和写放大,选择在线方式与低峰,灰度验证计划且准备回退。
- 关联专题:SQL(结构化查询语言)映射、执行计划与连接
- 问题(综合题):请按事故模板讲一次 Runner(执行器)任务重复执行。
考点:租约、检查点、幂等副作用、强杀和接管。
口述答案:现象是滚动发布后同一结算任务执行两次,向下游发送了重复通知;任务表却只有一个成功状态。止血暂停该类新任务和通知出口,按任务号、步骤号和下游请求号冻结影响清单,不直接把任务状态改回。证据显示旧实例收到停机后仍在执行,但租约续期线程先被关闭;新实例看到租约过期接管,两个实例形成约十八秒重叠。任务最终状态更新有版本条件,因此只有一个成功,但通知调用没有幂等键,旧实例恢复后仍发送。根因是停机顺序错误、租约只保护任务表而未对外部副作用使用栅栏或幂等。修复先摘就绪、停止领取,再让在途工作线程在宽限期续租和保存检查点,最后关闭续租器;接管使用递增栅栏版本,内部状态更新拒绝旧版本。外部通知以任务号和步骤号作为稳定请求键,下游返回既有结果;无法支持幂等的渠道先查状态并通过本地发送记录收敛。任务按可提交单元保存检查点,至少一次执行成为明确设计。验证在领取后、通知前后、状态提交前后逐点强杀,确认任务可恢复、通知一次、旧持有者无法覆盖新状态。复盘加入发布演练、租约剩余量、接管率和重复命中指标。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:栅栏版本为什么比只看租约更强?直接回答:旧实例可能长暂停后恢复,虽租约已过期仍执行;资源可用递增版本拒绝旧持有者。
- 追问 2:下游不支持幂等怎么办?直接回答:本地建立发送状态机与唯一请求,超时先查询;仍无法确认时保持未知并人工处理,不能盲重发。
- 追问 3:任务表成功为何仍重复?直接回答:任务状态约束只保护本地记录,外部副作用发生在另一个故障窗口,必须独立幂等。
- 关联专题:Spring Boot(快速开发框架)运行生命周期与停机
- 问题(综合题):请按事故模板讲一次 IoT(物联网)报警风暴治理。
考点:分区隔离、背压、规则版本、去重聚合和关键容量。
口述答案:现象是规则发布后入口从每秒两千升至四万五千,队列最老年龄超过两分钟,数据库连接使用率百分之九十五,普通设备心跳和关键安全告警都延迟。止血先回滚规则,限制异常租户速率,暂停低级短信和报表消费者,但保留原始关键事件接收与独立安全告警通道。证据按租户、设备、规则版本和分区聚合,确认一个租户占流量八成,规则阈值缺少迟滞导致设备在边界来回触发;消费者虽快速取消息,却在数据库单条写和通知接口等待,单看队列深度会低估在途。修复在入口按租户与设备分区并设置配额,原始事件持久化后再做规则计算;同设备同规则使用时间窗去重、抑制和聚合,保留计数、首末时间和恢复事件。消费者有界并发,队列年龄和连接使用触发背压,优先暂停低级副作用;批量持久化带检查点,通知与工单以告警号幂等。规则发布先离线回放历史,再灰度少量设备,以命中倍数、队列年龄和通知率自动守门,事件记录规则版本。恢复后对误报工单执行可审计关闭。演练每秒六万事件,证明关键通道年龄低于五秒、租户互不拖累且最终告警可解释。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么按租户隔离?直接回答:单租户配置或设备异常不应耗尽全局线程、连接和通知配额,隔离能保住其他租户与关键等级。
- 追问 2:去重会不会丢掉持续时长?直接回答:不应只保留一条,应累计次数、首末时间和峰值,并在恢复时关闭聚合告警。
- 追问 3:规则回放通过就能全量吗?直接回答:不能,历史样本不覆盖实时分布和依赖容量,仍需灰度、自动守门和回滚。
- 关联专题:请求并发、异步与背压边界
- 问题(综合题):应用启动变慢但最终成功,如何定位到具体 Bean(对象实例)和外部依赖?
考点:刷新阶段、对象创建耗时、线程栈、就绪和启动风暴。
口述答案:结论是“最终启动成功”不代表没有风险,滚动扩容时长会减少可用容量,多个实例同步探测还可能把外部依赖拖垮。先固定制品、配置和实例批次,分解进程创建、环境准备、定义处理、单例预实例化、网页服务器启动、完成事件和就绪各阶段耗时;再定位慢对象的构造、依赖填充或初始化回调。周期线程栈能看出是否等待网络、锁、类加载或连接池,客户端指标和分布式链路确认外部调用。常见根因是初始化回调串行探测多个仓库、加载全量配置、同步预热巨大缓存、处理器过早创建对象,或者所有实例同时重试。修复原则是启动主链只做快速、确定、本地校验;核心远端依赖使用短超时和缓存健康结果进入就绪门禁,可选能力在就绪后有界并行预热并独立降级。外部探测加入随机抖动、隔离和熔断,避免发布风暴。WMS(仓储管理系统)只验证核心库存库和租户仓映射,低频承运商后台预热。验证比较优化前后阶段分位,注入一个渠道慢、全部渠道慢和扩容十实例,确认启动时间有界、错误实例不接流量、外部依赖请求峰值受控,关闭时已创建资源释放。
进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:把所有对象设懒加载可以吗?直接回答:会把配置和创建错误推迟到首请求并增加延迟,核心对象应启动期验证,只对真正可选且有预热策略的对象使用。
- 追问 2:如何发现某个初始化回调慢?直接回答:结合启动阶段事件、对象创建耗时指标和刷新线程栈,必要时在预发启用受控诊断。
- 追问 3:并行预热越多越快吗?直接回答:不一定,受线程、连接和外部配额限制,应使用有界并发和总体时间预算。
- 关联专题:容器刷新、懒加载与初始化失败
- 问题(综合题):为什么有的方法事务生效、另一些方法不生效,如何证明是代理路径问题?
考点:实际引用、自调用、方法可拦截性、早期对象和通知器链。
口述答案:结论是同一类上有注解不代表所有调用都会经过同一代理入口,差异通常来自调用路径和对象身份。先获取容器最终对象的代理类别、目标类型和通知器链,再对比生效方法与失效方法由谁调用:外部协作对象通过代理调用通常可拦截,同类方法使用 this 直接调用会绕过代理;私有或不满足当前代理方式的方法也可能不可拦截。若存在循环依赖、初始化时静态注册或手工 new,某些调用者可能持有原对象,导致只有一部分路径失效。还要排除事务管理器、数据源和异常规则不同,不能看到自调用就停止。证明过程是为同一业务场景记录匿名对象身份、代理命中、事务标识、连接绑定和最终数据库状态,构造外部调用与内部调用的对照;若外部路径回滚而内部路径提交,且后者没有事务拦截记录,证据闭合。修复优先把事务用例提升到独立应用服务公开方法,使调用从外部进入;删除原对象注册和手工创建,循环依赖则重构方向。避免在业务代码大量从容器获取自身代理,因为会隐藏依赖并增加测试困难。回归覆盖异常、嵌套调用、消息和调度入口,确保所有入口复用同一代理边界。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:换成另一种代理方式能解决自调用吗?直接回答:代理式调用的核心问题仍是内部调用未经过代理,换实现不应作为首选设计修复。
- 追问 2:怎样检查原对象逃逸?直接回答:比较各调用者引用与容器最终引用的类型和匿名身份,搜索初始化、静态集合、监听器和手工创建。
- 追问 3:事务模板可以用吗?直接回答:可用于需要显式程序化边界的局部场景,但应保持清晰,不能到处嵌套掩盖职责问题。
- 关联专题:AOP(面向切面编程)代理命中与旁路
- 问题(综合题):接口超时、线程池满和连接池满同时出现时,怎样确定因果顺序?
考点:时间线、队列理论、资源依赖和可证伪假设。
口述答案:结论是这些指标常形成连锁,不能把最响的告警直接当根因;需要统一时间轴和资源流。先确定最早偏离基线的业务量、外部延迟、数据库锁、线程活跃、队列年龄和连接持有,使用同一秒级时间窗对齐。画出入口线程何时申请连接、是否持有连接等待远端、是否再提交异步任务,消费者取出后是否占用数据库。假设一是远端变慢使事务持有连接,连接池等待让网页线程阻塞,最终线程池满;假设二是入口突增先占满线程,任务排队超时但连接仍空闲;假设三是慢查询或锁等待先占连接。用线程栈、连接匿名标识、事务时长、远端链路和数据库等待类型证伪。止血按根因方向:远端故障应熔断并移出事务,数据库锁应限流热点并终止异常操作,纯流量突增应配额和背压;盲目同时扩大两个池会把压力推给数据库。修复建立总请求预算,把连接获取、远端、重试和排队各自限制在预算内,线程池与连接池按调用链容量配合。验证分别注入远端慢、数据库慢和流量峰值,确认告警顺序与预期一致,核心业务能降级且资源自动恢复。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:线程栈只截一次够吗?直接回答:不够,连续多次快照才能区分长期阻塞和瞬时执行,并与资源指标对齐。
- 追问 2:如何识别连接泄漏?直接回答:请求结束后连接仍长期未归还,持有栈固定且业务量下降不恢复;结合泄漏诊断和事务边界确认。
- 追问 3:熔断会不会丢业务?直接回答:对可重试操作返回明确降级或持久化待处理状态,资金等副作用不能只快速失败而无恢复闭环。
- 关联专题:事务连接与超时边界
- 问题(综合题):设计一个从订单创建到库存预占、支付和履约的 Spring(Java 应用框架)端到端边界。
考点:协议、应用服务、事务、状态机、事件和幂等。
口述答案:结论是每个服务只在本地资源内建立强一致,用稳定业务键、状态机和可靠事件连接跨服务步骤,不能用一个超长事务覆盖订单、库存、支付和履约。入口由 Spring(Java 应用框架) MVC(网页模型视图控制器)完成身份、租户、参数和请求幂等校验,订单应用服务在短事务中创建订单和待办事件。库存服务收到预占命令后以订单号和步骤唯一,使用条件更新守住不为负,并写预占流水;结果事件推进订单。支付服务以支付单和渠道请求号管理处理中、未知、成功和失败,渠道调用在数据库事务外,成功确认后本地写资金流水和事件。只有订单、库存和支付状态满足履约前置条件,履约服务才幂等创建出库单;失败或超时由订单编排状态机触发库存释放、支付关闭或人工处理。Bean(对象实例)容器负责组装端口和渠道适配器,AOP(面向切面编程)承载事务、安全与审计入口,但领域状态迁移显式写在应用服务;MyBatis(持久层框架)负责条件更新、唯一约束和同事务会话。所有消息至少一次,消费者按业务事件号幂等,Outbox(发件箱)保证本地状态与待发事件同成同败。验证通过重复命令、消息乱序、渠道超时、库存冲突、发布确认丢失和强杀,最终以订单、库存台账、资金流水和履约单四方对账。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么订单编排不直接调用所有服务并等结果?直接回答:可同步发起以降低延迟,但状态必须持久化并承受超时未知,不能依赖一次调用栈保证跨系统原子。
- 追问 2:库存预占多久释放?直接回答:由订单支付期限和业务规则决定,超时任务用幂等释放;释放前核对订单和支付状态防止竞态。
- 追问 3:怎样处理事件乱序?直接回答:状态机按版本和合法前置状态迁移,迟到事件幂等保存或拒绝,必要时查询权威状态修正。
- 关联专题:事务边界与最终一致性
- 问题(综合题):支付对账怎样设计,才能发现本地状态、渠道账单和资金流水的不一致?
考点:三方事实、差异分类、补偿审批和审计。
口述答案:结论是对账不是把两张表做相等比较,而是以渠道账单、本地支付单和不可变资金流水三方事实,按商户、币种、金额、渠道流水和业务日期建立可解释差异。渠道文件或接口先验签、校验摘要和批次完整性,原始文件只读归档;解析结果以渠道流水唯一落库。匹配时区分渠道成功本地未知、本地成功渠道缺失、金额币种不符、重复渠道流水、退款阶段不一致和手续费差异,每类都有自动与人工边界。渠道成功本地未知先主动查单,确认后通过幂等状态命令补记支付与流水并发布事件;本地成功渠道缺失不能自动冲正,需核对账期、延迟和渠道证明。资金调整必须生成独立审计单和审批,不直接改历史流水。Spring(Java 应用框架)任务以批次号和检查点运行,Runner(执行器)可重入;MyBatis(持久层框架)批量匹配按稳定游标和小事务,连接池有独立配额,不影响在线支付。安全上账单文件和管理接口最小权限、加密和访问审计。验证构造缺行、重复、金额差、延迟、任务强杀和重复导入,确保差异数、处理状态和调整流水可追溯;指标包括账单到达、未匹配金额、未知状态年龄和人工积压。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么不直接以渠道为准覆盖本地?直接回答:本地可能有退款、币种和业务映射问题,覆盖会丢失审计;应通过状态命令和调整流水收敛。
- 追问 2:对账任务重复运行怎么办?直接回答:批次、渠道流水和差异项都有唯一键,处理命令幂等,检查点只优化性能不承担正确性。
- 追问 3:实时查单能替代日终对账吗?直接回答:不能,实时查单处理单笔未知,账单对账发现系统性漏单、金额与手续费差异,两者互补。
- 关联专题:事务状态、未知结果与补偿
- 问题(综合题):全局异常处理怎样既保持协议稳定,又不吞掉事务和安全语义?
考点:异常分层、回滚、响应提交、敏感信息和可观测性。
口述答案:结论是异常处理器负责把已经发生的错误转换为稳定对外协议,不应改变领域原子性或把所有失败包装成成功。参数格式、校验、未认证、禁止访问、业务冲突、资源不存在、限流和系统故障要有不同错误码与可观测分类;对外消息不泄露堆栈、表名、密钥和内部拓扑,对内日志携带请求号、主体摘要、业务键和完整原因。事务方法必须先让需要回滚的异常穿过代理边界,控制器外层再转换响应;若在事务内捕获后正常返回,异常处理器根本看不到且数据可能提交。响应一旦部分写出,后续异常可能无法更改状态码,流式和异步响应需要专门的失败协议。安全异常应由正确过滤器或访问拒绝处理链处理,不能被普通业务处理器改成可缓存成功。支付渠道可能要求任何已受理重复回调返回特定成功文本,但内部仍要区分首次处理、幂等命中和系统失败并告警。排障确认异常从哪层抛出、是否被包装、事务回滚标记、响应是否提交和最终数据库状态。验证矩阵覆盖绑定失败、权限拒绝、业务冲突、数据库异常、提交失败、异步异常和响应中断,确保协议、日志和数据三者一致。
进一步验收时,我会固定制品、配置和依赖小版本,记录基线吞吐、延迟、线程、连接与业务状态,再分别注入重复、超时、部分失败和强杀。修复只有在技术指标恢复、业务不变量成立、失败可重放且回滚路径经过演练后才算完成;若仍依赖人工临时改数据,就继续保留风险项和所有者。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:统一返回 HTTP(超文本传输协议)状态
200有什么问题?直接回答:会让网关、监控和客户端重试难以区分失败;若外部协议强制,应在业务码和内部指标中保持真实语义。 - 追问 2:异常日志都打错误级别好吗?直接回答:不好,预期业务拒绝应较低级别并计数,系统故障才告警,避免噪声淹没真正事故。
- 追问 3:可以把原异常消息直接返回吗?直接回答:不应,可能泄露内部结构和敏感值;映射为受控消息,完整原因只在安全日志中保留。
- 关联专题:异常解析、响应提交与异步错误
- 问题(综合题):怎样建立跨容器、代理、事务和数据库的可观测证据链?
考点:关联标识、结构化日志、指标、链路和业务对账。
口述答案:结论是技术可观测性必须能回答一次业务操作从哪个制品、配置和实例进入,经过哪个代理与事务,使用哪条语句和连接,最终改变了什么业务状态。入口生成或验证请求号,支付、订单、任务和事件各有稳定业务键;日志使用结构化字段记录实例版本、配置摘要、主体与租户脱敏标识、代理入口、事务标识、映射语句标识和结果类别,但不输出令牌、密钥和完整报文。指标分层:容器看启动阶段与对象创建耗时,网页层看请求量、延迟与错误分类,安全层看认证和禁止原因,事务层看时长、回滚与连接等待,持久层看扫描行、锁和批次,业务层看未知支付、库存冲突、轨迹年龄和任务租约。链路追踪连接同步调用,消息和任务通过事件号或任务号建立链接,不能假设线程上下文自动传播。最终数据库状态、不可变流水和对账是正确性证据,日志和链路只是解释过程。事故中先用业务指标确定影响,再下钻技术证据,避免全量搜索。验证通过故意制造一次回滚、重复消息、越权拒绝和慢查询,确认能从业务键走到完整链路,也能从告警反查受影响业务清单;采样不能丢失资金错误,敏感字段经过审计。
为了让结论可复核,我会把主体、业务键、时间、线程、事务、连接和最终数据放到同一时间线,并明确每项证据能证明什么、不能证明什么。灰度期间设置停止条件和可回滚开关,结束后用独立查询或对账确认结果,避免把一条成功日志、一次无异常请求或短时指标下降误当成根因已经消失。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:链路追踪采样会不会漏事故?直接回答:会,关键失败和资金事件应强制保留或通过业务流水补充,普通成功请求再按策略采样。
- 追问 2:为什么需要配置摘要?直接回答:同一制品在不同配置下行为不同,摘要能关联变化且避免记录明文敏感值。
- 追问 3:日志能作为资金事实吗?直接回答:不能,日志可能丢失或重复,资金事实应由不可变流水和渠道对账证明。
- 关联专题:代理链、拦截顺序与诊断
- 问题(综合题):多租户条件由 MyBatis(持久层框架)插件自动改写时,怎样防止漏条件和误改写?
考点:语法树、语句边界、显式范围、测试和降级。
口述答案:结论是插件可集中提供租户守门,但它处在持久层代理链,面对联表、子查询、别名、更新、删除和自定义语句时容易漏改或重复改写,不能成为唯一授权依据。核心仓储接口先显式要求租户范围,应用服务从可信主体构造而不是接受任意请求参数;插件再解析 SQL(结构化查询语言)语法树,为受管表的每个正确别名追加参数化租户条件,并拒绝无法安全识别的语句。白名单绕过只允许迁移、对账等明确服务主体,必须审计,不能用线程本地布尔值长期关闭。改写前后语句、参数和映射标识可以在受控调试中记录摘要,绝不拼接原始租户值以免注入。批量、子查询、联合、逻辑删除和分页插件的顺序要明确,重复条件不应改变计划。排障从主体租户、实际 Mapper(映射器)语句、插件顺序、改写结果和数据库行归属核对,若发现无法解析应快速拒绝而非无条件执行。验证建立语句语料库,覆盖查询、更新、删除、联表、子查询、别名、动态片段和管理旁路,并进行跨租户负向测试;生产指标记录拒绝、绕过和改写失败。最终仍由数据库查询条件和审计证明没有越权,而不是“插件已启用”。
容量层面还会用接近生产的租户分布、热点比例和失败延迟进行阶梯压测,同时观察队列最老年龄、资源等待、拒绝、重试和下游承载。达到阈值时系统应先背压或降级非核心能力,而不是无限堆积;恢复后资源要自动回到基线,积压有明确清理速度,核心交易不因批处理或外部抖动被拖垮。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么不直接字符串拼接条件?直接回答:容易破坏语法、漏掉嵌套结构并引入注入;应使用结构化解析或显式语句。
- 追问 2:插件顺序有什么影响?直接回答:分页、租户和数据权限改写相互嵌套,错误顺序可能只给外层或计数语句加条件,必须用实际语句验证。
- 追问 3:无法解析时降级为不加条件可以吗?直接回答:安全边界应失败关闭,拒绝执行并告警;静默放行会造成越权。
- 关联专题:MyBatis(持久层框架)插件、代理与数据权限
- 问题(综合题):怎样为 Spring(Java 应用框架)项目建立比单元测试更完整的故障验收矩阵?
考点:机制验证、集成边界、故障注入和业务不变量。
口述答案:结论是精通级验收要同时证明正常机制、失败边界和恢复结果,单元测试只覆盖局部逻辑。第一层是无容器领域测试,验证状态机、金额与库存不变量;第二层是最小上下文测试,验证 Bean(对象实例)候选、生命周期、代理链、事务回滚、过滤器顺序和 Mapper(映射器)映射;第三层使用真实数据库和消息代理的集成测试,检查唯一约束、锁、传播、会话绑定、重复投递和缓存;第四层在预发或受控环境进行容量与故障注入,包括外部超时、连接池接近上限、消息积压、令牌轮换、进程强杀和滚动停机。每个用例记录前提、输入、注入点、期望技术证据和业务最终状态,不能只断言返回码。支付要求支付单、流水和事件同成同败,未知状态可查单;库存要求守恒,导出要求文件摘要与检查点,Runner(执行器)要求重复副作用被幂等吸收。版本相关行为先读取项目依赖树,避免测试另一版本的默认值。发布门禁使用少量高价值场景,耗时演练定期执行。验收失败要能定位到容器、代理、事务、请求、安全或持久层,而不是用大而全测试难以诊断。最终将生产事故转成最小复现和长期回归,证明防线自动化而非依赖记忆。
安全与可靠性验收会同时覆盖正常、越权、伪造、重复、乱序和过期输入,所有拒绝都要有脱敏审计,所有重试都要由持久业务键吸收。关键结论不能依赖单机内存或线程上下文,进程重启、实例扩容和消息重放后仍应得到同一业务终态;无法自动确定的结果必须进入未知状态、查证和人工闭环。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:为什么不追求百分之百覆盖率?直接回答:行覆盖不等于边界覆盖,应优先高风险状态迁移、故障窗口和不变量。
- 追问 2:生产能做故障注入吗?直接回答:只在有隔离、限额、回滚和审批时做小范围演练;先在预发验证,资金场景尤其谨慎。
- 追问 3:怎样防止测试依赖实现细节?直接回答:主要断言公开协议、业务状态和关键证据,源码方法名只用于版本化诊断测试。
- 关联专题:版本边界、验证门禁与学习路线
- 问题(综合题):面试中如何用一条主线证明自己真正掌握 Spring(Java 应用框架),而不是只会背注解?
考点:设计思想、运行链路、失败边界、项目证据和取舍。
口述答案:我的主线会从“边界所有权”展开。容器层通过 IoC(控制反转)和 DI(依赖注入)把对象创建、实现选择和生命周期移出业务类,但不能替代正确领域划分;Bean(对象实例)从定义、实例化、填充、初始化到代理和销毁,每阶段都有可观察失败。AOP(面向切面编程)把事务、安全和审计放在公开代理入口,但同类自调用、原对象和异步线程会旁路,所以领域状态机必须显式。Spring(Java 应用框架) Transaction(事务)只保证本地资源,连接、传播、异常和最终提交都要证据;支付、库存和履约跨系统靠幂等、Outbox(发件箱)、状态机、补偿与对账。Spring(Java 应用框架) MVC(网页模型视图控制器)负责协议、绑定、校验和异常边界,异步任务要持久化而不是只换线程。Spring Security(安全框架)按认证、方法授权和数据归属分层,MyBatis(持久层框架)把会话、连接、映射、锁和缓存落到真实数据库语义。项目上我会讲一个支付重复回调或库存并发事故,给出影响、止血、两项被否定假设、根因、修复取舍、故障注入和量化结果;再说明版本事实来自依赖树和运行证据。这样既能解释框架为何这样设计,也能说明什么时候不用它、失败时看什么、业务最后如何对账。精通不是记住所有类名,而是能把请求、对象、线程、连接、事务和业务状态连成可验证链路。
设计复盘时我会说明曾考虑的替代方案、没有采用的原因以及剩余边界,把临时止血、永久修复和长期治理分开。行动项必须转化为启动校验、唯一约束、容量门禁、告警或演练,并绑定负责人和期限;下一次相同故障应由系统提前拒绝、快速降级或自动恢复,而不是再次依赖个人经验排查。
最后还要归档可复现输入、核对语句和恢复结果,使后续维护者能独立验证结论,而不是只能相信事故当事人的口头判断。
- 追问 1:最能体现高级能力的点是什么?直接回答:能明确框架能力边界,把技术成功与业务正确分开,并用数据和故障验证取舍。
- 追问 2:源码要讲到什么程度?直接回答:讲清关键控制点、数据结构和调用阶段即可,方法名须绑定版本,不为炫技背完整调用栈。
- 追问 3:如何避免项目话术显得虚?直接回答:给出具体时间窗、量级、证据、错误方案、个人决策、结果和仍存边界,不编造无法核对的数字。
- 关联专题:Spring(Java 应用框架)模块知识图谱
4. 项目话术速记与复习清单
事务失效排查话术:我不会先补注解,而是先证明调用引用是否为代理、是否从外部进入可拦截方法,再核对事务管理器、数据源、线程、传播和连接;随后检查异常是否离开代理、回滚标记和提交日志,最后用独立连接确认数据库状态。止血与根因修复分开,修复后用异常矩阵、连接压力和业务对账回归。
- 能按定义、实例化、填充、初始化、代理和销毁复述 Bean(对象实例)生命周期,并指出每阶段失败证据。
- 能画出循环依赖早期引用与最终代理身份,说明构造器、原型和异步不可解边界。
- 能证明事务是否经过代理、绑定哪条连接、为何回滚或提交,以及数据库最终状态。
- 能串讲网页请求、过滤器安全、参数校验、应用服务事务和 MyBatis(持久层框架)会话。
- 能用支付、库存、物流、导出、Runner(执行器)和 IoT(物联网)各讲一个有数据的项目故事。
- 能按现象、影响、证据、假设、止血、根因、修复、验证和复盘讲事故。
- 能解释框架能力与跨系统一致性、业务幂等、补偿和对账的边界。
