谢飞机面Java大厂:电商秒杀场景下的Spring Cloud + Redis + Kafka 三轮暴击面试实录
面试官:我们电商中台正在重构秒杀系统。原单体应用QPS卡在1200就OOM,现在要拆成、、、四个Spring Boot微服务。你说说——谢飞机(咽下饼干渣):“啊…Gateway!因为能写Java过滤器,还能集成Sentinel…限流嘛,网关层QPS限1000,服务层用@SentinelResource再限500…最终一致性?呃…MQ发个消息,消费者重试?本地事务?不能不能,跨服务了!”✅ 面试官
谢飞机面Java大厂:电商秒杀场景下的Spring Cloud + Redis + Kafka 三轮暴击面试实录
面试官(推了推眼镜,手边是《阿里Java开发手册》和Prometheus监控大屏截图)
谢飞机(工牌歪戴,背包上贴着‘Hello World’贴纸,掏出半块奥利奥啃了一口)
🌟 第一轮:微服务拆分与流量入口 —— “你家秒杀服务,为啥不单体一把梭?”
面试官:我们电商中台正在重构秒杀系统。原单体应用QPS卡在1200就OOM,现在要拆成seckill-gateway、seckill-service、inventory-service、order-service四个Spring Boot微服务。你说说——
- 网关层用Spring Cloud Gateway还是Nginx?为什么?
inventory-service暴露的扣减库存接口,如何防止被恶意刷?需要哪几层限流?- 如果
order-service调用失败,怎么保证最终一致性?能用本地事务吗?
谢飞机(咽下饼干渣):“啊…Gateway!因为能写Java过滤器,还能集成Sentinel…限流嘛,网关层QPS限1000,服务层用@SentinelResource再限500…最终一致性?呃…MQ发个消息,消费者重试?本地事务?不能不能,跨服务了!”
✅ 面试官点头:“不错,网关选型和限流分层意识有,Sentinel整合也提到了——那你知道Gateway的全局过滤器GlobalFilter和普通Filter执行顺序吗?”
谢飞机(挠头):“这个…好像是…先Global再局部?或者反过来?哎哟我记混了…”
🌟 第二轮:高并发库存兜底 —— “Redis里那把锁,真能拦住10万人?”
面试官:秒杀开始瞬间,10万请求涌向库存服务。我们用Redisson的RLock做分布式锁,但上线后发现超卖了37件。
- 请画出标准Redis分布式锁加锁+续期+释放的时序图(文字描述也可);
- 如果Redis主从异步复制,master宕机前刚加锁,slave升主后锁丢失——这叫什么问题?怎么破?
- 缓存穿透怎么办?比如有人用-1或超长随机ID狂刷
/inventory/{id}?
谢飞机(猛灌一口冰美式):“加锁就是setnx对吧?过期时间设30秒…续期?好像有个看门狗…超卖?是不是没用lua脚本删锁?穿透?布隆过滤器!我简历写了!”
⚠️ 面试官皱眉:“布隆过滤器确实防穿透,但你初始化时怎么加载全量商品ID?如果商品百万级,布隆过滤器bit数组内存爆了怎么办?有没有更轻量的方案?”
谢飞机(眼神飘向窗外):“那个…可以…缓存空对象?但得设短过期…啊对!还可以…用Redis的…SCAN命令?不对不对…”
🌟 第三轮:削峰与可观测性 —— “Kafka消息积压200万条,你先喝口水再答”
面试官:秒杀成功后,订单创建走Kafka异步化。某次大促,order-create-topic积压200万条,下游消费延迟达8分钟。
- 你如何快速定位是生产者快、消费者慢,还是网络/磁盘瓶颈?
- 消费者用的是Spring Kafka
@KafkaListener,线程模型是单线程还是多线程?怎么提升吞吐? - 如果某条消息因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。”
📚 【附:小白也能懂的答案详解】
✅ 第一轮答案解析
- 网关选型:Spring Cloud Gateway(非Nginx)—— 因需动态路由、JWT鉴权、Sentinel熔断、灰度标透传等Java生态能力;Nginx适合静态资源和四层负载,七层逻辑弱。
- 三层限流:① CDN层(地域限流)、② API网关层(Sentinel QPS规则)、③ 库存服务层(Guava RateLimiter + 接口级@SentinelResource)。避免单点过载。
- 最终一致性:不能用本地事务(XA性能差且Spring Boot默认不支持)。正确方案:① Seata AT模式(推荐),② RocketMQ事务消息,③ Kafka + 本地事务表(记录待发消息,定时扫描补偿)。
✅ 第二轮答案解析
- Redis分布式锁标准流程:
- 加锁:
SET key random_value NX PX 30000(NX=不存在才设,PX=30秒自动释放) - 续期:Redisson看门狗机制,每10秒检查锁存在且持有者为自己 → 自动续期至30秒
- 释放:Lua脚本比对value再DEL,防误删
- 加锁:
- 主从切换导致锁丢失 → Redlock失效问题:本质是Redis不满足CP,无法提供强一致性锁。生产推荐方案:改用ZooKeeper(CP)或etcd,或接受AP场景下用“库存预热+本地缓存+数据库行锁”兜底。
- 缓存穿透轻量解法:
- ✅ 缓存空对象:
get(key)==null → set(key, "NULL", 2min),注意value用特殊标记(如"NULL"),避免和真实null混淆; - ✅ 参数校验前置:Controller层拦截负数、超长ID、非法字符;
- ⚠️ 布隆过滤器慎用:初始化需全量ID,可用RedisBloom模块(BF.RESERVE)或Caffeine本地布隆(适合读多写少)。
- ✅ 缓存空对象:
✅ 第三轮答案解析
- Kafka积压定位三板斧:
kafka-consumer-groups.sh --describe查lag;- Prometheus查
kafka_consumer_fetch_manager_records_lag_max(消费者拉取延迟) vskafka_producer_record_send_rate(生产速率); iotop/iostat看磁盘IO,iftop看网络带宽。
- Spring Kafka线程模型:默认单线程消费(一个
ConcurrentMessageListenerContainer含1个KafkaMessageListenerContainer)。提升吞吐:①concurrency=N启动N个独立消费者实例(注意分区数≥N),②max.poll.records=500+fetch.max.wait.ms=500批量拉取。 - 死信处理:
- Kafka无原生DLQ,需自建
order-create-dlq-topic; - 在
@KafkaListener中捕获异常 → 发送至DLQ topic → 单独服务监听DLQ做人工干预或告警; - 关键细节:
MANUAL_IMMEDIATE(收到消息立即触发ack)比MANUAL(需显式调用ack.acknowledge())更安全,避免重复消费。
- Kafka无原生DLQ,需自建
💡 一句话总结秒杀技术栈闭环: Gateway限流 → Redisson锁+空值缓存防穿透 → Kafka削峰 → Seata/本地事务表保一致 → Prometheus+Kibana全链路监控
📌 延伸学习路径:
- 动手实验:GitHub搜
spring-cloud-alibaba-seckill-demo(含完整代码+压测脚本) - 避坑清单:《阿里秒杀系统11个致命错误》PDF(评论区留言“秒杀”获取)
- 面试彩蛋:当被问“如果让你重构,会引入R2DBC吗?”——答:“读多写少场景可试,但秒杀核心链路仍优先保障MySQL稳定性,R2DBC价值在长连接耗尽场景。”
本文由CSDN「Java架构进阶」专栏出品 | 谢飞机友情出演,面试官原型来自某Top3电商中间件团队
更多推荐
所有评论(0)