Java 大厂一面模拟:从本地缓存到分布式事务的连环追问

张开发
2026/4/11 17:08:05 15 分钟阅读

分享文章

Java 大厂一面模拟:从本地缓存到分布式事务的连环追问
一面定位如果你来参加大厂 Java 一面今天这场模拟面试将聚焦于本地缓存设计、分布式事务一致性、Spring 事务传播、MySQL 锁机制与 JVM 内存模型的联动考察。候选人画像为 2 年 Java 后端经验参与过订单、支付或会员类系统设计。面试时长约 30 分钟节奏紧凑问题层层递进重点考察从“知道”到“会用”再到“能兜底”的完整链路。面试官风格参考牛客常见大厂一面先问基础概念再追问原理与边界最后落到项目场景中的取舍与落地。整场面试不堆题库但必须体现“拷打感”——即一个问题引出多个关联点连续追问不留喘息。主问题部分1. 你在项目中用过哪些本地缓存为什么不用 Redis参考回答我用过 Caffeine 做本地缓存比如缓存商品基础信息、用户权限数据等读多写少的场景。选择本地缓存是因为这些数据变更频率低且对延迟敏感本地缓存可以避免网络开销提升接口响应速度。而 Redis 虽然也能缓存但每次访问都要走网络QPS 高时对 Redis 压力大也容易成为瓶颈。2. Caffeine 的缓存淘汰策略有哪些你项目中用的是哪一种参考回答Caffeine 支持三种淘汰策略基于大小的Size-based、基于时间的Time-based、基于引用的Reference-based。我项目中主要用基于大小的maximumSize配合expireAfterWrite比如设置最大 1000 条写入后 5 分钟过期。这样既能控制内存占用又能保证数据基本新鲜。3. 本地缓存和 Redis 缓存如何配合使用有没有遇到过一致性问题参考回答我们采用“本地缓存 Redis 二级缓存”架构。先查本地没有再查 Redis都没有才查 DB。更新时先更新 DB再删除 Redis最后通过消息通知各节点删除本地缓存。遇到过一致性问题某次 DB 更新后Redis 删除成功但本地缓存因消息丢失未清理导致读到旧数据。后来加了本地缓存的 TTL 兜底并记录日志用于排查。4. 你说更新 DB 后删 Redis那如果删 Redis 失败了怎么办参考回答这是个经典问题。我们目前的做法是DB 更新成功后异步发送一条“缓存删除”消息到 Kafka由消费者执行 Redis 删除。如果删除失败会重试 3 次仍失败则告警人工介入。同时Redis 本身也设置了过期时间作为最终兜底。5. 如果多个服务实例同时更新同一数据如何保证缓存一致性参考回答我们通过分布式锁Redisson保证同一时间只有一个实例能执行更新操作。更新流程是获取锁 - 更新 DB - 删除 Redis - 释放锁。其他实例在锁释放后下次读取时会重新加载最新数据。6. 你提到 Redisson它底层是怎么实现分布式锁的参考回答Redisson 基于 Redis 的SET resource_name random_value NX PX timeout命令实现。NX 表示只有 key 不存在时才设置PX 设置过期时间防止死锁。释放锁时用 Lua 脚本判断 value 是否匹配只有匹配才删除避免误删其他线程的锁。7. 如果 Redis 主从切换Redisson 的锁会丢吗参考回答会的。Redis 主从异步复制如果主节点加锁后还没同步到从节点就挂了新主节点没有这个锁会导致锁丢失。Redisson 官方推荐使用 RedLock 算法多实例部署来降低风险但我们项目目前还是单 Redis 实例靠业务层幂等和补偿机制兜底。8. 你们的事务是怎么管理的Spring 的 Transactional 用过吗参考回答用过。我们大部分业务用Transactional声明式事务默认传播行为是 REQUIRED。跨服务调用时会用 Seata 做分布式事务比如下单扣库存 创建订单 扣余额这种多库操作。9. Transactional 在什么情况下会失效参考回答常见失效场景有方法不是 public、异常被捕获未抛出、同类内方法调用AOP 代理不生效、数据库引擎不支持事务如 MyISAM。我们项目里踩过同类内调用的坑后来改成通过代理对象调用解决。10. 你提到 Seata它的事务模式有哪些你们用的是哪一种参考回答Seata 支持 AT、TCC、Saga、XA 模式。我们用的是 AT 模式因为它对代码侵入小自动反向 SQL 回滚。但 AT 模式要求所有参与事务的表必须有主键否则无法生成回滚日志。11. 如果 AT 模式回滚失败你们怎么处理参考回答Seata 会记录 undo_log如果回滚失败会进入“悬挂事务”状态由定时任务扫描重试。我们还在业务层做了补偿接口比如订单创建失败后手动调用库存回滚接口。12. MySQL 的间隙锁你了解吗什么场景下会触发参考回答间隙锁是 InnoDB 在可重复读隔离级别下为了防止幻读而加在索引记录之间的锁。比如执行SELECT * FROM user WHERE age BETWEEN 20 AND 30 FOR UPDATE会锁住 (20,30) 这个区间其他事务不能插入该范围内的数据。13. 间隙锁会导致死锁吗你们遇到过吗参考回答会的。比如两个事务分别插入不同数据但落在同一个间隙互相等待对方释放间隙锁就会死锁。我们遇到过后来通过缩小查询范围、避免大范围 FOR UPDATE 来减少触发概率。14. JVM 的堆内存结构是怎样的你项目中 GC 调优过吗参考回答堆分为新生代Eden Survivor和老年代。我们项目用 G1 GC堆大小 4G新生代初始占比 30%。之前 Full GC 频繁后来发现是缓存对象太多调整了 Caffeine 的 maximumSize并加了堆外内存监控。15. 你说用 G1它的 Mixed GC 是怎么工作的参考回答Mixed GC 是 G1 特有的它会同时回收新生代和部分老年代区域根据回收价值排序。触发条件是堆占用达到 InitiatingHeapOccupancyPercent默认 45%。我们设置成 60%避免过早触发。追问部分面试官开始深入“本地缓存 分布式事务”的联动场景追问 1如果本地缓存未失效但 DB 已被另一个服务更新你们怎么感知候选人回答我们靠消息通知。更新服务发一条“数据变更”消息各服务监听后清理本地缓存。面试官追问消息丢了怎么办有没有做过压测验证消息可靠性候选人回答消息丢了就只能靠 TTL 兜底。压测时模拟过消息丢失发现本地缓存最长可能脏 5 分钟业务上可接受。面试官追问如果业务要求强一致比如金融场景这种方案还适用吗候选人回答不适用。强一致场景应该放弃本地缓存或者用更短的 TTL 主动刷新机制甚至直接读 DB。追问 2Seata AT 模式的反向 SQL 是怎么生成的候选人回答Seata 会在执行 SQL 前记录 before image执行后记录 after image回滚时用 before image 生成反向 SQL。面试官追问如果更新的是 JSON 字段before image 能准确回滚吗候选人回答不能完全准确。如果只更新 JSON 中某个字段Seata 会记录整个 JSON 的旧值回滚时覆盖整个字段可能丢失其他并发更新的内容。面试官追问那你们怎么解决候选人回答我们避免在事务中更新 JSON 字段或者拆成多个字段或者改用 TCC 模式手动控制回滚逻辑。追问 3G1 的 Region 大小怎么确定你们设置过吗候选人回答Region 大小由堆大小自动决定范围 1MB~32MB。我们没有手动设置默认就行。面试官追问如果对象很大比如 10MBG1 怎么处理候选人回答大对象会直接进入 Humongous Region如果连续 Region 不够会触发 Full GC。我们遇到过后来优化了缓存对象结构避免大对象。面试点评本场面试主要考察候选人在缓存架构、分布式事务、数据库锁、JVM GC四个核心模块的实战能力。重点不是背答案而是看是否能将技术点串联成完整解决方案。候选人容易卡壳的点本地缓存与 Redis 的协同机制尤其消息丢失场景分布式事务回滚失败的处理Seata 悬挂事务间隙锁与死锁的关联分析G1 大对象处理机制大厂一面更看重“边界意识”和“兜底能力”比如是否考虑过消息丢失、回滚失败、缓存雪崩等极端情况。技术补丁包Caffeine 本地缓存原理基于 W-TinyLFU 算法结合频率与时效性淘汰策略。 设计动机解决高并发下 Redis 网络开销问题提升读性能。 边界条件缓存一致性依赖消息通知或 TTL 兜底强一致场景慎用。 落地建议配合 Kafka 消息清理缓存设置合理 maximumSize 和 expireAfterWrite。二级缓存架构本地 Redis原理先查本地再查 Redis最后查 DB更新时 DB - Redis - 本地。 设计动机平衡性能与一致性减少 Redis 压力。 边界条件消息丢失导致本地缓存脏数据需 TTL 兜底。 落地建议使用 RocketMQ 事务消息或本地消息表保障删除通知可靠性。Redisson 分布式锁原理基于 Redis SET NX PX Lua 脚本释放。 设计动机实现跨 JVM 的互斥访问。 边界条件主从切换导致锁丢失RedLock 可缓解但非绝对安全。 落地建议锁 value 用 UUID释放时校验业务层加幂等防重复执行。Seata AT 模式原理通过代理数据源记录 before/after image自动生成反向 SQL。 设计动机实现无侵入的分布式事务。 边界条件要求表有主键JSON 字段回滚可能覆盖并发更新。 落地建议避免在事务中更新复杂 JSON回滚失败时启用补偿机制。Spring Transactional 失效场景原理基于 AOP 代理同类内调用不走代理。 设计动机简化事务管理。 边界条件非 public 方法、异常被捕获、同类内调用均失效。 落地建议通过 ApplicationContext.getBean() 获取代理对象调用。MySQL 间隙锁原理在可重复读隔离级别下锁住索引记录之间的间隙。 设计动机防止幻读。 边界条件大范围查询易触发可能导致死锁。 落地建议避免大范围 FOR UPDATE缩小查询条件范围。G1 GC Mixed GC原理同时回收新生代和部分老年代 Region按回收价值排序。 设计动机平衡吞吐与延迟。 边界条件IHOP 设置过低易频繁触发。 落地建议根据堆大小调整 IHOP监控 Mixed GC 频率。大对象与 Humongous Region原理超过 Region 一半大小的对象直接进入 Humongous Region。 设计动机避免复制开销。 边界条件连续 Region 不足时触发 Full GC。 落地建议避免缓存大对象优化数据结构。缓存一致性兜底策略原理通过 TTL 强制过期 消息通知 日志监控。 设计动机保障最终一致性。 边界条件TTL 内数据可能脏。 落地建议设置合理 TTL记录缓存更新日志用于排查。消息可靠性保障原理事务消息 本地消息表 重试 告警。 设计动机确保缓存清理指令必达。 边界条件网络分区或消费者宕机。 落地建议使用 RocketMQ 事务消息消费者实现幂等。分布式事务补偿机制原理事务失败时调用反向接口回滚。 设计动机弥补 AT 模式回滚失败。 边界条件补偿接口需幂等。 落地建议设计通用补偿接口记录事务日志用于对账。死锁排查与预防原理通过SHOW ENGINE INNODB STATUS查看死锁日志。 设计动机快速定位问题。 边界条件间隙锁与插入意向锁冲突。 落地建议统一加锁顺序避免长事务。JVM 堆外内存监控原理通过 NMT 或 JMX 监控 Direct Buffer、Metaspace 等。 设计动机避免 OOM 无法排查。 边界条件堆外内存不受 GC 管理。 落地建议启用 NMT限制 Netty Direct Buffer 大小。缓存穿透与空值缓存原理对查询不到的数据缓存空值。 设计动机防止恶意查询压垮 DB。 边界条件空值缓存过期时间不宜过长。 落地建议空值缓存 5-10 分钟配合布隆过滤器。事务传播行为 REQUIRED原理当前有事务则加入没有则新建。 设计动机保证业务操作在同一个事务中。 边界条件嵌套事务回滚影响外层。 落地建议明确事务边界避免长事务。RedLock 算法原理在多个独立 Redis 实例上依次加锁多数成功才算成功。 设计动机提高锁的可用性。 边界条件时钟漂移可能导致锁提前释放。 落地建议仅在强一致场景使用配合 fencing token。Undo Log 与回滚机制原理记录数据修改前的状态用于回滚。 设计动机支持事务回滚。 边界条件回滚失败时需人工介入。 落地建议定期清理 undo_log监控回滚失败率。G1 Region 大小自动调整原理根据堆大小自动选择 1MB~32MB。 设计动机适应不同堆规模。 边界条件手动设置不当影响 GC 效率。 落地建议一般无需手动设置监控 Region 分布。缓存雪崩防护原理随机化 TTL避免同时过期。 设计动机防止大量缓存同时失效压垮 DB。 边界条件随机范围不宜过大。 落地建议TTL 加随机值如 5~10 分钟。分布式事务最终一致性原理通过消息 补偿实现最终一致。 设计动机避免强一致带来的性能损耗。 边界条件补偿延迟。 落地建议设计对账系统监控不一致率。

更多文章