Java大厂面试实录:电商秒杀+支付场景下,Redis缓存、Kafka削峰、分布式事务与微服务安全连环问
2026/9/19 13:25:17 网站建设 项目流程

Java大厂面试实录:电商秒杀+支付场景下,Redis缓存、Kafka削峰、分布式事务与微服务安全连环问

故事背景

某互联网大厂,会议室。一位面无表情的资深面试官推了推眼镜,对面坐着一位头发略油、自称"三年Java经验"的程序员——谢飞机。今天面的岗位是电商中台后端开发,业务覆盖秒杀、订单、支付三大块。

面试官:谢先生,我们直接进入正题,一共三轮。


第一轮 · Java基础与JVM(热身局)

面试官:先说说你平时用的 JDK 版本,8、11、17 有什么区别?

谢飞机:8 最熟,11 和 17 也了解。JDK 11 是 LTS,加了var局部变量推断、ZGC 垃圾收集器、新的 HttpClient,还有 String 的isBlankrepeatlines这些方法。JDK 17 也是 LTS,主要是废弃一些旧 API、封禁内部 API,加了密封类 sealed class。

面试官:嗯,答得挺顺,看来是真用过。那HashMap 在并发下有什么问题?为什么推荐 ConcurrentHashMap?

谢飞机:HashMap 线程不安全,并发 put 会数据覆盖。JDK7 扩容时头插法可能形成环形链表,get 死循环;JDK8 改成尾插法解决了死循环,但并发丢数据还是存在。ConcurrentHashMap 是 CAS + synchronized 锁桶,读操作不加锁,写操作锁住单个桶,并发性能好。

面试官(微微点头):底子可以。那Spring 的 IoC 和 AOP 核心思想说说。

谢飞机:IoC 就是把对象的创建和依赖管理反转给 Spring 容器,降低耦合;AOP 是面向切面编程,把日志、事务、权限这些横切逻辑从业务里抽出来,靠动态代理(JDK 动态代理 / CGLIB)实现。

面试官:热身过了,我们来点真实的。


第二轮 · 电商秒杀场景(上强度)

面试官:假设我们做双十一秒杀,流量瞬间几百万 QPS 的读、几十万 QPS 的写,Redis 怎么设计库存扣减?

谢飞机:秒杀要提前把库存预热到 Redis,用 Lua 脚本保证"判断库存 + 扣减"的原子性,防止超卖。接口层面可以加限流、防重复提交。

面试官:好,Lua 这块答得可以。那缓存穿透、击穿、雪崩分别怎么解决?

谢飞机:穿透就是查不存在的 key,请求全打 DB,用布隆过滤器 + 空值缓存;击穿是热点 key 过期瞬间大量请求打 DB,用互斥锁或者逻辑过期;雪崩是大量 key 同时过期,用随机过期时间 + 多级缓存 + 熔断降级。

面试官:不错。那缓存和数据库的一致性怎么保证?先删缓存还是先更新 DB?

谢飞机:呃…这个一般先更新数据库,再删缓存,加上延迟双删……但极端情况下还是会不一致,得靠 binlog 订阅或者 TTL 兜底……(越说越小声)

面试官:含糊了。那我们聊消息队列——秒杀下单怎么用 Kafka 削峰?怎么保证不丢消息?

谢飞机:订单请求先写 Kafka,消费者慢慢消费落库。不丢消息的话……生产者要 ack=all,消费者手动提交 offset,还要幂等……呃,还有那个事务消息?(眼神开始飘忽)

面试官:行,这块我们记下了。


第三轮 · 支付与微服务(压轴局)

面试官:秒杀只是入口,钱才是大事。假设我们做支付系统,订单、账户、积分、优惠券多个系统一起改,怎么保证分布式事务的一致性?

谢飞机:嗯…分布式事务有 2PC、TCC、SAGA,还有那个 Seata……大概就是要么都成功要么都回滚,但本地消息表 + MQ 最终一致性我比较熟……(支支吾吾)

