面试知识

15.91.07 Spring(Java 应用框架)事务安全与运行时追问

91-高频追问题库 面试知识整理。

15.91.07 Spring(Java 应用框架)事务安全与运行时追问

本册训练的不是背诵注解,而是从运行现象还原 Bean(对象实例)、代理、线程、事务、身份与数据库事实。每个结论都要区分 E1(直接证据)、E2(已有材料映射)、E3(演练设计)和 E0(待核对);没有源码、配置、原始日志或可复现实验时,不把候选根因说成生产事实。

Spring(Java 应用框架)事务安全与运行时失败追问证据链

完整机制回链 Spring(Java 应用框架)生态正文入口,项目事实边界回链 模块 15 唯一事实卡。统一事故回答顺序为:现象定界、竞争假设、证据链、止血、根因修复、存量校验、分档验证与复盘。

1. Bean(对象实例)生命周期、容器刷新与 Spring Boot(快速开发框架)启动失败

1.1 从定义注册到可接流量的运行边界

Spring Boot(快速开发框架)启动成功不能只看进程存在。环境属性先形成,BeanDefinition(Bean 定义)被注册和修改,BeanFactoryPostProcessor(Bean 工厂后置处理器)影响定义,BeanPostProcessor(Bean 后置处理器)参与实例化前后、依赖注入和初始化,代理可能在后置阶段替换原对象;所有关键单例创建、内嵌服务器绑定、Runner(执行器)初始化和就绪事件完成后,实例才有资格接流量。构造器异常、循环依赖、配置绑定错误、端口冲突、数据库迁移阻塞、初始化外部调用超时,分别落在不同阶段,必须用首个根异常、条件评估和生命周期事件定位,不能被最后一层“上下文刷新失败”概括。

flowchart TD
    A[加载环境与配置源] --> B[注册 BeanDefinition(Bean 定义)]
    B --> C[执行工厂级后置处理]
    C --> D[实例化与依赖注入]
    D --> E[执行 BeanPostProcessor(Bean 后置处理器)]
    E --> F[初始化并形成代理]
    F --> G[启动内嵌服务器]
    G --> H[执行 Runner(执行器)与就绪事件]
    H --> I{依赖与自检通过}
    I -- 是 --> J[开放 Readiness(就绪状态)并接流量]
    I -- 否 --> K[保持摘流量并保存启动证据]

图解读: 正常路径把“进程启动”和“业务就绪”分开;失败路径在开放 Readiness(就绪状态)前截断流量。前提是探针不只检查端口,还检查应用真正依赖的最小能力;结论是启动阶段必须可定位、可超时、可降级,不能在构造器里执行无界远程调用。

失败阶段典型现象首要证据临时止血长期修复与验证
环境与绑定属性缺失、类型转换失败生效配置源、绑定路径、版本差异回退配置或保持摘流量启动前校验、配置契约测试
定义注册条件不匹配、对象重复条件评估报告、定义来源回退依赖或显式排除缩小自动配置条件、固定兼容矩阵
实例化与注入循环依赖、构造器异常首个根异常、依赖图禁用非关键模块重划职责、移除不可解构造环
初始化外部依赖超时、迁移阻塞初始化耗时、线程栈、连接状态限时跳过可延后任务延迟初始化、异步预热、失败预算
就绪与接流量端口开但关键能力未好就绪事件、探针、首批请求立刻摘流量以业务最小能力定义就绪门禁

数据演绎 1:启动风暴怎样耗尽连接。 E3(演练设计)中,20 个实例同时发布,每个初始化逻辑并发打开 8 个数据库连接并等待 45 秒,瞬时需求为 20 × 8 = 160 个连接;若数据库给该应用的池外总预算只有 100,至少 60 个请求进入等待,超时重试又会复制连接尝试。把发布批次限制为 4 个实例、每实例预热并发限制为 2,瞬时预热需求降为 4 × 2 = 8。真实数字必须由连接监控、发布批次与启动日志核对,不能把演练值表述成生产容量。

失败注入、证据链与项目落地: E3(演练设计)分别注入配置缺失、端口占用、数据库 3 秒延迟、初始化任务抛错和就绪事件前强制流量。保存提交版本、配置摘要、条件评估、首个根异常、线程栈、连接池、端口、生命周期事件与探针转换时间。止血是暂停发布、把未就绪实例摘流量、限制同时启动数并回退最后配置;修复后在隔离环境重放五类注入,再以 10%、30%、60%、100% 分档发布。验证不仅看进程和端口,还要检查支付回调验签、库存查询、Runner(执行器)续租和健康接口均可用;复盘明确为什么就绪门禁未阻断坏实例。机制为 E2(已有材料映射),具体事故与收益无原始证据时为 E0(待核对)。参考 Spring Boot(快速开发框架)官方启动文档Spring(Java 应用框架) Framework(Spring 核心框架)官方容器扩展点文档

热门面试题

  1. 问题(基础题):Bean(对象实例)从定义到可用要经历哪些关键阶段?

    • 考点:定义注册、实例化、注入、后置处理、初始化、代理与销毁。
    • 回答思路:按容器刷新顺序回答,并指出原对象可能被代理替换。
    • 详细答案:容器先读取并注册 BeanDefinition(Bean 定义),工厂级后置处理器可修改定义;随后实例化、属性填充,BeanPostProcessor(Bean 后置处理器)在初始化前后介入,初始化回调完成后对象可能以代理形式进入单例池。关闭时再按依赖关系执行销毁回调。不同作用域和提前暴露会改变局部路径,不能把三级缓存说成所有生命周期的必经步骤。
    • 进阶追问:为什么构造器里不适合调用远程服务?
    • 进阶回答:构造器处于容器创建关键路径,远程抖动会阻塞上下文刷新、占住连接并放大发布风暴,也难以使用完整代理和容错设施。
  2. 问题(原理题):端口已经监听,为什么实例仍不应接业务流量?

    • 考点:Liveness(存活状态)、Readiness(就绪状态)和业务依赖。
    • 回答思路:区分进程存活、网络可达和关键业务能力就绪。
    • 详细答案:服务器绑定端口只证明网络入口存在,Runner(执行器)、缓存预热、数据库迁移、鉴权密钥或路由表可能尚未准备好。应由 Readiness(就绪状态)表达是否接流量,由 Liveness(存活状态)表达进程是否需要重启;把二者混用会导致启动期错误或重启循环。
    • 进阶追问:就绪探针是否应该检查所有下游?
    • 进阶回答:不应无差别检查;只纳入决定该实例能否安全服务的关键依赖,非关键依赖应降级,否则一个边缘依赖会让全体实例同时摘流量。
  3. 问题(项目题):Runner(执行器)启动时数据库变慢,如何避免全量任务重复领取?

    • 考点:就绪门禁、租约、领取开关和幂等。
    • 回答思路:先阻止新实例领任务,再保护已有租约与业务不变量。
    • 详细答案:新实例在数据库与租约组件未通过自检前保持领取开关关闭;旧实例按既有租约继续或进入受控停机。若启动超时,实例摘流量退出而不是反复抢占。任务领取使用稳定任务键、租约版本和条件更新,执行结果保持幂等,因此即便实例重启也不能仅凭本地内存判断所有权。
    • 进阶追问:启动完成后立即把领取并发拉满可以吗?
    • 进阶回答:不可以,应按数据库连接、队列年龄和任务成功率分档升并发,出现锁等待或重复率上升就退回上一档。

2. AOP(面向切面编程)代理、自调用失效与事务入口

2.1 代理边界不是注解边界

Spring(Java 应用框架)声明式事务通常由代理拦截外部调用,TransactionInterceptor(事务拦截器)根据方法与传播属性获取或加入事务,再调用目标对象。目标对象内部使用 this 直接调用另一个带 @Transactional(事务注解)的方法时,调用没有重新经过代理,新的传播、回滚与监控规则可能全部不生效。类似失效还包括对象由 new 创建、方法不可被当前代理方式增强、错误代理对象被缓存、注解放在无法解析的位置。排查要证明“运行时对象是谁、调用从哪里进入、拦截器链是否执行、连接是否绑定当前线程”,不能只看到注解存在。

sequenceDiagram
    participant C as 调用方
    participant P as AOP(面向切面编程)代理
    participant T as 目标对象
    participant X as 事务拦截器
    participant D as 数据库
    C->>P: 调用外部入口
    P->>X: 匹配事务规则
    X->>D: 开启或加入事务
    X->>T: 执行业务方法
    T->>T: this 调用内部方法
    Note over T: 不再经过代理,内部传播规则可能失效
    T-->>X: 返回或抛异常
    X->>D: 提交或回滚
    X-->>C: 返回结果

图解读: 正常外部入口经过代理和拦截器;失败支路在目标对象内部闭合,因此注解存在却没有新事务语义。前提是使用代理式声明事务;结论是边界应由服务职责和调用图设计,而不是依赖获取当前代理等隐藏技巧维持。

失效方式可观察证据风险首选修复回归验证
this 自调用调用栈无代理与事务拦截器传播、回滚、监控不生效拆到独立协作 Bean(对象实例)外部入口触发新代理链
手工 new 对象运行时对象不在容器所有容器增强失效由容器注入对象身份与定义来源一致
捕获异常不再抛事务拦截器看到正常返回本应回滚却提交转换后继续抛或显式标记回滚数据库最终状态符合规则
异步线程继续执行新线程无原线程资源事务被切断新线程显式开启独立事务线程与事务标识可关联
多代理顺序错误拦截器链顺序与预期不同鉴权、重试、事务交互异常明确顺序并缩小职责记录完整调用链与一次副作用

数据演绎 2:自调用让批处理部分提交。 E3(演练设计)中,外层方法循环处理 100 个支付补偿,本意让每个内部方法以 REQUIRES_NEW(总是新事务)独立提交;实际使用 this 自调用后,100 个操作加入同一外层 REQUIRED(需要事务)。第 80 个抛出运行时异常时,前 79 个也回滚;若异常被捕获,100 个又可能整体提交。修复为独立 Bean(对象实例)代理入口后,假设 79 个成功、1 个失败、20 个未执行,则账面应明确为 79 个提交、1 个失败记录、20 个待处理,而不是依赖模糊的循环返回值。

失败注入、证据链与项目落地: E3(演练设计)对代理入口、自调用、手工对象、捕获异常和异步线程分别注入第 3 步失败。记录运行时对象类型、调用栈、拦截器日志、事务名、线程名、连接身份、提交/回滚和业务流水。止血先暂停自动补偿与批量重放,防止错误边界继续扩大;修复优先拆分独立事务服务、显式定义异常策略和幂等键,不以当前代理暴露作为默认架构。验证对支付回调、库存释放和 Runner(执行器)任务各做成功、受检异常、运行时异常和进程强杀测试,核对业务表与 Outbox(发件箱)守恒。复盘补充代理边界测试和架构评审清单。机制为 E2(已有材料映射),生产调用图与历史影响为 E0(待核对)。参考 Spring(Java 应用框架) Framework(Spring 核心框架)官方代理机制文档Spring(Java 应用框架) Framework(Spring 核心框架)官方事务文档

