华测一面:交易链路一致性、Spring AI 与状态机
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 介绍一下实习相关的项目。
- 说一下交易链路是怎么设计的,有没有优化?
- 这条链路是强一致性吗?
- 如果错误情况下,有什么兜底?
- 如果兜底方案出问题了,你有什么高可用方案?
- 如果用户下单以后直接把软件杀端了,你们怎么处理?
- 你们纯依靠 MQ?
- 你了解 Spring AI 框架为了能够开发 Agent 做了一些什么设计?
- 你对设计模式有什么了解?大概说一下。
- 状态机你用到了吗?举一个具体的例子。
- Git 过程说一下,冲突了怎么处理的?
《参考解析》
交易链路的强一致、兜底与高可用
先给结论再展开:交易链路通常是「资金/库存强一致,下游履约最终一致」。核心的几步是创建订单、锁库存、发起支付、回调更新订单状态、通知履约。订单与库存如果在同一个库,就用本地事务加行锁或乐观锁版本号;跨服务时用 TCC 或事务消息(RocketMQ 半消息 + 本地事务回查)保证「扣减」和「下单」不会只成功一半。
兜底要按失败点分别讲:支付回调丢失——靠定时对账任务主动查询支付网关的订单状态,把「等回调」变成「回调 + 轮询」双保险;下游通知失败——本地消息表 + 重试队列,重试到上限进人工处理池;超时未支付——延时消息或定时任务关单并回补库存,且回补必须幂等(以订单号 + 状态机流转为准,防止重复回补);金额与账目不一致——T+1 与支付渠道、账务系统三方对账,差额自动挂账、人工介入。所谓高可用方案,本质是让每个补偿动作都幂等、可重试、有对账兜底,再配合 MQ 集群、数据库主从与故障转移、限流降级(高峰期关闭非核心链路,比如积分、推荐),避免局部故障放大成全站不可用。
用户下单后直接杀端,为什么不能只靠 MQ
客户端杀端意味着本地不会有任何回调或重试,所以订单状态的真相必须在服务端。正确设计是:客户端点击下单携带一个唯一幂等键(requestId/bizNo),服务端以状态机记录订单状态;客户端的职责只剩「发起 + 查询」,无论它死在哪一步,用户重进 App 时先按幂等键/订单号查一次服务端状态,是「已创建待支付」就继续支付,是「支付中」就查支付网关,是「已支付」就直接进结果页。
「纯依靠 MQ」的风险在于:消息可能重复(至少一次语义,需要消费端幂等)、可能丢失(要开同步刷盘 + 主从复制 + 发送端本地消息表兜底)、也可能延迟(用户等不及)。所以 MQ 只适合做异步解耦与最终一致性的通道,不适合承载「用户能否看到订单」这类强需求——强需求要么走同步接口 + 事务,要么靠「主动查询 + 对账」补齐。这套「状态机 + 幂等 + 主动查询」的组合也顺带解决了重复提交、前端超时重试导致的重复下单问题。
Spring AI 为 Agent 开发提供了什么
它把大模型接入抽象成了一套 Spring 风格的组件,值得说的有几层:统一的模型抽象(ChatModel/EmbeddingModel/ImageModel 的接口与 ChatClient 的流式 API,换厂商只换依赖和配置);结构化的 Prompt 与消息角色管理(PromptTemplate、System/User/Assistant 消息、ChatMemory 做多轮上下文与窗口裁剪);Tool Calling / Function Callback——把 Java 方法注册成工具,模型返回调用意图,框架负责参数反序列化、执行、把结果回灌给模型,这是 Agent 能「动手」的基础;Advisor 机制——在调用链前后插入可复用的横切逻辑,比如 RAG 检索增强、对话记忆、日志与内容审核,等价于把 Agent 的固定套路做成了可组合的拦截器;RAG 的 ETL 流水线——DocumentReader → TextSplitter → VectorStore 写入与相似度检索(支持 Milvus、Redis、PGVector 等);还有结构化输出转换(把模型回复映射成 POJO/JSON)、多模态支持,以及通过 MCP 客户端把外部工具/服务接进模型。
面试里可以补一句自己的判断:框架解决的是「标准件和胶水」,但 Agent 的可靠性仍然要靠自己设计——工具调用的幂等与超时、上下文预算管理、循环终止条件、失败重试与降级,这些框架只能给钩子,不能替你决定。
状态机的实际应用
最典型的例子就是订单/支付:把订单抽象成状态(待支付、支付中、已支付、已发货、已完成、已取消、已退款)和事件(创建、支付成功、支付超时、发货、取消、退款),在代码里用一张「当前状态 × 事件 → 目标状态 + 动作」的转移表驱动,而不是到处写 if (status == ...)。
好处有三点,正好对应面试官想听的:① 非法流转直接被拒绝(已完成的订单不能再取消),把业务规则集中到一处,避免并发下的状态错乱;② 每次流转可落一条状态流水,配合乐观锁版本号(UPDATE ... WHERE id=? AND status=?)做幂等,MQ 重复消费也不会重复发货;③ 新状态只是加一行配置,容易扩展和画图给产品确认。实现上小项目手写枚举 + 转移表就够,复杂流程可以用 Spring StateMachine 或 Cola StateMachine;关键配套是状态变更与业务动作放在同一个事务里,并保证「状态先落库、再发消息」。
设计模式怎么答
不要背 23 种名字,挑交易链路里真实用到的讲:策略模式——不同支付渠道/不同优惠计算各自实现同一接口,由工厂或 Spring 容器按类型路由,新增渠道不改主流程;状态模式——订单状态驱动行为,替代大量条件分支;责任链模式——下单前的风控校验、参数校验、限流逐层过滤,可以动态增删节点;模板方法——下单主流程固定(校验 → 锁库存 → 创建订单 → 通知),差异步骤交给子类或回调;工厂/建造者——复杂订单对象的组装;单例——无状态的服务与配置(Spring 默认单例,要注意别在里面存可变状态,否则会有线程安全问题);观察者/发布订阅——订单状态变更后通知积分、通知、履约等下游,配合 MQ 解耦。
答这类题最好的结构是「模式 + 我解决的具体问题 + 不用它会怎样」,再补一个反例:过度设计比不设计更糟,比如只有两种类型就上抽象工厂,会显著增加阅读成本。