面试官:最终一致性?那你讲讲对账,怎么发现少钱多钱?

谢飞机:对账就是……拉微信、支付宝的账单和本地流水比对,有差异就生成差错单人工处理……但具体的对账引擎设计我没做过,我都是看别人配的……(越说越没底)

面试官:那换个轻松的。Spring Cloud 微服务里,服务发现、熔断降级怎么做?

谢飞机:服务发现用 Nacos 或 Eureka,OpenFeign 做远程调用,熔断用 Resilience4j 或者 Sentinel,配置中心用 Apollo……这块我搭过 Demo,生产环境还是大佬带的。(挠头)

面试官:JWT 和 OAuth2 区别?支付接口怎么防篡改防重放?

谢飞机:JWT 是无状态 token,OAuth2 是授权协议,可以签发 JWT……防篡改用签名验签,防重放要加 nonce 和 timestamp……(声音越来越虚)

面试官:最后问一个。线上突然 OOM 或者 CPU 飙高,你怎么排查?

谢飞机:OOM 先看堆转储,jmap 导 dump 用 MAT 分析……CPU 飙高用 jstack 看线程栈,找 top 线程……不过一般我都是重启一下,先恢复业务再查……(尴尬地笑)

面试官(合上电脑,面无表情):行,谢先生,你的基础面还是可以的,底层原理和常用组件都能说上来。但是分布式事务、对账、性能排查这些核心的深度还不够,我们中台这块要求比较高。这样吧,你先回去等通知,我们下周还有几位候选人要面,有结果 HR 会联系你。

谢飞机:好的好的,谢谢面试官,我回去一定好好补课!(走出会议室,掏出手机:赶紧买个《分布式事务从入门到放弃》)


答案解析:小白学习版

下面把每一问的业务场景 + 技术点展开讲透,面试前可以直接背。

第一轮:Java基础与JVM

Q1:JDK 8 / 11 / 17 有什么区别?

  • JDK 8(LTS):企业线上绝对主力。核心特性:Lambda 表达式、Stream 流式 API、Optional、CompletableFuture 异步编程、新的日期时间 API(LocalDateTime)、接口默认方法与静态方法、Metaspace 替代永久代。
  • JDK 11(LTS)var局部变量类型推断(注意:只能用于局部变量,不能用于成员变量);String 新增isBlank/repeat/lines/strip;全新的 HttpClient(替代 HttpURLConnection);ZGC 试验性引入;移除 Java EE 模块(JAXB、JTA 等)。
  • JDK 17(LTS):密封类 sealed class 正式化;强封装 JDK 内部 API(要使用需加--add-opens);默认禁用 finalize;移除部分实验性 AOT 编译等。

面试加分点:能说出每个版本的 LTS 定位、线上用的哪个、升级的顾虑(如依赖兼容、GC 选择)。

Q2:HashMap 并发问题 & 为什么用 ConcurrentHashMap?

  • HashMap 线程不安全:并发 put 时两个线程同时往同一个桶写,可能互相覆盖数据。
  • JDK7 死循环问题:数组 + 链表,扩容用头插法。多线程并发扩容时链表被倒序重排,可能形成环形链表,get 时无限循环 → CPU 飙高甚至宕机。
  • JDK8 改进:改成尾插法,死循环问题解决,但并发数据覆盖问题依旧存在(putValtab[i]赋值被后写的线程覆盖)。
  • ConcurrentHashMap(JDK8):放弃分段锁,改为CAS + synchronized 锁单个桶(bin),锁粒度从整张表降到单个哈希桶;读操作完全无锁;扩容时多线程协助搬运;size()用 CounterCell 数组累加。

Q3:Spring 的 IoC 与 AOP

  • IoC(控制反转):把对象的创建、依赖关系交给 Spring 容器管理,开发者只需声明依赖(构造器注入、Setter、@Autowired),实现高内聚低耦合。
  • AOP(面向切面编程):把事务(@Transactional)、日志、权限校验、性能监控等横切逻辑从业务中抽离。实现基于动态代理:目标类有接口 → JDK 动态代理;无接口 → CGLIB 字节码增强。