热门面试题

  1. 问题(基础题):为什么同类自调用会让 @Transactional(事务注解)失效?

    • 考点:代理入口与目标对象内部调用。
    • 回答思路:画出调用方、代理和目标对象三者关系。
    • 详细答案:外部调用先到代理,代理才执行事务拦截器;目标对象中的 this 调用直接落到自身方法,不会再次穿过代理,因此内部方法声明的新传播或回滚规则不被读取。外层若已有事务,内部数据库操作可能仍在外层事务中,这容易造成“看似有效”的误判。
    • 进阶追问:改成从容器获取自己是否可行?
    • 进阶回答:技术上可能绕回代理,但引入容器耦合和隐藏调用关系;优先按事务职责拆分独立 Bean(对象实例),让入口显式可测。
  2. 问题(原理题):怎样证明一次调用真正经过了事务代理?

    • 考点:运行时对象、拦截器链、线程资源和数据库结果。
    • 回答思路:给出从对象身份到提交结果的完整证据。
    • 详细答案:检查对象是否为容器管理代理、方法是否命中事务属性、调用栈是否包含事务拦截器、当前线程是否绑定事务资源,并通过受控异常观察提交或回滚。只打印“事务活跃”仍不够,还要核对同一连接上的业务写和最终数据库状态。
    • 进阶追问:日志显示开启事务就能证明回滚成功吗?
    • 进阶回答:不能,日志可能只覆盖入口;还需读取目标行、唯一流水、事务完成回调和必要的数据库日志证据。
  3. 问题(项目题):支付回调自调用导致 REQUIRES_NEW(总是新事务)未生效,如何恢复?

    • 考点:资金状态、幂等、存量查证与分批重放。
    • 回答思路:先冻结回放,再按支付单和账务事实分类。
    • 详细答案:暂停该版本回调重放,固定回调标识、支付请求号和受影响时间窗,按渠道状态、支付单、不可变分录与 Outbox(发件箱)分类成功、失败、未知。修复为独立代理入口并保持原幂等键,代表样本影子执行后分批重放;任何差异通过独立补偿单处理,不能修改历史流水伪造一致。
    • 进阶追问:为什么不能把所有回调重新发送一遍?
    • 进阶回答:全量重放会绕过未知态查证并放大重复副作用,必须先分类、确认幂等覆盖范围,再限速恢复。

3. 事务传播、回滚规则与异步边界

3.1 本地事务只约束同一资源与执行上下文

REQUIRED(需要事务)适合让一组本地写共享成败,REQUIRES_NEW(总是新事务)会暂停外层并建立独立物理事务,NESTED(嵌套事务)通常依赖保存点允许局部回滚,但具体支持取决于事务管理器和资源。传播回答必须说明“当前是否已有事务、使用哪个事务管理器、是否同一资源、异常是否穿过代理”。默认回滚通常面向未检查异常和错误;受检异常、被捕获异常、返回失败码、完成后才出现的异步异常,都可能让拦截器看到正常返回而提交。@Async(异步注解)、线程池、消息消费和远程调用会建立新的执行上下文,原线程事务不会自动传播;本地提交与外部支付、MQ(消息队列)发布、邮件或物流请求之间要用幂等、Outbox(发件箱)、查证和补偿闭环。

flowchart TD
    A[代理入口] --> B{当前是否存在事务}
    B -- 否 --> C[按传播规则创建或非事务执行]
    B -- 是 --> D{REQUIRED(需要事务)/ REQUIRES_NEW(总是新事务)/ NESTED(嵌套事务)}
    D -- REQUIRED --> E[加入当前事务]
    D -- REQUIRES_NEW --> F[暂停外层并开启独立事务]
    D -- NESTED --> G[在支持时建立保存点]
    E --> H{是否切换线程或调用外部系统}
    F --> H
    G --> H
    H -- 否 --> I[按异常规则提交或回滚]
    H -- 是 --> J[原事务边界终止]
    J --> K[新事务、幂等、Outbox(发件箱)或补偿]

图解读: 传播首先是当前调用栈内的选择,线程切换或跨系统后必须重新定义一致性协议。前提是事务资源绑定线程且由对应事务管理器控制;结论是“方法上有事务”不能推出支付渠道、消息与数据库原子提交。

场景实际边界常见误判安全做法验证证据
REQUIRED(需要事务)内层失败通常影响同一物理事务捕获后外层还能正常提交明确异常合同与回滚标记最终状态、回滚原因
REQUIRES_NEW(总是新事务)审计内层独立提交,外层暂停外层回滚会带回内层只保存允许独立存在的事实两个事务标识和提交顺序
NESTED(嵌套事务)局部回滚依赖保存点和资源支持等同独立新事务启动时验证能力并准备降级保存点、外层最终结果
@Async(异步注解)方法新线程与新调用链继承调用方事务新入口显式事务与幂等线程、事务和任务标识
数据库后调用支付两个独立系统本地回滚能撤销扣款原请求号、未知态查单与补偿渠道、支付单、分录对账
提交后发布消息写库与发送存在窗口发送成功等于业务提交成功Outbox(发件箱)同事务记录业务行、事件行、投递回执

数据演绎 3:错误传播如何扩大失败域。 E3(演练设计)中,外层订单事务耗时 120ms(毫秒),每次调用内层 REQUIRES_NEW(总是新事务)写审计 30ms(毫秒),并发 80 个请求;理论同时占用连接的上界可能接近 80 × 2 = 160,因为外层连接暂停但未释放,内层还需新连接。若连接池只有 100,至少 60 个内层请求存在等待机会,超时又可能把外层拖成长事务。把审计改为与业务同事务的 Outbox(发件箱)或提交后受控异步处理,可减少双连接峰值,但必须接受并治理最终一致性。

失败注入、证据链与项目落地: E3(演练设计)注入受检异常、异常被捕获、内层回滚标记、REQUIRES_NEW(总是新事务)连接耗尽、异步线程抛错、提交后进程强杀和消息发送成功但本地回滚。保存调用树、传播属性、事务管理器、线程、连接、回滚标记、事务同步回调、业务行、Outbox(发件箱)与外部请求号。止血先关停自动重试、缩小批次、暂停非关键独立事务并保护账务连接;修复异常合同、事务拆分和可靠事件。验证以支付单、账务分录、库存流水和事件表做守恒,对未知态逐笔查证;复盘把事务时长、回滚率、连接等待和未知态年龄纳入门禁。机制为 E2(已有材料映射),演练连接数为 E3(演练设计),生产影响为 E0(待核对)。参考 Spring(Java 应用框架) Framework(Spring 核心框架)官方事务传播文档Spring(Java 应用框架) Framework(Spring 核心框架)官方回滚规则文档

热门面试题

  1. 问题(基础题):REQUIRED(需要事务)与 REQUIRES_NEW(总是新事务)的核心差异是什么?

    • 考点:逻辑作用域、物理事务、资源占用与回滚关系。
    • 回答思路:从当前是否有事务、连接和最终提交分别比较。
    • 详细答案:REQUIRED(需要事务)在已有事务时加入同一物理事务,没有时才新建;REQUIRES_NEW(总是新事务)会暂停外层,申请独立资源并单独提交或回滚。后者能隔离结果,却增加连接占用和提交顺序复杂度,不适合被当成“保证记录一定成功”的通用补丁。
    • 进阶追问:内层 REQUIRES_NEW(总是新事务)成功后外层失败,内层会回滚吗?
    • 进阶回答:通常不会,它已经独立提交;所以内层只能保存允许脱离主业务长期存在的事实,并准备关联和补偿。
  2. 问题(原理题):为什么 @Async(异步注解)方法不会继承调用方本地事务?

    • 考点:线程绑定资源和执行上下文切换。
    • 回答思路:说明事务资源绑定原线程,再说明新线程如何建立边界。
    • 详细答案:声明式本地事务通常把连接等资源绑定当前线程;异步任务在线程池的另一线程执行,原线程可能已经提交并返回,新线程既没有原连接,也不能延长原事务。异步入口要自己开启短事务,并通过持久任务、业务键和状态机处理重复与恢复。
    • 进阶追问:把事务上下文手工复制到新线程可以吗?
    • 进阶回答:不应复制活动连接和事务控制权,线程生命周期与提交时序不匹配;应传递业务标识和只读观测上下文,重建独立事务。
  3. 问题(项目题):支付回调数据库提交成功,但异步通知失败,如何保证最终一致?

    • 考点:本地事实、可靠事件、幂等消费和对账。
    • 回答思路:把支付状态与通知副作用拆开,用可恢复记录连接。
    • 详细答案:验签与幂等通过后,在同一本地事务内更新支付单、写账务分录和 Outbox(发件箱)事件;事务提交后由投递器发送,消费者按事件和业务键幂等。发送失败保留重试状态和预算,超过时效进入人工;支付事实不因通知失败倒退,对账持续核对渠道、支付单、分录和事件。
    • 进阶追问:事务提交后直接同步发消息,失败再重试是否足够?
    • 进阶回答:进程可能在提交后、记录重试前崩溃,形成不可见窗口;持久 Outbox(发件箱)或等价日志才能让待投递事实可恢复。

4. Spring Security(安全框架)认证授权、网关与上下文传播

4.1 身份建立、权限裁决与跨线程隔离

认证回答“调用者是谁以及凭据是否可信”,授权回答“该身份能否对这个资源执行这个动作”。Spring Security(安全框架)过滤器链先匹配请求,再从会话、令牌或其他凭据构造 Authentication(认证信息),由认证组件校验后放入 SecurityContext(安全上下文),后续网页层与方法安全基于身份、权限和业务资源做裁决。Gateway(网关)可以做粗粒度验签、限流和路由,但下游仍需验证可信来源并执行资源级授权;否则内部绕过网关、错误路由或权限变更会变成越权。SecurityContext(安全上下文)、租户、TraceId(链路标识)和 MDC(映射诊断上下文)常与线程相关,进入线程池必须显式传播所需快照并在结束时清理,不能让前一请求身份泄漏到后一任务。

sequenceDiagram
    participant U as 调用方
    participant G as Gateway(网关)
    participant F as 安全过滤器链
    participant A as 认证组件
    participant Z as 资源级授权
    participant W as 业务服务
    U->>G: 携带凭据与请求标识
    G->>G: 粗验签、限流、路由
    G->>F: 传递受信请求信息
    F->>A: 校验凭据、有效期与撤销状态
    A-->>F: Authentication(认证信息)
    F->>Z: 身份、租户、动作、资源
    Z-->>W: 允许或拒绝
    W-->>U: 业务结果
    Note over F,W: 异步执行只传播必要快照,结束必须清理

图解读: 网关与服务内安全是分层防线,不是二选一;认证成功也不代表对任意资源有权。前提是下游只信任经过鉴别的网关信息且保留独立校验能力;结论是身份、租户和链路上下文必须同任务绑定并有清理证据。

