谢飞机面Java大厂:电商秒杀场景下的Spring Cloud + Redis + Kafka 三轮暴击面试实录

面试官(推了推眼镜,手边是《阿里Java开发手册》和Prometheus监控大屏截图)

谢飞机(工牌歪戴,背包上贴着‘Hello World’贴纸,掏出半块奥利奥啃了一口)


🌟 第一轮:微服务拆分与流量入口 —— “你家秒杀服务,为啥不单体一把梭?”

面试官:我们电商中台正在重构秒杀系统。原单体应用QPS卡在1200就OOM,现在要拆成seckill-gatewayseckill-serviceinventory-serviceorder-service四个Spring Boot微服务。你说说——

  1. 网关层用Spring Cloud Gateway还是Nginx?为什么?
  2. inventory-service暴露的扣减库存接口,如何防止被恶意刷?需要哪几层限流?
  3. 如果order-service调用失败,怎么保证最终一致性?能用本地事务吗?

谢飞机(咽下饼干渣):“啊…Gateway!因为能写Java过滤器,还能集成Sentinel…限流嘛,网关层QPS限1000,服务层用@SentinelResource再限500…最终一致性?呃…MQ发个消息,消费者重试?本地事务?不能不能,跨服务了!”

面试官点头:“不错,网关选型和限流分层意识有,Sentinel整合也提到了——那你知道Gateway的全局过滤器GlobalFilter和普通Filter执行顺序吗?”

谢飞机(挠头):“这个…好像是…先Global再局部?或者反过来?哎哟我记混了…”


🌟 第二轮:高并发库存兜底 —— “Redis里那把锁,真能拦住10万人?”

面试官:秒杀开始瞬间,10万请求涌向库存服务。我们用Redisson的RLock做分布式锁,但上线后发现超卖了37件。

  1. 请画出标准Redis分布式锁加锁+续期+释放的时序图(文字描述也可);
  2. 如果Redis主从异步复制,master宕机前刚加锁,slave升主后锁丢失——这叫什么问题?怎么破?
  3. 缓存穿透怎么办?比如有人用-1或超长随机ID狂刷/inventory/{id}

谢飞机(猛灌一口冰美式):“加锁就是setnx对吧?过期时间设30秒…续期?好像有个看门狗…超卖?是不是没用lua脚本删锁?穿透?布隆过滤器!我简历写了!”

⚠️ 面试官皱眉:“布隆过滤器确实防穿透,但你初始化时怎么加载全量商品ID?如果商品百万级,布隆过滤器bit数组内存爆了怎么办?有没有更轻量的方案?”

谢飞机(眼神飘向窗外):“那个…可以…缓存空对象?但得设短过期…啊对!还可以…用Redis的…SCAN命令?不对不对…”


🌟 第三轮:削峰与可观测性 —— “Kafka消息积压200万条,你先喝口水再答”

面试官:秒杀成功后,订单创建走Kafka异步化。某次大促,order-create-topic积压200万条,下游消费延迟达8分钟。

  1. 你如何快速定位是生产者快、消费者慢,还是网络/磁盘瓶颈?
  2. 消费者用的是Spring Kafka @KafkaListener,线程模型是单线程还是多线程?怎么提升吞吐?
  3. 如果某条消息因DB唯一键冲突反复投递失败,如何避免死循环?Kafka本身支持死信队列吗?

谢飞机(擦汗):“看监控!Prometheus查kafka_consumergroup_lag…线程?默认一个吧?可以配concurrency=5!死信…Kafka没DLQ,得自己建个topic转发…”

面试官微笑:“很好,监控指标和并发配置答准了。那补充一问:Spring Kafka里enable.auto.commit=false时,手动commit的时机该选ACK_MODE = MANUAL_IMMEDIATE还是MANUAL?为什么?”

谢飞机(突然安静,掏出手机想搜):“这个…我…我回家查查文档…”


🚪 终场:面试官合上笔记本