第二轮:电商秒杀场景

Q1:Redis 如何设计库存扣减?

  • 预热:活动开始前把库存批量写入 Redis,避免活动瞬间冷启动打爆 DB。
  • Lua 脚本保证原子性:把"判断库存 > 0 + 扣减"合并成一个 Lua 脚本整体执行,Redis 单线程保证脚本内操作原子,从根本上防超卖:
    if tonumber(redis.call('get', KEYS[1])) > 0 then return redis.call('decr', KEYS[1]) end return -1
  • 限流:Sentinel / Guava RateLimiter / 令牌桶,限制单位时间放行量;用户维度做防重复提交(Redis SETNX + 幂等标记)。
  • 异步落库:Redis 预扣成功 → 发 MQ → 下游真正扣库存落库 → 超时未支付则释放库存。

Q2:缓存穿透 / 击穿 / 雪崩

| 问题 | 现象 | 解法 | |---|---|---| |穿透| 查不存在的 key,请求全打到 DB | 布隆过滤器(BloomFilter)先拦截 + 空值缓存(短 TTL) | |击穿| 单个热点 key 过期瞬间,大量请求打 DB | 互斥锁(SETNX 只放一个线程去 DB 重建);逻辑过期(value 里存过期时间,异步重建) | |雪崩| 大量 key 同时过期 / Redis 宕机 | 过期时间加随机值;多级缓存(本地 Caffeine + Redis);熔断降级;Redis 集群高可用 |

Q3:缓存与 DB 一致性(为什么先更新 DB 再删缓存)

  • 标准姿势是Cache Aside:先更新数据库,再删缓存。如果先删缓存再更新 DB,在"删缓存 → 更新 DB"的窗口期,并发读会把旧值重新写回缓存,导致长期脏数据
  • 延迟双删:删缓存 → 更新 DB → 延迟几百毫秒再删一次,兜住并发窗口期写回的旧值。
  • 兜底:订阅 MySQL binlog(如 Canal)同步刷新缓存 / 设置 TTL,最终保证最终一致性

面试官觉得含糊的点:严格的缓存一致性无法 100% 保证,关键在于权衡;能说出延迟双删、binlog 订阅、TTL 兜底这套组合才是完整答案。

Q4:Kafka 削峰 & 不丢消息

  • 削峰原理:MQ 是天然缓冲区。秒杀请求先全部写入 Kafka(吞吐极高),下游消费者按数据库能承受的速率慢慢消费落库,实现流量整形,防止瞬时洪峰压垮订单服务。
  • 不丢消息三端保障
    • 生产者acks=all(ISR 全部副本确认)+retries重试 +enable.idempotence幂等。
    • Broker:分区多副本 +min.insync.replicas,防止副本丢失。
    • 消费者手动提交 offset(先处理完业务逻辑再提交),避免提交后消费失败丢数据;消费逻辑要做幂等(用业务流水号去重)。

谢飞机含糊的地方:Kafka 事务(Exactly-Once)、offset 提交时机、消费积压的排查与扩容方案都没讲透。

第三轮:支付与微服务

Q1:分布式事务怎么保证一致性?

  • 业务场景:下单同时要扣库存、扣账户、发积分、改优惠券状态,跨越多个微服务,单库本地事务管不了。
  • 方案对比
    • 2PC / XA:强一致,但同步阻塞、性能差、协调者单点,适合对一致性要求极高且并发不高的场景。Seata 的AT 模式就是 2PC 的改良(生成 undo_log 回滚日志,无侵入)。
    • TCC:Try(预留资源)→ Confirm(确认)→ Cancel(补偿),业务侵入强,适合金融扣款等,要注意空回滚悬挂问题。
    • SAGA:长事务,每一步有反向补偿操作,适合跨多服务的编排型流程。
    • 本地消息表 + MQ(最终一致性):本地事务里同时写业务表和消息表 → 定时任务投递 MQ → 消费方幂等处理,是互联网最常用的做法。