风险失败机制首要证据止血修复与验证
网关已验签但下游越权只校验角色,不校验资源归属主体、租户、资源、策略版本关闭高风险写入口资源级授权与负向用例
异步任务丢身份新线程没有 SecurityContext(安全上下文)提交线程与执行线程上下文降级到服务身份或拒绝显式快照、最小权限
线程池身份串号上一任务上下文未清理线程名、前后任务主体停止池并轮换实例包装执行器并在最终块清理
令牌过期仍被接受缓存或时钟策略错误签发、过期、时钟、密钥版本缩短入口、撤销高危令牌密钥轮换与边界测试
内部伪造身份头服务无可信来源判断网络入口、签名头、调用方身份阻断旁路网络双向认证或服务签名
租户上下文缺失查询未带租户约束请求、数据源、SQL(结构化查询语言)暂停批量写强制租户条件与跨租户测试

数据演绎 4:上下文泄漏如何造成低概率越权。 E3(演练设计)中,一个 20 线程的共享池每秒处理 200 个任务,若异常路径有 0.1% 未清理 SecurityContext(安全上下文),10 分钟约执行 200 × 600 = 120,000 个任务,理论产生 120 次残留机会。残留不等于每次越权,但只要后续任务错误读取旧身份,风险就不可接受。修复后用固定 20 线程交替运行两个租户各 50,000 次,要求主体、租户、TraceId(链路标识)和 MDC(映射诊断上下文)在任务前后均可证明隔离,任何一次串号都判失败。

失败注入、证据链与项目落地: E3(演练设计)注入过期令牌、错误租户、网关旁路、权限变更缓存延迟、异步线程丢上下文与异常路径未清理。日志只记录令牌摘要,不记录完整敏感凭据;串联请求号、主体、租户、资源、动作、策略版本、网关判定、服务判定、线程与审计结果。止血先关闭高风险管理和资金入口、阻断旁路、撤销受影响凭据并轮换污染实例;修复资源级授权、可信服务身份和上下文包装器。验证覆盖允许、拒绝、过期、撤销、跨租户和并发复用,并核对支付回调只允许受信渠道身份、Runner(执行器)只获得任务所需最小权限。复盘补充负向测试与权限变更生效目标。机制为 E2(已有材料映射),生产越权事实为 E0(待核对)。参考 Spring Security(安全框架)官方架构文档Spring Security(安全框架)官方授权文档

热门面试题

  1. 问题(基础题):认证和授权有什么区别?

    • 考点:主体可信度、动作和资源裁决。
    • 回答思路:先回答是谁,再回答能做什么。
    • 详细答案:认证验证凭据并建立可信主体,授权结合主体、权限、租户、动作和具体资源决定是否允许。认证成功只说明身份成立,不表示能访问所有订单或支付单;多租户系统尤其要在资源级检查归属,不能只看一个粗粒度角色。
    • 进阶追问:管理员角色是否可以跳过资源级校验?
    • 进阶回答:除非业务明确授予且审计,否则仍应按管理范围、租户和动作约束;高权限更需要最小授权和完整审计。
  2. 问题(原理题):为什么网关鉴权后服务内仍要授权?

    • 考点:纵深防御、内部旁路和业务资源语义。
    • 回答思路:区分入口通用能力与服务拥有的业务事实。
    • 详细答案:网关适合统一验签、限流和粗粒度路由,但通常不知道订单归属、仓库范围和支付状态等细粒度事实。内部调用也可能绕过公共入口。服务必须确认请求来源可信,并在靠近数据的位置执行资源级授权,避免网关规则与业务状态脱节。
    • 进阶追问:服务间调用都用一个超级账号可以吗?
    • 进阶回答:不可以,会失去最小权限、责任主体和撤销能力;应使用可识别服务身份,并按调用目的授予有限权限。
  3. 问题(项目题):异步导出如何防止下载到其他租户数据?

    • 考点:提交时授权、执行时约束、条件快照和下载再校验。
    • 回答思路:把任务创建、执行、文件交付三段分别设安全边界。
    • 详细答案:创建任务时校验用户对筛选条件和字段的权限,持久化不可变租户、主体、条件摘要与策略版本;执行器使用最小服务身份,并强制所有查询携带任务租户,不直接复用易过期的用户线程上下文。下载时再次校验当前用户与任务归属,使用短期签名地址并记录审计。
    • 进阶追问:用户在任务执行期间被撤权怎么办?
    • 进阶回答:策略要明确是按提交时快照继续还是执行/下载时重新校验;高敏数据通常在执行和下载时重校验并可取消已生成文件。

5. MyBatis(持久层框架)会话、缓存与批处理失败

5.1 会话作用域必须服从事务与线程边界

Mapper(映射器)接口由代理把方法定位到 MappedStatement(映射语句),经 Executor(执行器)、参数处理、Statement(数据库语句)和结果映射访问数据库。SqlSession(数据库会话)封装连接、执行器与一级缓存,不是可跨线程共享的业务单例;在 Spring(Java 应用框架)集成中,模板按当前事务获取和释放会话,让同一事务内操作复用受管资源。一级缓存主要避免同会话重复查询,但若同事务中存在框架外更新或错误缓存范围,就可能读到旧对象;二级缓存跨会话,失效和可见性更复杂,不应承担支付或库存的权威事实。Batch(批处理)执行器会延迟发送或汇总结果,异常可能在刷新时才暴露,因此每批必须有稳定输入范围、返回行数校验、明确提交点与可恢复检查点。

flowchart LR
    A[Mapper(映射器)代理] --> B[MappedStatement(映射语句)]
    B --> C{当前事务是否绑定会话}
    C -- 是 --> D[复用受管 SqlSession(数据库会话)]
    C -- 否 --> E[创建短生命周期会话]
    D --> F[Executor(执行器)]
    E --> F
    F --> G{普通、复用或 Batch(批处理)}
    G --> H[参数绑定与数据库执行]
    H --> I[结果映射与一级缓存]
    I --> J{提交、回滚或刷新批次}
    J --> K[清理会话与线程资源]

图解读: 会话由事务和调用生命周期拥有,批处理的真实错误边界在刷新与提交处。前提是使用受支持的 Spring(Java 应用框架)集成方式;结论是不能把 SqlSession(数据库会话)放进共享字段,也不能用缓存命中代替数据库不变量校验。

失败类型机制证据止血修复与验证
跨线程共享会话连接、游标和一级缓存互相污染会话、线程、连接标识停止共享执行器每任务独立受管会话
一级缓存陈旧同会话外部更新不可见查询顺序、缓存作用域、版本列对关键读清缓存或重新查询条件更新与版本验证
二级缓存错读跨会话失效晚于业务变化缓存键、命名空间、提交时间关闭高风险缓存权威读回数据库
Batch(批处理)延迟报错刷新前不知道逐条结果批次号、输入范围、更新计数暂停后续批次小批、校验、检查点
动态 SQL(结构化查询语言)越权租户或状态条件漏拼最终 SQL(结构化查询语言)、参数、主体暂停批量写强制条件与负向测试
插件改写异常拦截顺序或分页改写错误插件链、改写前后语句禁用可疑插件兼容矩阵与语义回放

数据演绎 5:批大小如何同时影响内存与恢复。 E3(演练设计)中,需要更新 120,000 条库存投影,每条参数和缓存对象平均占 1.5KB(千字节)。若单批 20,000 条,仅对象量约 20,000 × 1.5KB = 30,000KB,未计驱动、语句和结果;第 19,000 条失败时,定位与重放范围也接近整批。改为 1,000 条一批,对象量约 1,500KB,共 120 个检查点。批次增多会增加往返与提交成本,因此应按内存水位、锁时长、日志量和恢复时间做阶梯压测,而不是追求单批最大。

失败注入、证据链与项目落地: E3(演练设计)注入同会话外部更新、两个线程共享会话、第 751 条唯一约束冲突、批次刷新超时、插件改写丢租户条件和提交后响应丢失。记录任务号、批次号、输入主键范围、线程、会话、连接、事务、最终 SQL(结构化查询语言)、参数摘要、更新计数、提交点和业务版本。止血暂停问题批次、隔离插件版本、冻结受影响租户写入;修复为受管会话、稳定主键分页、小批提交、版本条件和逐批守恒。验证用 WMS(仓储管理系统)库存 期初 + 入库 - 冻结 - 出库 + 释放 = 期末 对账,并对支付分录检查借贷平衡。复盘建立批次水位、零更新与多更新告警。机制为 E2(已有材料映射),生产批大小和影响为 E0(待核对)。参考 MyBatis(持久层框架)官方 Java(编程语言) API(应用程序接口)文档MyBatis-Spring(持久层框架集成模块)官方事务文档

热门面试题

  1. 问题(基础题):为什么 SqlSession(数据库会话)不能跨线程共享?

    • 考点:连接、执行器、游标、一级缓存与生命周期。
    • 回答思路:说明会话含有可变运行状态,再给受管替代方案。
    • 详细答案:SqlSession(数据库会话)持有执行器、连接和一级缓存等状态,并未为并发调用设计。两个线程共享时,提交、回滚、关闭和缓存内容可能相互影响。应让每个事务或任务通过模板获取受管会话,由框架按线程和事务绑定并在结束时释放。
    • 进阶追问:Mapper(映射器)代理是单例,为什么却能并发使用?
    • 进阶回答:代理本身不保存某个固定会话,而是在每次调用时从受管上下文获取合适会话;安全的是代理委托模式,不是共享 SqlSession(数据库会话)。
  2. 问题(原理题):MyBatis(持久层框架)一级缓存为什么可能读到旧数据?

    • 考点:会话范围、查询键、更新来源与一致性边界。
    • 回答思路:用同会话两次查询中间发生外部更新解释。
    • 详细答案:一级缓存位于会话内,相同查询条件可能直接返回先前结果。如果中间更新由另一会话、存储过程或框架外路径完成,当前会话未必知道需要清理。关键裁决应使用版本条件、当前读或重新查询数据库,不能把缓存对象当作支付与库存权威状态。
    • 进阶追问:把一级缓存范围改小是否解决所有一致性问题?
    • 进阶回答:只能减少会话内陈旧窗口,无法替代事务隔离、数据库锁、版本条件和跨系统对账。
  3. 问题(项目题):批量库存修复第 751 条失败,如何判断前 750 条是否生效?

    • 考点:批刷新、事务提交、检查点与业务对账。
    • 回答思路:先判断刷新和提交边界,再按批次与流水核验。
    • 详细答案:固定批次号、事务标识和输入主键范围,检查异常发生在参数组装、刷新还是提交;若同一事务明确回滚,前 750 条不应提交,仍要查询验证。若分批提交,则以最后成功检查点、更新计数和库存流水为准。未知时不能直接从第 751 条继续,应按业务键读取当前版本并幂等重放。
    • 进阶追问:驱动返回成功计数就能证明业务正确吗?
    • 进阶回答:不能,计数只说明受影响行,还要核对租户、版本、数量守恒和是否命中正确记录。

6. 支付回调、异步任务与 Runner(执行器)恢复闭环

6.1 从入口安全到最终业务不变量

支付回调与 Runner(执行器)任务把前五节串成一条运行链。回调入口先保留原始请求摘要、校验渠道身份与签名,再用渠道事件号和支付意图键幂等;服务必须通过代理进入短本地事务,条件推进支付状态、写不可变账务分录和 Outbox(发件箱),提交后再通知订单或下游。响应超时只代表调用方没拿到确定结果,必须按原键查证。Runner(执行器)从持久任务表按租约和版本领取,在独立线程中重建 TraceId(链路标识)、MDC(映射诊断上下文)和最小安全身份,每个检查点使用短事务;失租、停机或重试时依靠 Fencing Token(栅栏令牌)、幂等和状态机拒绝旧执行者写入。二者共同底线是:入口可重复,状态不可倒退,外部副作用可查证,恢复可对账。