面试官:谢同学,基础框架使用和问题敏感度有亮点,尤其对限流分层和Kafka运维有实感。但分布式锁的容错细节、缓存方案的工程权衡、消息语义的精确控制,还需要结合线上Case深挖。建议精读《Redis设计与实现》第9章、《Kafka权威指南》第6章,再跑一遍Seata+Redisson+Kafka的本地联调Demo。

稍顿,起身握手

“感谢今天的时间。结果会在5个工作日内通过邮件通知,祝你拿到心仪的offer。”


📚 【附:小白也能懂的答案详解】

✅ 第一轮答案解析

  1. 网关选型:Spring Cloud Gateway(非Nginx)—— 因需动态路由、JWT鉴权、Sentinel熔断、灰度标透传等Java生态能力;Nginx适合静态资源和四层负载,七层逻辑弱。
  2. 三层限流:① CDN层(地域限流)、② API网关层(Sentinel QPS规则)、③ 库存服务层(Guava RateLimiter + 接口级@SentinelResource)。避免单点过载。
  3. 最终一致性:不能用本地事务(XA性能差且Spring Boot默认不支持)。正确方案:① Seata AT模式(推荐),② RocketMQ事务消息,③ Kafka + 本地事务表(记录待发消息,定时扫描补偿)。

✅ 第二轮答案解析

  1. Redis分布式锁标准流程
    • 加锁:SET key random_value NX PX 30000(NX=不存在才设,PX=30秒自动释放)
    • 续期:Redisson看门狗机制,每10秒检查锁存在且持有者为自己 → 自动续期至30秒
    • 释放:Lua脚本比对value再DEL,防误删
  2. 主从切换导致锁丢失 → Redlock失效问题:本质是Redis不满足CP,无法提供强一致性锁。生产推荐方案:改用ZooKeeper(CP)或etcd,或接受AP场景下用“库存预热+本地缓存+数据库行锁”兜底。
  3. 缓存穿透轻量解法
    • 缓存空对象get(key)==null → set(key, "NULL", 2min),注意value用特殊标记(如"NULL"),避免和真实null混淆;
    • 参数校验前置:Controller层拦截负数、超长ID、非法字符;
    • ⚠️ 布隆过滤器慎用:初始化需全量ID,可用RedisBloom模块(BF.RESERVE)或Caffeine本地布隆(适合读多写少)。

✅ 第三轮答案解析

  1. Kafka积压定位三板斧
    • kafka-consumer-groups.sh --describe 查lag;
    • Prometheus查 kafka_consumer_fetch_manager_records_lag_max(消费者拉取延迟) vs kafka_producer_record_send_rate(生产速率);
    • iotop/iostat 看磁盘IO,iftop看网络带宽。
  2. Spring Kafka线程模型:默认单线程消费(一个ConcurrentMessageListenerContainer含1个KafkaMessageListenerContainer)。提升吞吐:① concurrency=N 启动N个独立消费者实例(注意分区数≥N),② max.poll.records=500 + fetch.max.wait.ms=500 批量拉取。
  3. 死信处理
    • Kafka无原生DLQ,需自建order-create-dlq-topic
    • @KafkaListener中捕获异常 → 发送至DLQ topic → 单独服务监听DLQ做人工干预或告警;
    • 关键细节MANUAL_IMMEDIATE(收到消息立即触发ack)比MANUAL(需显式调用ack.acknowledge())更安全,避免重复消费。

💡 一句话总结秒杀技术栈闭环Gateway限流 → Redisson锁+空值缓存防穿透 → Kafka削峰 → Seata/本地事务表保一致 → Prometheus+Kibana全链路监控


📌 延伸学习路径

  • 动手实验:GitHub搜 spring-cloud-alibaba-seckill-demo(含完整代码+压测脚本)
  • 避坑清单:《阿里秒杀系统11个致命错误》PDF(评论区留言“秒杀”获取)
  • 面试彩蛋:当被问“如果让你重构,会引入R2DBC吗?”——答:“读多写少场景可试,但秒杀核心链路仍优先保障MySQL稳定性,R2DBC价值在长连接耗尽场景。”

本文由CSDN「Java架构进阶」专栏出品 | 谢飞机友情出演,面试官原型来自某Top3电商中间件团队

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