Q2:对账怎么做?

  • 目的:发现外部渠道(微信/支付宝/银联)账单与本地账务的差异,防止资金损失和错账。
  • 流程:定时拉取渠道对账单 → 按订单号、金额、状态与本地流水/订单比对 → 差异分类(长款/短款/未达账)→ 生成差错单 → 自动或人工调账 → 复核对账闭环。
  • 技术点:定时调度(XXL-Job)、大数据量比对用分片或 Spark 批处理、差错处理必须幂等(同一笔差账不能重复入账)。

Q3:Spring Cloud 服务治理

  • 注册发现:Nacos / Eureka / Consul;配合 Spring Cloud LoadBalancer 做负载均衡。
  • 远程调用:OpenFeign 声明式 HTTP 客户端。
  • 熔断降级:Resilience4j(熔断器、信号量、舱壁隔离)或 Alibaba Sentinel;降级时返回兜底数据(fallback)。
  • 网关:Spring Cloud Gateway 统一入口、鉴权、限流。
  • 配置中心:Nacos / Apollo,配置热更新。

谢飞机只答了名词,缺少选型理由和生产细节(熔断阈值、超时重试要防重放、服务漂移与缓存等)。

Q4:JWT 与 OAuth2 区别 + 支付接口防篡改防重放

  • JWT:无状态签名令牌,自包含三段header.payload.signature,HS256(对称)/ RS256(非对称)签名,用于认证。缺点是无法主动吊销,过期前一直有效。
  • OAuth2授权协议,定义授权码 / 密码 / 客户端等模式,颁发 access_token + refresh_token,用于第三方授权。两者可结合:OAuth2 签发 JWT 格式的 token。
  • 支付接口安全三板斧
    1. 防篡改:对报文做摘要 + 商户私钥数字签名,平台验签;全程 HTTPS。
    2. 防重放:timestamp 过期校验 + 随机数 nonce(用 Redis SETNX 记录已用 nonce)+ 业务流水号幂等(同一请求重复到达只处理一次)。
    3. 敏感数据:RSA / AES 加密传输。

Q5:OOM / CPU 飙高排查

  • OOM 排查
    1. 保留现场:jmap -dump:format=b,file=heap.hprof <pid>,或启动参数-XX:+HeapDumpOnOutOfMemoryError自动导出。
    2. 分析:用 MAT 看 Dominator Tree 找大对象、Leak Suspects 定位泄漏引用链。
    3. 常见原因:大 List 未释放、ThreadLocal 内存泄漏、静态集合无限增长、连接/IO 流未关闭。
  • CPU 飙高排查
    1. top找到高 CPU 进程 →top -Hp <pid>找到高 CPU 线程。
    2. printf '%x' <tid>把线程 ID 转十六进制。
    3. jstack <pid> | grep -A 20 <十六进制tid>定位线程栈。
    4. 常见原因:死循环、频繁 Full GC、正则回溯、序列化、锁竞争自旋。

谢飞机"先重启"的做法暴露了生产排查经验的欠缺——现场没了,问题就永远查不明白了。


写在最后

这一场面试下来,其实能看出大厂考察的分层逻辑

  1. 第一层:语言基础与 JVM(JDK 版本、集合并发、Spring 原理)——考察基本功扎不扎实。
  2. 第二层:核心中间件(Redis、Kafka)结合真实业务场景(秒杀)——考察能不能把组件用到业务里,而不仅是背概念。
  3. 第三层:复杂分布式问题(分布式事务、对账、微服务治理、安全、性能排查)——考察深度与生产经验。

谢飞机败在哪?简单问题对答如流,但一旦进入"深度"和"细节"就含糊其辞——这正是很多"面经背得多、实战想得少"的同学的通病。面试官要的不是你背过多少名词,而是每个方案背后的为什么、取舍和坑

把上面每道题的"业务场景 + 技术点 + 坑"吃透,你离大厂 offer 就不远了。祝各位面友好运!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询