stateDiagram-v2
    [*] --> 已接收
    已接收 --> 已拒绝: 验签或授权失败
    已接收 --> 处理中: 幂等键首次建立
    处理中 --> 本地已提交: 状态、分录、Outbox(发件箱)同事务
    处理中 --> 未知: 提交或外部响应超时
    未知 --> 本地已提交: 按原键查证成功
    未知 --> 待人工: 超过查证预算
    本地已提交 --> 异步处理中: Runner(执行器)按租约领取
    异步处理中 --> 已完成: 幂等副作用与检查点完成
    异步处理中 --> 待重试: 瞬时失败且预算未耗尽
    异步处理中 --> 待人工: 永久失败、失租或预算耗尽
    待重试 --> 异步处理中
    已拒绝 --> [*]
    已完成 --> [*]

图解读: 未知态不是失败态,待重试也不是无限循环;每个迁移都需要稳定业务键、前置状态和证据。前提是状态机拒绝倒退且旧租约不能继续写;结论是支付回调返回成功、队列清空或任务线程结束都不是业务闭环的充分证据。

链路阶段业务不变量失败信号止血动作恢复验收
回调入口未验签数据不得改状态签名失败、重放激增限流并隔离原文合法与非法样本分流正确
幂等与状态机同一支付意图只生效一次唯一冲突、状态倒退保持原键并停止新键重试渠道、支付单和分录一致
本地事务状态、分录、事件共同提交回滚、提交未知保持处理中并查证数据库权威事实明确
任务领取同一租约版本只有一个有效写者重复领取、续租失败停领并冻结旧令牌旧执行者写入被拒绝
异步执行每个检查点可幂等恢复重启、超时、永久错误限制重试并隔离毒任务完成、重试、人工三类守恒
对账复盘资金与任务结果可闭环长龄未知、差异扩大冻结高风险动作差异清零或有责任人接管

数据演绎 6:重复回调叠加任务重试。 E3(演练设计)中,渠道因响应丢失把同一回调发送 5 次,本地提交后 Outbox(发件箱)投递又重复 3 次,Runner(执行器)在一次失租后重试 2 次,理论调用尝试为 5 × 3 × 2 = 30。正确设计仍只产生 1 次支付状态迁移、1 组借贷分录和 1 个有效履约副作用;其余应被回调键、事件键和任务检查点分别命中幂等。验收式为 30 次尝试 = 1 次首次生效 + 29 次幂等命中或合法拒绝,并逐层记录命中原因,不能只看最终余额碰巧正确。

失败注入、证据链与项目落地: E3(演练设计)组合注入回调重放、签名错误、事务提交后断连、Outbox(发件箱)投递重复、Runner(执行器)失租、线程池拒绝、执行中重启和下游超时。证据串联渠道事件号、支付请求号、账务分录、事件号、任务号、租约版本、线程、TraceId(链路标识)、状态迁移和外部回执。止血先保住验签、幂等、账务与任务状态库,停领新任务、关闭立即重试并把未知态隔离;修复后按“回调样本、事件样本、任务样本”三级影子执行,再分档重放。验证要求借贷平衡、支付状态单调、同任务只有新令牌可写、异步副作用不重复,对账差异清零或进入有责任人的人工队列。复盘记录响应为何丢失、何处缺少唯一键、为何失租写入未被拦截。方法为 E2(已有材料映射),事故次数与真实收益为 E0(待核对)。参考 Spring(Java 应用框架) Framework(Spring 核心框架)官方事务绑定事件文档Spring(Java 应用框架) Framework(Spring 核心框架)官方任务执行文档

热门面试题

  1. 问题(基础题):支付回调为什么必须先验签再幂等?

    • 考点:来源真实性、重放与幂等记录污染。
    • 回答思路:先确认消息可信,再判断可信消息是否处理过。
    • 详细答案:幂等只能抑制重复,不能证明请求来自合法渠道。若先写幂等记录,攻击者可用伪造事件号占位,使后续合法回调被误判重复。应先按渠道协议校验签名、时间和必要的重放窗口,再以渠道事件号和支付意图键做幂等与状态裁决。
    • 进阶追问:验签失败的请求可以直接丢弃吗?
    • 进阶回答:业务状态不得更新,但应以脱敏摘要、来源和失败原因留审计并限流,既支持安全调查又避免记录敏感凭据。
  2. 问题(原理题):Runner(执行器)为什么需要 Fencing Token(栅栏令牌)而不只需要租约?

    • 考点:暂停执行者、租约过期与旧写入。
    • 回答思路:用旧执行者暂停后恢复的时间线解释。
    • 详细答案:旧执行者可能因长暂停错过续租,租约已转给新实例,但旧实例恢复后并不知道自己失权,仍可能写结果。单调递增的 Fencing Token(栅栏令牌)随每次领取传到权威存储,存储拒绝小于当前版本的写入,才能把所有权变化落实到数据层。
    • 进阶追问:任务本身幂等是否可以省掉栅栏令牌?
    • 进阶回答:不能完全替代;幂等处理重复意图,栅栏阻止过期所有者覆盖新状态,二者解决的时间竞争不同。
  3. 问题(项目题):支付回调已成功,Runner(执行器)履约任务却长期失败,如何收敛?

    • 考点:资金事实不倒退、错误分类、重试预算和人工闭环。
    • 回答思路:保持支付成功事实,单独恢复履约副作用。
    • 详细答案:先确认支付单与账务分录完整,不因履约失败回退资金事实;按任务号区分瞬时依赖故障、永久参数错误和未知外部结果。瞬时故障退避重试,永久错误隔离,未知结果复用原请求号查证。超过时效转人工并冻结可能重复的外部动作,修复后从原事件和检查点限速重放,最后对账订单、履约单和外部回执。
    • 进阶追问:履约失败是否应自动退款?
    • 进阶回答:不能直接联动,退款是新的资金动作,必须按业务政策、履约事实和人工责任发起独立且可审计的补偿流程。

