互联网大厂高阶Java面试现场:从Spring AI到分布式事务的极限拷问
2026年3月24日,杭州某互联网大厂会议室。空调恒温23℃,白板笔迹未干,面试官李工推了推眼镜,目光平静却带着审视。对面坐着坤哥,30岁出头,穿着一件略显宽松的格子衬衫,眼神里透着一股“我准备很久了”的笃定。
“坤哥,先简单自我介绍一下吧。”李工语气平和,手指轻轻敲了敲桌面。
“好的,李工。我叫坤,五年Java后端经验,目前在一家中厂负责订单系统架构。熟悉Spring生态、分布式事务、高并发设计,最近也在研究AI工程化落地,比如用Spring AI做知识库助手。”
李工点点头,翻开笔记本:“那我们从基础开始。你说熟悉Spring,IOC和AOP是核心,能说说Spring如何解决循环依赖?”
坤哥稍作思考:“Spring通过三级缓存解决循环依赖。一级缓存是singletonObjects,存放完整Bean;二级缓存earlySingletonObjects,存放早期暴露的Bean;三级缓存singletonFactories,存放ObjectFactory。当A依赖B,B又依赖A时,Spring在创建A时就将A的ObjectFactory放入三级缓存,B在注入A时从三级缓存获取早期引用,从而打破循环。”
“那如果A和B都是原型作用域呢?”李工追问。
“原型作用域不支持循环依赖,Spring会直接抛BeanCurrentlyInCreationException。”坤哥回答得干脆。
“为什么?”
“因为原型Bean每次getBean都会新建实例,无法通过缓存复用早期对象,所以Spring干脆禁止。”
李工点头:“那三级缓存的设计动机是什么?为什么不用二级?”
坤哥顿了一下:“三级缓存主要是为了处理AOP代理。如果Bean需要被代理,早期暴露的应该是代理对象,而不是原始对象。ObjectFactory可以在需要时动态创建代理,避免提前生成代理导致逻辑混乱。”
“举个例子?”
“比如A被@Transactional注解,如果提前生成代理,可能在B注入A时,A的代理还未完全初始化,导致事务失效。ObjectFactory延迟了代理的创建时机。”
李工在笔记本上记了一笔:“好,进入下一轮。你提到Spring AI,你们项目里怎么用?”
“我们用Spring AI + RAG架构做企业知识库助手。用户提问后,先通过向量数据库检索相关文档片段,再拼接成Context交给LLM生成回答。”
“向量数据库选型?”
“用的是Milvus,支持高维向量相似度检索,延迟低,适合实时问答。”
“Prompt怎么设计?有没有做调优?”
“我们做了分层Prompt:系统层设定角色和输出格式,用户层拼接检索结果,还加了Few-shot示例。调优方面,试过Temperature、Top-p参数,也做了Context Window压缩,避免Token超限。”
“Context Window压缩具体怎么做的?”
“用TF-IDF筛选关键句,或者用Sentence-BERT重排,优先保留高相关性片段。”
“如果用户问题很模糊,检索结果质量差,怎么办?”
坤哥想了想:“我们会加兜底策略,比如返回‘未找到相关信息,请尝试更具体的关键词’,或者引导用户选择预设问题。”
李工点头:“那如果检索结果很多,但LLM生成超时,怎么优化?”
“可以异步处理,先返回‘正在分析中’,再通过WebSocket推送结果。或者做缓存,对高频问题预生成答案。”
“缓存一致性怎么保证?”
“用Redis做本地缓存,设置TTL,同时监听文档变更事件,通过MQ通知更新缓存。”
李工突然话锋一转:“说到MQ,你们用Kafka还是RocketMQ?”
“RocketMQ。因为需要事务消息,Kafka的事务支持相对复杂,RocketMQ的Half Message机制更直观。”
“事务消息如何保证可靠性?”
“Producer发送Half消息到Broker,执行本地事务,再发送Commit/Rollback。Broker收到Commit后才对Consumer可见。如果Producer宕机,Broker会回查本地事务状态。”
“回查机制怎么实现?”
“Producer要实现TransactionListener接口,提供executeLocalTransaction和checkLocalTransaction方法。Broker定时扫描Half消息,调用checkLocalTransaction确认状态。”
“如果本地事务长时间未提交,会怎样?”
“Broker会持续回查,默认最多回查15次,间隔指数退避。如果最终未确认,消息会被丢弃或进入死信队列。”
“那如果Consumer消费失败,怎么处理?”
“RocketMQ支持重试队列,消费失败后进入%RETRY%队列,延迟重试。超过重试次数进入死信队列,人工介入。”
李工喝了口水:“好,进入第三轮。假设你们知识库系统要支撑千万级用户,日均问答量过亿,怎么设计架构?”
坤哥深吸一口气:“首先,前端做限流,用Sentinel做QPS控制。接入层用Nginx做负载均衡,后端服务无状态,横向扩展。”
“服务层怎么拆分?”
“拆成三个微服务:检索服务、生成服务、用户服务。检索服务负责向量检索,生成服务调用LLM,用户服务管理权限和会话。”
“检索服务压力大,怎么优化?”
“做多级缓存:本地缓存 存热点问题,Redis集群存检索结果,Milvus做持久化向量库。同时做查询合并,避免重复检索。”
“生成服务调用LLM,延迟高,怎么应对?”
“用异步+流式响应。用户提问后,先返回‘正在思考’,生成服务流式返回Token,前端逐字渲染。同时做模型量化,用vLLM加速推理。”
“如果LLM服务宕机,怎么降级?”
“降级到规则引擎,比如关键词匹配FAQ,或者返回预设模板:‘服务繁忙,请稍后再试’。”
“数据一致性呢?比如文档更新了,向量库没同步?”
“用CDC监听MySQL binlog,通过Debezium捕获变更,发MQ通知向量库更新。同时做版本控制,每次更新生成新版本向量,旧查询仍用旧版本,保证一致性。”
“如果MQ积压,向量库更新延迟,怎么办?”
“监控积压量,动态扩容消费者。或者做批量更新,牺牲一点实时性换吞吐量。”
李工在白板上画了个架构图:“那如果用户量突然暴增,比如某热点事件引发集中提问,怎么扛住?”
“首先,Sentinel做集群流控,避免雪崩。其次,检索服务做读写分离,Milvus支持多副本。生成服务做模型预热,提前加载权重。最后,做请求排队,超时就返回‘系统繁忙’。”
“那如果Redis挂了,缓存穿透怎么办?”
“用布隆过滤器过滤无效查询,同时做空值缓存,避免重复查库。还有限流,避免DB被打崩。”
“布隆过滤器误判率怎么控制?”
“根据预期元素数量和容忍误判率计算位数组大小和哈希函数数量。我们设的是0.1%误判率,用Guava的实现。”
李工点头:“最后一个问题。如果你们系统要跨地域部署,比如北京和广州两个机房,怎么保证数据一致?”
坤哥沉默了几秒:“这……可能需要分布式事务。比如用Seata的AT模式,或者TCC。但跨机房延迟高,AT模式锁冲突严重,可能影响性能。”
“那你怎么权衡?”
“如果强一致不是必须,可以用最终一致。比如文档更新后,先写本地库,再发MQ通知对端机房异步同步。牺牲一点实时性,换高可用。”
“如果对端机房MQ挂了,数据丢了呢?”
“加本地消息表,记录同步状态,定时重试。或者用可靠消息服务,比如阿里云的MNS。”
李工合上笔记本:“好,今天就到这里。坤哥,你整体思路清晰,但对跨机房一致性的方案还不够深入,尤其是网络分区下的脑裂问题没提到。”
坤哥点头:“确实,这块我经验不足,回去会补。”
李工站起身:“回去等通知吧。”
坤哥走出会议室,阳光刺眼。他摸了摸口袋里的手机,心想:这次,应该稳了吧?
技术补丁包
1. Spring循环依赖与三级缓存
- 原理:Spring通过三级缓存解决单例Bean的循环依赖。一级缓存存完整Bean,二级存早期暴露对象,三级存ObjectFactory。
- 设计动机:三级缓存的核心是延迟代理对象的创建。若Bean需要AOP代理,ObjectFactory可在注入时动态生成代理,避免提前代理导致事务或切面失效。
- 边界条件:仅支持单例作用域,原型Bean因无法复用早期对象而禁止循环依赖。
- 源码关键:
DefaultSingletonBeanRegistry.getSingleton()方法中,addSingletonFactory将ObjectFactory放入三级缓存。 - 风险提示:过度依赖循环依赖可能暗示设计缺陷,建议通过重构消除。
2. Spring AI与RAG架构
- 核心流程:用户提问 → 向量检索 → Context拼接 → LLM生成 → 返回答案。
- Prompt调优:分层设计(系统指令+用户输入+示例),控制Temperature(0.7~1.0)避免过于随机,使用Few-shot提升准确性。
- Context Window压缩:TF-IDF筛选关键词句,或Sentence-BERT重排,确保高相关性内容优先进入LLM。
- 落地建议:结合业务FAQ做缓存预热,降低冷启动延迟;监控Token使用量,避免超限。
3. RocketMQ事务消息
- 流程:发送Half消息 → 执行本地事务 → 发送Commit/Rollback → Broker确认 → Consumer可见。
- 回查机制:Broker定时扫描Half消息,调用Producer的
checkLocalTransaction确认状态,最多15次,间隔指数退避。 - 可靠性保障:本地事务需幂等,避免重复执行;建议记录事务日志,便于排查。
- 对比Kafka:RocketMQ事务API更简洁,适合金融、订单等强一致性场景。
4. 多级缓存与一致性
- 层级设计:本地缓存(Caffeine) → Redis集群 → 持久化存储(MySQL/Milvus)。
- 缓存穿透:布隆过滤器过滤无效Key,空值缓存避免重复查库。
- 缓存雪崩:随机TTL,避免集中失效;降级策略返回默认值。
- 一致性维护:CDC监听数据变更,MQ通知缓存更新;版本控制保证查询一致性。
5. 高并发架构设计
- 限流降级:Sentinel做QPS控制,集群流控防雪崩;降级到规则引擎或模板响应。
- 异步处理:生成服务流式返回,提升用户体验;MQ解耦,提升系统韧性。
- 跨机房部署:最终一致性优先,本地消息表+定时重试保障数据同步;避免强一致带来的性能损耗。
- 监控告警:关键指标(QPS、延迟、错误率)实时监控,自动扩容应对突发流量。
6. 分布式事务权衡
- 强一致方案:Seata AT模式(全局锁)、TCC(业务侵入高)。
- 最终一致方案:本地消息表、可靠消息队列、Saga模式。
- 选型建议:非金融场景优先最终一致,牺牲实时性换高可用;跨机房部署慎用强一致,避免网络分区引发脑裂。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)