7. 综合口述题

  1. 问题:Spring Boot(快速开发框架)发布后一直卡在启动阶段,如何定位并恢复?

    • 考点:启动阶段、首个根异常、线程与依赖证据、就绪门禁和发布恢复。
    • 回答思路:先阻断坏实例接流量,再按生命周期缩小阶段,用对照和失败注入完成闭环。
    • 事实等级:启动机制与排查路径为 E2(已有材料映射),20 个实例等量级为 E3(演练设计),具体事故根因与恢复时长为 E0(待核对)。
    • 详细答案:启动故障先按环境、定义注册、实例化、初始化、服务器绑定和就绪门禁分段,再用首个根异常、线程栈与依赖水位证伪;恢复必须在业务就绪验证后分档接流量。
    • 进阶追问:如果回退版本仍然启动缓慢,下一步优先查什么?
    • 进阶回答:优先查两个版本共享的配置、数据库迁移、外部依赖和基础设施变化,用单实例隔离对照,不再把应用提交当唯一变量。
    • 口述答案:我先确认“卡住”是进程未创建、上下文刷新未完成、端口未绑定,还是端口已开但 Readiness(就绪状态)未通过,并立即暂停后续批次,让未就绪实例保持摘流量,避免滚动发布同时耗尽连接。证据按同一时间线保存提交版本、镜像、配置摘要、环境属性来源、条件评估、首个根异常、线程栈、连接池、数据库锁、端口和生命周期事件;最后一层上下文异常只作入口,不当根因。竞争假设包括配置绑定失败、对象定义冲突、构造器循环、初始化远程调用超时、数据库迁移阻塞、Runner(执行器)预热无界以及端口占用。我会用上一版本、单实例、相同配置和隔离依赖做对照,线程栈若长期停在同一远程调用,就同时核对依赖延迟与超时配置,而不是盲目提高启动超时。止血优先回退配置或版本、限制同时启动数、跳过可延后的非关键预热;关键迁移未完成则不强行开放流量。修复后把远程操作移出构造关键路径,增加有限超时、配置契约和业务就绪门禁。验证注入配置缺失、数据库延迟、端口冲突与初始化异常,按 10%、30%、60%、100% 分档发布,要求支付验签、库存查询、任务续租和健康入口都成功。复盘记录为什么首个信号没被告警、为何发布未自动暂停以及回退耗时,真实影响没有原始发布记录时保持 E0(待核对)。启动证据包还要保留到下一次演练,确认同类故障能在开放流量前被门禁自动识别并阻断,同时核对回退路径仍可用。
    • 追问 1:能否直接增加启动超时? 直接回答:只能作为短期观察手段;若根因是死等或连接风暴,延长超时会扩大资源占用。
    • 追问 2:端口可访问是否代表启动成功? 直接回答:不代表,还要等关键对象、依赖和业务就绪门禁通过。
    • 追问 3:初始化失败后应该自动无限重启吗? 直接回答:不应该,无限重启会放大依赖压力,应有退避、总预算和发布暂停条件。
    • 追问 4:怎样证明恢复没有掩盖配置问题? 直接回答:在同版本隔离环境重放缺失配置,并验证启动前校验能稳定拒绝。
    • 详细章节:Spring Boot(快速开发框架)启动、自动配置与运行生命周期
    • Spring Boot(快速开发框架)官方启动文档
  2. 问题:Bean(对象实例)可以创建,但事务和切面完全不生效,如何判断是生命周期还是代理问题?

    • 考点:对象来源、后置处理、代理创建时机、早期引用和调用入口。
    • 回答思路:先证明运行时对象身份,再沿定义、创建、代理、注入和调用逐层比对。
    • 事实等级:生命周期与代理机制为 E2(已有材料映射),复现调用为 E3(演练设计),生产对象来源与影响范围为 E0(待核对)。
    • 详细答案:对象能创建只证明生命周期走到实例阶段,切面生效还要求后置处理器形成代理且调用方持有最终代理;应同时核对对象来源、代理链、注入身份和实际入口。
    • 进阶追问:同一类型有两个 Bean(对象实例),为什么只有一个事务生效?
    • 进阶回答:两个定义可能来自不同容器、作用域或创建路径,调用方也可能注入了原对象;必须按对象标识和定义来源分别核对,不能按类型名合并判断。
    • 口述答案:我不会先修改切点表达式,而是固定失效对象的类名、容器、对象标识、注入位置、调用入口、线程和发布时间,确认调用方拿到的是容器最终代理、早期引用、原始目标对象还是手工 new 的实例。随后检查 BeanDefinition(Bean 定义)来源、作用域、BeanPostProcessor(Bean 后置处理器)是否注册、代理创建日志、运行时类型、拦截器链与事务属性匹配结果;同名对象、父子容器和过早实例化也要纳入假设。若对象在后置处理器注册前被某个工厂级扩展提前创建,它可能绕过代理;若循环依赖注入了不一致的早期引用,不同调用方可能拿到不同身份;若方法由 this 自调用,则外部对象虽是代理,内部调用仍绕过拦截器。止血是暂停依赖该切面的高风险写入口,回退引入提前创建或对象来源变化的版本,不用临时手工提交掩盖事务缺失。修复优先消除手工对象、重新划分循环职责、让协作方只注入稳定代理,并以独立 Bean(对象实例)暴露事务入口。验证用受控异常比较代理入口、自调用和手工对象三条路径,核对事务开启、提交/回滚、审计切面和数据库最终状态;再扫描容器中同类型对象与调用方注入结果。复盘补充运行时对象身份断言、代理边界集成测试和禁止关键对象提前实例化的评审项。没有生产调用图时只给 E0(待核对)结论。还要抽查所有注入点,确认调用方没有缓存旧对象,销毁阶段也只处理同一最终代理身份。
    • 追问 1:运行时类型带代理标记就一定生效吗? 直接回答:不一定,还要确认调用方法命中切点、入口经过该对象且拦截器顺序正确。
    • 追问 2:循环依赖一定能靠三级缓存解决吗? 直接回答:不能,构造器环和某些代理时序不可解,即使能启动也可能暴露设计耦合。
    • 追问 3:把方法改成公开方法就足够吗? 直接回答:不一定,仍取决于代理方式、调用入口和事务属性解析。
    • 追问 4:为什么不直接暴露当前代理调用自己? 直接回答:会引入隐藏容器耦合和脆弱调用图,独立职责边界更清晰可测。
    • 详细章节:IoC(控制反转)容器、Bean(对象实例)生命周期与循环依赖
    • Spring(Java 应用框架) Framework(Spring 核心框架)官方容器扩展点文档
  3. 问题:同类自调用导致事务失效,线上已经出现部分数据不一致,如何止血和修复?

    • 考点:代理绕过、异常传播、影响面、存量裁决和幂等回放。
    • 回答思路:先冻结继续写入,用运行时证据证明绕过,再修调用边界并恢复存量。
    • 事实等级:代理失效机制与恢复方法为 E2(已有材料映射),故障注入数据为 E3(演练设计),真实不一致记录数为 E0(待核对)。
    • 详细答案:先暂停产生差异的入口,证明内部调用绕过代理,再把事务职责拆到独立 Bean(对象实例);历史数据按支付、账务或库存权威流水分类查证并使用原幂等键恢复。
    • 进阶追问:如果自调用期间既有提交又有回滚,如何确定重放起点?
    • 进阶回答:不能按时间或循环下标猜测,应以每个业务键的最终状态、唯一流水和外部回执分类,未知项先查证,重放只处理有证据缺失的意图。
    • 口述答案:我先暂停受影响的自动回调、批量补偿或任务重放,保留查询和人工处置通道,固定版本、方法入口、业务键和时间窗,避免新写继续扩大差异。证据链从运行时对象和调用栈开始:外部入口是否经过 AOP(面向切面编程)代理,内部方法是否由 this 调用,事务拦截器是否出现,线程是否绑定连接,异常有没有被捕获,最终是提交、回滚还是未知。确认自调用只是根因后,还要按业务不变量恢复存量:支付场景对齐渠道状态、支付单、不可变账务分录和 Outbox(发件箱);库存场景对齐订单行、冻结、扣减、释放和唯一流水。结果分为有证据成功、有证据失败、未知三类,未知按原业务键查证,禁止换键重试。代码修复优先把内部事务方法拆到独立 Bean(对象实例),由外部代理入口调用,并明确受检异常、运行时异常和失败码的回滚合同;如果需要独立事务,还要评估连接池和外层提交顺序。验证先对成功、异常、进程强杀和重复请求做故障注入,确认拦截器链和数据库结果一致,再用代表样本影子执行,最后按小批次重放。每批核对成功、幂等命中、合法拒绝和再次隔离守恒,出现新差异就停止。复盘记录为何单元测试绕过代理、为什么监控只看异常率,以及后续代理边界测试和存量对账负责人;无原始事故证据时不宣称已修复全部历史数据。恢复后继续观察完整对账窗口,确认迟到回调与补偿任务没有再次打开已经闭合的差异,并登记差异关闭人与复核时间。
    • 追问 1:给内部方法再加一个事务注解有用吗? 直接回答:没有,调用仍未经过代理,注解不会被事务拦截器读取。
    • 追问 2:可以全量重放历史请求吗? 直接回答:不可以,必须先查证、保持原幂等键并分批恢复。
    • 追问 3:事务日志显示回滚就无需对账吗? 直接回答:不够,外部副作用和框架外写入仍需按业务事实核对。
    • 追问 4:如何防止再次出现? 直接回答:增加经容器代理调用的集成测试,并在架构评审中显式检查自调用和对象来源。
    • 详细章节:AOP(面向切面编程)代理链与扩展机制
    • Spring(Java 应用框架) Framework(Spring 核心框架)官方代理机制文档
  4. 问题:大量使用 REQUIRES_NEW(总是新事务)后连接池耗尽,如何证明和治理?

    • 考点:外层暂停、双连接需求、长事务、超时反馈和容量验证。
    • 回答思路:用调用树与连接时间线证明资源叠加,再按业务语义减少独立事务。
    • 事实等级:传播与连接占用机制为 E2(已有材料映射),并发和池大小为 E3(演练设计),生产耗尽原因和收益为 E0(待核对)。
    • 详细答案:REQUIRES_NEW(总是新事务)可能在外层资源仍占用时申请内层连接;应以嵌套调用树、连接借还和数据库会话证明,再按业务语义减少独立事务而非只扩池。
    • 进阶追问:为什么暂停外层事务不能立即归还外层连接?
    • 进阶回答:暂停只切换当前事务上下文,外层仍需保留资源以便内层结束后恢复并决定提交或回滚,所以资源占用通常持续到外层完成。
    • 口述答案:我先固定连接池开始等待的时间、版本、接口、线程、事务名和数据库实例,区分是泄漏、慢查询、锁等待还是 REQUIRES_NEW(总是新事务)把外层连接暂停后又申请内层连接。证据需要把请求调用树、外层和内层事务标识、连接借出归还时间、池活跃与等待、数据库会话、锁、提交耗时和超时重试放到同一时间轴。E3(演练设计)中若 80 个并发外层事务各持有 1 个连接,随后都进入独立内层,而池上限为 100,最多只有 20 个内层可立即获得连接,其余 60 个等待;等待拉长外层持有时间,入口重试又形成正反馈。止血先限制问题接口和批处理并发、关闭立即重试、暂停非关键审计或通知独立事务,保护支付和库存主链路;不能只把池扩到数据库承载之外。修复时逐项判断内层数据是否真的允许脱离主业务提交:审计或通知可改为同事务 Outbox(发件箱),必须独立的补偿则缩短外层、移出远程调用并给独立事务设置预算。验证用阶梯并发注入内层延迟,观察有效完成率、连接等待、事务年龄和数据库锁,而不是只看池不再报错;同时核对外层回滚后独立记录语义仍正确。分档放量后持续一个完整压力窗口,出现等待拐点立即回退。复盘建立传播方式清单、连接容量公式和代码评审门禁,所有真实阈值必须由监控与配置证明。容量评审还要加入单实例失效情形,确认剩余实例不会因接管流量再次跨过连接等待拐点,并保留原始压测采样。
    • 追问 1:把连接池扩大一倍能解决吗? 直接回答:可能把瓶颈推给数据库并增加锁竞争,只能在数据库余量和压测证据支持时调整。
    • 追问 2:REQUIRES_NEW(总是新事务)是否一定同时占两个连接? 直接回答:在常见本地事务实现中外层资源被暂停但仍占用,内层需独立资源,具体要用事务管理器和连接证据确认。
    • 追问 3:审计日志为什么适合 Outbox(发件箱)? 直接回答:业务与待审计事件同事务落库,后续异步投递可恢复,避免嵌套连接峰值。
    • 追问 4:如何设置回退条件? 直接回答:连接等待、最老事务、错误率或业务完成率越界就退回上一并发档。
    • 详细章节:事务传播、失效与一致性边界
    • Spring(Java 应用框架) Framework(Spring 核心框架)官方事务传播文档
  5. 问题:业务方法抛过异常却仍然提交,如何排查回滚规则和异常边界?

    • 考点:异常类型、捕获转换、回滚标记、代理返回和最终状态。
    • 回答思路:从拦截器实际看见的退出方式出发,而不是从日志中的异常字样出发。
    • 事实等级:回滚规则为 E2(已有材料映射),异常组合测试为 E3(演练设计),生产提交原因和影响为 E0(待核对)。
    • 详细答案:事务拦截器按代理边界最终看到的返回或异常决定结果;应核对异常类型、捕获转换、回滚标记和连接提交,再用数据库最终状态验证,不能以错误日志代替回滚证据。
    • 进阶追问:提交阶段才抛异常时,业务状态应如何表达?
    • 进阶回答:客户端应进入未知态,使用原业务键查询数据库和外部事实;不能仅依据异常重试,更不能先假定回滚后创建新意图。
    • 口述答案:我先固定业务键、方法签名、事务属性、异常原始类型、捕获位置、返回值和提交时间,确认“抛过异常”是内部日志记录后被捕获、转换成受检异常、包装后丢失类型、异步线程异常,还是确实穿过代理返回给事务拦截器。声明式事务通常根据代理边界看到的最终退出方式决定提交或回滚,所以内部出现错误日志并不等于拦截器收到异常;返回失败码也可能被当成正常返回。证据链包括调用栈、异常因果链、事务活动与回滚标记、事务同步回调、连接提交/回滚、数据库目标行和业务流水。止血时暂停会制造不可逆副作用的入口,关闭调用方无脑重试,按支付、库存或任务业务键分类已提交、已回滚和未知,未知必须读取权威状态。修复应明确异常合同:需要回滚的受检异常配置规则或转换为稳定领域异常后继续抛出;若上层必须捕获,则显式标记回滚并避免继续执行外部副作用。不能把所有异常统一回滚而不评估业务,因为某些校验失败发生在写入前,某些独立审计已允许提交。验证覆盖运行时异常、受检异常、嵌套捕获、提交阶段异常和异步异常,核对日志、事务结果与业务状态一致;再注入重复请求确认幂等。复盘记录日志为何误导、测试为何只断言异常未断言数据库,以及异常分类、监控和回滚规则的版本治理。没有数据库前后快照时,历史影响只能标 E0(待核对)。发布门禁还要保存异常类型到回滚结果的契约快照,防止依赖升级后默认规则悄然变化,并由数据库断言复核。
    • 追问 1:配置所有异常都回滚是否最安全? 直接回答:不一定,会改变既有业务合同;应按异常语义和已发生副作用明确规则。
    • 追问 2:捕获后重新抛出新异常可以吗? 直接回答:可以,但要保留原始因果链并确保新类型命中回滚规则。
    • 追问 3:异步线程异常能让调用方事务回滚吗? 直接回答:通常不能,调用方可能已提交;异步任务要有独立事务与失败状态。
    • 追问 4:怎样证明修复有效? 直接回答:在真实代理入口注入各类异常,同时断言事务回调与数据库最终状态。
    • 详细章节:事务传播、失效与一致性边界
    • Spring(Java 应用框架) Framework(Spring 核心框架)官方回滚规则文档
  6. 问题:事务方法里提交 @Async(异步注解)任务,为什么会出现查不到数据或部分提交?

    • 考点:线程事务边界、提交时序、任务持久化、幂等与失败恢复。
    • 回答思路:用调用线程和执行线程两条时间线证明竞态,再把临时任务改成可恢复事实。
    • 事实等级:线程绑定事务机制为 E2(已有材料映射),延迟和并发为 E3(演练设计),生产发生频率为 E0(待核对)。
    • 详细答案:异步线程不继承调用方活动事务,任务可能早于提交读取或晚于回滚产生副作用;关键意图应随本地事务持久化,再由独立事务按业务键领取和恢复。
    • 进阶追问:只把异步提交放到事务完成回调里,还缺哪一个失败窗口?
    • 进阶回答:本地提交完成后、内存回调持久化任务前进程仍可能强杀;关键任务必须先有可恢复的 Outbox(发件箱)或任务记录。
    • 口述答案:我先固定请求号、业务键、提交线程、执行线程、事务标识、任务入队时间和数据库提交时间,判断异步任务是在外层提交前启动、提交后启动,还是外层最终回滚但任务已经产生副作用。原线程的连接和事务不会自动传到线程池,新线程可能在外层提交前查询,因此看不到未提交数据;也可能外层回滚后仍按参数调用外部系统,形成孤儿副作用。证据要串联事务同步回调、线程池队列、任务开始结束、数据库行版本、异常和外部请求号,不能只凭两条日志先后判断,因为不同线程时钟与缓冲会误导。止血先暂停该异步入口和自动重试,保护在线线程池,把已启动任务按业务键标记待查证;外部结果未知时复用原请求号查单。修复优先在本地事务内持久化任务或 Outbox(发件箱),提交成功后由独立 Runner(执行器)领取;异步入口自己开启短事务,读取已提交事实,使用幂等键和状态机推进。若只需提交后通知,可使用事务完成回调,但仍要处理提交后进程强杀造成的丢失,所以关键任务不能只放内存。验证控制时间屏障,覆盖任务先运行、外层回滚、提交后强杀、线程池拒绝和重复领取,要求未提交数据不被消费、已提交任务最终可见、外部副作用至多生效一次。复盘记录为何测试没有控制时序、为何队列深度未告警,并建立持久任务年龄、拒绝率与未知态监控;无原始任务记录时保持 E0(待核对)。还要核对取消与完成竞争,确保迟到执行不能重新打开已取消任务。
    • 追问 1:使用事务完成事件就绝对不丢吗? 直接回答:不绝对,提交后到事件处理持久化前仍可能强杀,关键任务需要持久记录。
    • 追问 2:给异步方法加事务注解有用吗? 直接回答:可建立新线程自己的事务,但不能延长或共享调用方事务。
    • 追问 3:传完整对象给异步线程可以吗? 直接回答:不建议,对象可能陈旧且含懒加载状态,应传稳定标识并重新读取已提交事实。
    • 追问 4:线程池队列清空能证明完成吗? 直接回答:不能,还要核对任务状态、业务数据和外部回执。
    • 详细章节:Spring(Java 应用框架)MVC(网页模型视图控制器)请求、校验、异常与异步
    • Spring(Java 应用框架) Framework(Spring 核心框架)官方异步执行文档
  7. 问题:支付回调处理超时,怎样判断本地事务成功、失败还是未知,并防止重复记账?

    • 考点:验签、幂等、提交未知态、账务分录、Outbox(发件箱)与对账。
    • 回答思路:先承认超时不代表失败,再按同一支付意图逐层查证和恢复。
    • 事实等级:支付事务安全方案为 E2(已有材料映射),重放次数为 E3(演练设计),具体渠道结果与事故规模为 E0(待核对)。
    • 详细答案:支付超时只能判为未知;应按原请求号核对渠道、支付单、不可变分录和 Outbox(发件箱),有证据后再返回原结果、原键重试或进入人工。
    • 进阶追问:本地支付单成功但账务分录缺失时,能否补插一条分录?
    • 进阶回答:不能直接补插,应先确认事务边界和渠道事实,通过独立、可审计且关联原单的补偿动作恢复,避免伪造历史原子提交。
    • 口述答案:我先固定渠道事件号、商户请求号、支付单号、金额、币种、签名摘要、应用实例和时间窗,禁止调用方换新请求号重试。入口先验证渠道身份、签名和必要的重放窗口,再查幂等记录;超时只说明响应未被调用方确认,不能直接改成失败。数据库侧读取支付单、唯一业务键、状态版本、不可变借贷分录和 Outbox(发件箱),应用侧对齐事务开始、提交/回滚、连接断开与事务同步回调,渠道侧按原请求号主动查单。若支付单、金额币种和分录完整一致,就返回原成功结果;若本地明确回滚且渠道未受理,可按原键重试;任一侧无法确认就保持处理中或未知,进入有预算的查证,不能先退款或再扣一次。止血限制异常渠道流量、关闭立即重试,保护验签、账务写和查单能力,并隔离签名失败与格式错误请求。修复保证状态、分录和待投递事件在同一本地事务中提交,状态机拒绝成功回退,消费者按事件和业务键幂等。存量恢复按渠道、支付单、分录、订单和事件五方对账,差异通过独立补偿单处理。验证注入提交前断连、提交后丢响应、重复回调、乱序回调、进程强杀和投递重复,要求同一支付意图只生效一次、借贷平衡且通知最终收敛。复盘记录未知态发现时间、查证时长、人工数量和重试放大,真实收益没有对账证据时保持 E0(待核对)。还要覆盖跨日对账窗口,防止迟到渠道文件把已关闭差异再次暴露而无人接管;人工裁决保留双人复核、原因和关联补偿单。
    • 追问 1:唯一索引冲突是否可以直接返回成功? 直接回答:不能,需读取原记录并核对金额、币种、主体和状态是否是同一意图。
    • 追问 2:为什么超时后不能立即退款? 直接回答:原支付可能仍在成功路径,盲退会增加一个新的未知资金动作。
    • 追问 3:回调返回成功就闭环了吗? 直接回答:没有,还要核对账务、订单、事件投递和渠道对账。
    • 追问 4:补偿为什么使用新业务键? 直接回答:补偿是独立可审计动作,应关联原单但不能冒充原扣款。
    • 详细章节:支付回调、本地事务消息与资金一致性
    • Spring(Java 应用框架) Framework(Spring 核心框架)官方事务绑定事件文档
  8. 问题:接口突然大量出现 401/403,如何区分认证故障、授权故障和业务拒绝?

    • 考点:过滤器链、身份建立、资源授权、策略版本与安全止血。
    • 回答思路:先按响应阶段与主体分桶,再用相同请求的前后版本证据逐项证伪。
    • 事实等级:认证授权机制为 E2(已有材料映射),故障样本为 E3(演练设计),生产拒绝分布和安全影响为 E0(待核对)。
    • 详细答案:先定位拒绝由网关、认证过滤链、方法授权还是业务资源校验产生,再核对凭据、主体、租户、资源与策略版本;止血应回退规则而非关闭安全。
    • 进阶追问:大量 401 与密钥轮换同时发生,如何避免误把攻击流量当发布故障?
    • 进阶回答:按凭据签发时间、密钥版本、来源和实例分桶,对合法样本做受控验证,同时保留异常来源限流,分别观察合法成功率和攻击拒绝率。
    • 口述答案:我先确认状态码由 Gateway(网关)、Spring Security(安全框架)过滤器链、方法安全、业务资源校验还是统一异常映射产生,并按租户、客户端、接口、凭据签发时间、实例和发布版本分桶。401 通常指向凭据缺失、无效或认证未建立,403 通常表示身份已建立但权限不足,不过自定义处理可能改变表现,所以必须看安全事件和调用链。证据包括脱敏令牌摘要、签发与过期时间、密钥版本、时钟、认证提供器结果、SecurityContext(安全上下文)主体、角色、资源归属、策略版本、网关与服务的独立判定;绝不记录完整令牌。竞争假设有密钥轮换不一致、实例时钟偏差、过滤器顺序变化、路径匹配变化、权限缓存陈旧、租户上下文丢失和业务异常被错误映射。止血不是关闭鉴权,而是回退安全配置、隔离错误实例、恢复上一密钥集合,对高风险写保持默认拒绝,并为已验证的关键服务开受限人工通道。修复后用允许、拒绝、过期、撤销、跨租户、网关旁路和权限变更样本做负向回归,确认每个拒绝发生在预期层。分档放量时同时观察认证成功率、授权拒绝原因和业务成功率,避免把攻击流量误当故障。复盘记录策略发布为何未做双版本兼容、告警为何只有总状态码,以及密钥轮换、时钟和资源级授权的演练频率。没有安全日志时不能声称无越权,只能标 E0(待核对)。恢复后再抽样高权限操作审计,确认临时人工通道已撤销且没有遗留扩大授权。
    • 追问 1:可以临时允许所有请求止血吗? 直接回答:不可以,应回退配置或开放最小范围受审计通道,高风险动作默认拒绝。
    • 追问 2403 一定说明角色不足吗? 直接回答:不一定,也可能是租户、资源归属、方法规则或异常映射问题。
    • 追问 3:网关通过为何服务还拒绝? 直接回答:网关只做粗粒度判断,服务还需验证来源和业务资源权限。
    • 追问 4:如何验证密钥轮换安全? 直接回答:在重叠窗口同时验证新旧合法凭据、过期与撤销凭据,并检查所有实例版本一致。
    • 详细章节:Spring Security(安全框架)认证、授权与过滤器链
    • Spring Security(安全框架)官方认证架构文档
  9. 问题:网关转发到异步线程后出现租户串号和 TraceId(链路标识)丢失,如何排查?

    • 考点:可信上下文、线程池复用、显式传播、清理和跨租户验证。
    • 回答思路:区分未传播与残留污染,按请求、任务和线程建立三维证据。
    • 事实等级:上下文传播机制为 E2(已有材料映射),复用次数为 E3(演练设计),生产串号事实与影响数据为 E0(待核对)。
    • 详细答案:上下文为空说明未传播,出现前一任务身份说明未清理;应只传播经过验证的不可变快照,在执行前安装、最终块清理,并让数据层强制校验租户。
    • 进阶追问:任务已落库且租户字段正确,为什么仍可能跨租户?
    • 进阶回答:执行查询、缓存键、临时文件或下载授权仍可能读取线程残留身份;必须验证从任务到数据访问和交付的整条链,而不是只看任务表。
    • 口述答案:我先停止受影响的批量写和高敏导出,保留只读查询并轮换可能污染的执行实例,固定网关请求号、任务号、线程名、主体、租户、TraceId(链路标识)和 MDC(映射诊断上下文)快照。要区分两类问题:新线程没有显式传播,表现为上下文为空;异常路径没有清理,线程复用后读到上一任务,表现为低概率串号。证据从网关验签和可信来源开始,比较进入服务、提交任务、执行任务、数据库查询和结束清理五个时点的上下文,同时核对最终 SQL(结构化查询语言)是否带正确租户条件。不能盲目复制所有线程本地状态,尤其不能传播活动事务连接;只传不可变、经过验证且任务真正需要的主体、租户、链路和语言等快照。止血关闭共享池中的问题装饰器或回退版本,对缺失租户采取默认拒绝,绝不回落到全租户。修复使用统一任务包装器,在执行前安装最小上下文,在最终块无条件清理,并让数据库访问层强制要求租户参数;服务身份与用户身份分开,后台任务记录发起者但按最小服务权限执行。验证让两个租户在固定小线程池中交替执行各 50,000 次,注入异常、取消、超时和线程复用,要求主体、租户、链路和查询条件零串号;随后对已生成文件和写入记录按任务归属做存量审计。复盘记录为什么没有负向并发测试、上下文清理是否可观测以及网关身份头如何防伪。无原始访问审计时,影响范围只能保持 E0(待核对)。文件缓存与临时目录也要按租户复核,避免数据库已隔离但交付层仍串号。
    • 追问 1:使用可继承线程本地变量能解决吗? 直接回答:在线程池复用场景并不可靠,还可能把创建线程时的旧值长期带入。
    • 追问 2:为什么不能传播数据库事务? 直接回答:连接和提交控制属于原线程生命周期,新线程应建立独立事务。
    • 追问 3:上下文为空时默认系统管理员可以吗? 直接回答:不可以,应默认拒绝或使用明确、最小权限的服务身份。
    • 追问 4:TraceId(链路标识)丢失只是观测问题吗? 直接回答:不是,若同一传播组件也承载租户和主体,丢失可能同时暴露安全与数据隔离缺陷。
    • 详细章节:Spring Security(安全框架)认证、授权与过滤器链
    • Spring Security(安全框架)官方并发支持文档
  10. 问题:MyBatis(持久层框架)同一事务内查询到旧数据,如何判断是缓存、隔离级别还是写入未提交?

  • 考点:SqlSession(数据库会话)、一级缓存、事务可见性、外部更新和权威读。
  • 回答思路:固定两次查询之间的更新来源,逐层绕过缓存并比较数据库事实。
  • 事实等级:会话缓存与事务机制为 E2(已有材料映射),复现数据为 E3(演练设计),生产旧读原因和影响为 E0(待核对)。
  • 详细答案:固定两次查询及中间更新的会话、连接和事务,分别绕过一级缓存、回到权威数据源并改变查询类型,即可区分缓存陈旧、快照可见性和未提交写。
  • 进阶追问:清理一级缓存后仍读旧值,下一步如何缩小范围?
  • 进阶回答:核对读写实例、事务隔离与快照、更新是否提交、SQL(结构化查询语言)条件和行版本;若读到副本,还要量化复制水位。
  • 口述答案:我先固定业务键、线程、事务、SqlSession(数据库会话)、连接、两次查询的最终 SQL(结构化查询语言)、参数、结果版本和中间更新来源,确认所谓“同一事务”是否真的由同一事务管理器和连接控制。竞争假设分三组:一级缓存直接返回第一次查询对象;更新由另一个会话、存储过程或框架外路径完成,当前会话不知道清理;数据库隔离级别和查询类型使当前事务看不到其他事务提交;或者写入本身尚未刷新、提交失败或路由到了不同实例。证据要比较缓存命中、会话标识、事务活动、数据库主从角色、行版本和提交时间。止血时对支付、库存等关键裁决关闭高风险二级缓存,必要时在明确代价下清理当前会话缓存并回主库重新读取,但不能把所有查询改成无缓存后就宣布根因。修复根据证据选择统一受管会话、避免框架外更新、缩小会话缓存范围、使用版本条件或在需要时执行当前读;跨系统事实仍靠业务状态机和对账。验证用屏障控制“第一次查询、外部更新、第二次查询”的顺序,分别测试同会话更新、另一会话提交和事务回滚,断言对象版本与数据库状态;再覆盖副本延迟,避免把路由问题误归缓存。项目验收要复算库存流水或支付分录,而不是只比较页面显示。复盘记录缓存为何参与关键裁决、缺少哪些会话与连接标签,并为重要查询增加版本和数据源观测。没有原始事务快照时保持 E0(待核对)。恢复后保留一段缓存命中与版本冲突对照,确认绕过缓存不是唯一正确路径。
  • 追问 1:每次查询前清缓存是否可行? 直接回答:只能规避部分会话陈旧,增加数据库压力且不解决隔离和路由问题。
  • 追问 2:一级缓存是全局共享的吗? 直接回答:不是,主要在 SqlSession(数据库会话)作用域内。
  • 追问 3:读主库能排除所有旧读吗? 直接回答:不能,同事务快照、一级缓存和未提交写仍可能影响结果。
  • 追问 4:库存为什么要用版本条件? 直接回答:版本或数量条件让数据库原子裁决并发变化,避免基于旧对象无条件覆盖。
  • 详细章节:MyBatis(持久层框架)执行、映射、缓存、事务与插件
  • MyBatis(持久层框架)官方 Java(编程语言) API(应用程序接口)文档
  1. 问题:MyBatis(持久层框架)批处理在第 751 条报错,如何判断前面的记录是否生效并安全重跑?
  • 考点:批刷新、提交边界、更新计数、检查点、幂等与业务守恒。
  • 回答思路:先确定错误发生阶段和事务边界,再按稳定范围查证,不按循环下标猜测。
  • 事实等级:批处理机制和恢复方法为 E2(已有材料映射),751 条与批大小为 E3(演练设计),生产已提交数量为 E0(待核对)。
  • 详细答案:第 751 条只是发现点,真实边界取决于刷新与提交;应依据批次号、稳定主键范围、更新计数、事务结果和数据库版本确定检查点,再幂等恢复。
  • 进阶追问:批次中存在永久脏数据时,怎样避免它阻塞全部恢复?
  • 进阶回答:把永久错误连同原输入、业务键和异常隔离,正常记录继续按小批提交;隔离项修复后仍复用原任务身份单独回放。
  • 口述答案:我先暂停后续批次和自动重跑,固定任务号、批次号、输入主键范围、排序规则、线程、SqlSession(数据库会话)、连接、事务和异常位置。第 751 条报错不等于前 750 条已提交,也不等于全部回滚;要判断异常发生在参数组装、驱动批刷新、数据库执行还是提交响应阶段,并查看 Batch(批处理)结果、更新计数、事务回滚标记和数据库最终行版本。若全部在一个本地事务且有明确回滚证据,理论上前序写不应生效,但仍需按业务键查询;若每小批独立提交,则最后成功检查点之前可能已经生效;若提交响应丢失则进入未知,不能从 751 机械续跑。止血保护在线交易连接池,降低修复任务优先级,冻结受影响租户或 SKU(库存单位)的并发修复,防止人工和任务同时覆盖。恢复时使用稳定主键分页和不可变输入快照,每批记录起止键、摘要、事务结果与更新计数,写入带版本条件和任务幂等键;唯一约束、零更新、多更新分别分类,永久数据错误进入隔离,不拖住全批。对未知批次先读取当前版本和业务流水,再决定幂等命中、补写或人工。验证注入第 1、751、最后一条失败、刷新超时、提交后断连和进程强杀,要求检查点可恢复且重复执行不改变结果。WMS(仓储管理系统)场景还要复算期初、入库、冻结、出库、释放与期末,支付场景核对借贷平衡。复盘记录为何批次过大、为何没有零更新告警和恢复脚本演练,真实生效量无数据库证据时保持 E0(待核对)。
  • 追问 1:驱动返回成功计数能证明提交吗? 直接回答:不能,执行成功与事务最终提交是不同阶段,还要查提交证据和数据库状态。
  • 追问 2:从第 751 条继续最省时间吗? 直接回答:风险很高,输入排序可能变化且提交边界未知,应从稳定检查点按业务键幂等恢复。
  • 追问 3:单批越大吞吐越好吗? 直接回答:不一定,过大会增加内存、锁、日志和失败恢复范围,需阶梯压测。
  • 追问 4:零更新为什么要告警? 直接回答:可能表示版本冲突、路由错误或数据已变化,静默跳过会掩盖差异。
  • 详细章节:MyBatis(持久层框架)执行、映射、缓存、事务与插件
  • MyBatis-Spring(持久层框架集成模块)官方事务文档
  1. 问题:Runner(执行器)发生重复执行和旧实例回写,如何用租约、栅栏与事务证据恢复?
  • 考点:领取原子性、租约过期、Fencing Token(栅栏令牌)、幂等和存量裁决。
  • 回答思路:先停领并冻结旧写者,再按任务版本和外部结果逐项查证。
  • 事实等级:调度恢复机制为 E2(已有材料映射),暂停和重试次数为 E3(演练设计),生产重复副作用数量为 E0(待核对)。
  • 详细答案:租约负责限时所有权,Fencing Token(栅栏令牌)让权威存储拒绝旧执行者,幂等处理有效执行者的重复尝试;恢复必须同时核对任务状态和外部回执。
  • 进阶追问:下游系统不能校验 Fencing Token(栅栏令牌)时怎么办?
  • 进阶回答:在本地权威出口先校验令牌并记录唯一外部请求号,下游依靠业务幂等查证;高风险副作用仍需评估无法栅栏的残余窗口。
  • 口述答案:我先停止问题队列领取和无预算重试,保留任务查询与人工接管,固定任务号、业务键、租约持有者、租约到期时间、Fencing Token(栅栏令牌)、线程、事务、实例重启和外部请求号。重复执行常见于领取条件不原子、执行超过租约、长暂停导致续租失败、停机时任务未交接,或旧实例恢复后继续写。证据要串联任务状态迁移、每次领取版本、续租结果、数据库条件更新行数、外部调用回执、检查点和最终写入;只看“两个实例都打印开始”不能证明产生两次副作用。止血在权威存储提高当前令牌并拒绝旧令牌写入,隔离失租实例,对外部结果未知的任务按原请求号查证,不生成新键。修复把领取实现为状态与版本条件更新,租约每次转移生成单调令牌,所有关键写和完成操作校验令牌;任务副作用仍需幂等,因为栅栏只拒绝旧所有者,不能处理同一有效所有者的网络重试。长任务拆成短事务检查点,续租失败立即停止产生新副作用。存量按任务分为未开始、执行中、已完成、未知和永久失败,外部成功但本地未记完成的,读取回执后幂等补状态。验证注入线程长暂停、续租丢包、实例强杀、双实例同时领取和提交后断连,要求旧令牌零成功写、同一业务副作用至多一次、任务最终进入完成、重试或人工之一。复盘记录租约为何短于最坏执行、失租告警为何未停写和人工恢复是否审计;无外部回执时保持 E0(待核对)。还要核对暂停任务与恢复任务的令牌跃迁,防止人工操作重新激活旧所有者。
  • 追问 1:租约时间设得很长能避免重复吗? 直接回答:只能降低过期概率,却延长故障接管时间,仍不能阻止暂停后的旧写。
  • 追问 2:有 Fencing Token(栅栏令牌)就不需要幂等吗? 直接回答:仍需要,网络重试可能由同一有效令牌重复发起。
  • 追问 3:发现两个执行日志就要回滚结果吗? 直接回答:不能,先查权威写入和外部回执,日志开始不等于副作用完成。
  • 追问 4:如何优雅接管长任务? 直接回答:停止领新任务,保存可验证检查点,失租即停写,由新令牌从检查点幂等恢复。
  • 详细章节:Runner(执行器)调度租约、重复执行与优雅停机
  • Spring(Java 应用框架) Framework(Spring 核心框架)官方任务执行文档
  1. 问题:应用滚动发布时异步任务被中断,如何设计优雅停机并证明没有丢任务?
  • 考点:摘流量、停领、在途任务、检查点、强杀边界和发布验证。
  • 回答思路:把网页请求与后台任务分开收敛,用持久状态而不是内存队列证明完成。
  • 事实等级:生命周期和停机方案为 E2(已有材料映射),等待时长与任务量为 E3(演练设计),生产丢失数量为 E0(待核对)。
  • 详细答案:优雅停机按摘流量、停领新任务、有限等待、保存检查点、释放租约和新实例接管执行;是否丢任务只能由持久任务与业务结果证明。
  • 进阶追问:平台强杀时间短于应用停机宽限时,哪一项设计承担兜底?
  • 进阶回答:持久任务、短事务检查点、租约和幂等承担兜底;应用内等待只能优化完成率,不能作为可靠性唯一保证。
  • 口述答案:我先明确停机合同:实例收到终止信号后先把 Readiness(就绪状态)切为不可接流量,等待网关摘除;Runner(执行器)关闭领取开关,不再获得新任务;网页请求、线程池任务和消息处理分别统计在途量。对短任务给有限宽限时间完成并提交,对长任务保存检查点、释放租约并由新实例接管,任何超过期限的强杀都必须能从持久任务或 Outbox(发件箱)恢复。证据链包括终止信号时间、就绪转换、网关连接、领取开关、线程池活跃与队列、任务状态、检查点、事务完成回调、租约和新实例接管记录;“进程正常退出”不能证明业务没丢。止血时暂停滚动批次、延长当前实例摘除间隔但设置上限,对非持久内存任务立即停止新提交并导出可识别清单,不能无限等待拖住发布。修复将关键异步意图先持久化,任务以稳定键和状态机领取,停机钩子只负责停止入口与有限等待,不在销毁阶段发起新的远程工作。数据库事务保持短小,外部调用结果未知时记录待查证。验证在任务执行前、事务中、提交后未记完成、续租前和文件交付前分别强杀实例,要求新实例能识别并恢复,重复执行被幂等或栅栏拒绝;滚动发布按单实例、少量实例到全量推进,观察最老任务年龄、重复率和业务对账。复盘记录终止宽限是否覆盖第 99 百分位任务、编排平台强杀配置与应用是否一致,以及每次发布是否自动抽样查任务守恒。真实丢失量没有任务台账时只能标 E0(待核对)。
  • 追问 1:把停机等待设成无限长最安全吗? 直接回答:不安全,会阻塞发布和故障替换;关键是任务可检查点恢复。
  • 追问 2:线程池队列为空是否说明没有丢任务? 直接回答:不说明,任务可能已取出未完成,需看持久状态和业务结果。
  • 追问 3:销毁回调里补发所有消息可以吗? 直接回答:不可靠,进程可能被强杀;待发事实应在运行期持久化。
  • 追问 4:如何选择停机宽限? 直接回答:依据任务时长分布、检查点能力和平台强杀上限,并通过演练验证。
  • 详细章节:Spring Boot(快速开发框架)启动、自动配置与运行生命周期
  • Spring Boot(快速开发框架)官方优雅停机文档
  1. 问题:如何设计一条安全、幂等、可恢复的支付回调全链路?
  • 考点:认证、状态机、本地事务、MyBatis(持久层框架)、可靠事件、异步和对账。
  • 回答思路:从入口真实性讲到资金不变量,再把每个失败窗口配上证据和恢复动作。
  • 事实等级:方案与不变量为 E2(已有材料映射),重复次数和恢复批次为 E3(演练设计),生产采用情况与效果为 E0(待核对)。
  • 详细答案:安全回调链依次完成可信来源与验签、幂等和状态机、本地事务内支付单/分录/Outbox(发件箱)、独立异步处理、未知态查证和多方对账。
  • 进阶追问:渠道事件号不稳定或可能重复使用时,幂等键怎样设计?
  • 进阶回答:组合商户、渠道、支付意图、事件类型和受控版本形成业务唯一键,同时校验金额币种;原始事件号仍保留作审计而非单独裁决。
  • 口述答案:入口先保存脱敏原文摘要、渠道事件号、商户请求号和接收时间,通过 Spring Security(安全框架)或专用过滤链验证可信来源,再按渠道协议验签、检查时间窗口;未通过的请求只审计不改业务。通过后以渠道事件号和支付意图键做幂等,读取原支付单校验主体、金额、币种和允许的状态迁移。业务必须从容器代理进入短事务,MyBatis(持久层框架)使用受管 SqlSession(数据库会话)执行带版本条件的更新,同时写不可变借贷分录和 Outbox(发件箱);任一步失败整体回滚,异常不得被吞。提交响应丢失时保持未知并按原键查证,绝不换键再记账。事务外由投递器发布事件,Runner(执行器)或消费者在独立线程重建最小安全与 TraceId(链路标识)上下文,按事件和业务键幂等处理订单、履约或通知;外部调用超时复用原请求号查单,永久错误隔离,重试有退避和总预算。证据链贯穿签名摘要、幂等命中、代理与事务、支付单、分录、事件、任务、租约和外部回执。止血时优先保护验签、账务和查单,关闭无预算重试,未知态不自动失败。验证组合注入伪造签名、5 次重复回调、自调用绕过、提交后断连、事件重复、线程池拒绝、Runner(执行器)失租和下游超时,要求一次支付意图仅有一组分录、状态单调、异步副作用至多一次。对账持续比较渠道、支付单、分录、订单和事件,差异清零或进入有责任人的人工队列。复盘记录每个窗口的发现时延、恢复时长和撤销条件,未核验的生产方案保持 E0(待核对)。
  • 追问 1:验签与幂等谁先执行? 直接回答:先验签,避免伪造请求污染幂等记录,再对可信事件去重。
  • 追问 2:本地事务能保证消息一定发送吗? 直接回答:不能,需用 Outbox(发件箱)或等价持久事实弥合提交与发送窗口。
  • 追问 3:回调重复但最终余额正确就够吗? 直接回答:不够,还要验证分录唯一、状态不倒退、通知和履约副作用未重复。
  • 追问 4:未知态多久转失败? 直接回答:不能仅按时间强转,应按渠道时效、查单证据和人工裁决合同处理。
  • 详细章节:支付回调、本地事务消息与资金一致性
  • Spring Security(安全框架)官方架构文档
  1. 问题:一次发布后同时出现启动变慢、支付重复、权限拒绝和任务积压,如何组织跨组件事故处理?
  • 考点:事件指挥、时间线、故障域、竞争假设、分层止血、验证与复盘。
  • 回答思路:先建立共同时间轴与业务优先级,再把症状拆成可证伪分支,避免一个猜测解释全部。
  • 事实等级:事故方法和候选机制为 E2(已有材料映射),组合故障为 E3(演练设计),生产根因、损失和恢复时长为 E0(待核对)。
  • 详细答案:组合事故先建立统一时间线和业务优先级,分层止血后把启动、支付、安全和任务积压拆成竞争假设;回退只提供相关性,仍需独立注入证明因果。
  • 进阶追问:多个症状最终来自同一个连接池,复盘还要分别记录吗?
  • 进阶回答:要分别记录触发、传播和业务影响;共享资源是根因或放大器,支付重复与权限拒绝仍有各自的防线缺口和恢复责任。
  • 口述答案:我先建立单一事件指挥和共同时间轴,固定发布版本、配置、实例、网关、数据库、线程池、渠道和任务队列变化,把资金正确性、跨租户安全放在最高优先级,延迟和吞吐排在其后。立即暂停发布,未就绪实例摘流量,高风险支付和管理写入口限流,关闭无预算回调与任务重试,Runner(执行器)停领新任务,同时保留验签、查单、账务查询和人工处置能力。四类症状不能默认同一根因:启动慢可能是初始化连接风暴;支付重复可能是代理自调用或提交未知后换键重试;401/403 可能是密钥、过滤链或资源授权变化;积压可能是线程池上下文泄漏、数据库连接耗尽或下游变慢。证据链按请求号和任务号关联条件评估、首个根异常、对象与代理、事务和连接、签名与 SecurityContext(安全上下文)、MyBatis(持久层框架)会话、队列年龄、租约和外部回执,并与上一版本做对照。先回退最小可疑变更,若回退同时恢复多个指标,只能提高关联概率,仍需独立故障注入证明因果。存量恢复先对支付渠道、支付单、分录和事件做资金对账,再审计跨租户访问,最后按任务键和检查点分批消化积压;未知结果不自动失败,错误身份不默认放行。修复后分别重放启动依赖延迟、重复回调、密钥轮换、上下文污染和任务失租,再做组合压力,按 10%、30%、60%、100% 放量,任何资金差异、安全拒绝异常或积压斜率转正都回退。复盘区分触发条件、根因、放大器和发现缺口,记录止血副作用、恢复时间目标、负责人及下一次演练。没有原始证据时不把“发布导致”当最终结论,保持 E0(待核对)。
  • 追问 1:回退后恢复是否证明发布是唯一根因? 直接回答:不能,只证明强相关,还需逐项对照和注入排除依赖波动等共同因素。
  • 追问 2:为什么先处理资金和安全? 直接回答:二者可能造成不可逆损失和越权,优先级高于可恢复的延迟与积压。
  • 追问 3:积压时为什么还要停重试? 直接回答:无预算重试会继续占连接和线程,降低真实完成率并扩大积压。
  • 追问 4:事故关闭标准是什么? 直接回答:根因已证实,存量差异闭环,完整观察窗无反弹,修复和回退都通过演练并有负责人。
  • 详细章节:Spring(Java 应用框架)项目排障与综合面试题库
  • Spring Boot(快速开发框架)官方生产就绪文档

8. 复习与口述检查清单

  • 能从运行时对象身份证明代理是否存在,并解释 this 自调用为什么绕过事务拦截器。
  • 能比较 REQUIRED(需要事务)、REQUIRES_NEW(总是新事务)与 NESTED(嵌套事务)的物理边界、资源成本和回滚关系。
  • 能说明受检异常、异常捕获、失败码和异步异常为什么可能造成意外提交。
  • 能把 Spring Boot(快速开发框架)端口监听、Liveness(存活状态)和 Readiness(就绪状态)分开。
  • 能把认证、授权、资源归属、网关可信来源和线程池上下文清理串成证据链。
  • 能解释 SqlSession(数据库会话)作用域、一级缓存陈旧和 Batch(批处理)刷新失败边界。
  • 能用支付回调讲清验签、幂等、状态机、分录、Outbox(发件箱)、未知态查单和对账。
  • 能用 Runner(执行器)讲清租约、Fencing Token(栅栏令牌)、检查点、优雅停机和旧写者拒绝。
  • 每次故障回答都包含失败注入、证据链、止血、修复、存量校验、分档验证与复盘。
  • 所有生产数字、事故、收益与个人贡献都按 E1(直接证据)、E2(已有材料映射)、E3(演练设计)或 E0(待核对)约束口述。