Skip to content

【面试题】1. 秒杀场景下分布式事务怎么做?TCC、可靠消息最终一致、本地消息表你怎么选? ​

【标准答案】秒杀链路涉及"扣库存 + 创订单 + 发优惠券"三个动作,跨了缓存、MQ、DB 三个系统,必须保证最终一致。

TCC(Try-Confirm-Cancel): Try 阶段冻结库存、预占优惠券,Confirm 阶段全部确认生效,任何一步失败走 Cancel 回滚。优点是一致性强、实时性好,缺点是业务侵入大——库存服务、订单服务、优惠券服务都要实现 TCC 接口。秒杀场景下 TCC 的自定义回滚逻辑太重,不推荐。

可靠消息最终一致(推荐): 扣库存成功后发事务消息到 RocketMQ,订单服务消费消息创建订单。RocketMQ 的事务消息保证"扣库存成功 → 消息一定发出",消费者做幂等处理。优点是解耦、轻量,缺点是弱一致——订单可能有秒级延迟。秒杀对此容忍度高,是最佳方案。

本地消息表: 扣库存的同时在本库写一条"待发送消息"记录,定时任务扫表发送并更新状态。不需要事务消息支持,任何 MQ 都能用,但多了数据库写开销。适合没有 RocketMQ 的团队。

一句话: 有 RocketMQ 用事务消息,没 RocketMQ 用本地消息表,TCC 太重秒杀别用。


【面试题】2. 秒杀系统上线前,JMeter 全链路压测怎么做?QPS 你是怎么推算的? ​

【标准答案】压测不是随便发几万请求跑完就收工,要回答三个问题:能扛多少、哪里先挂、边界在哪。

第一步:单接口基准压测。 先不压全链路,逐个接口"摸高"——下单接口极限 QPS、扣库存接口极限 QPS、Nginx 直连 Redis 的极限。找出各环节的天花板。比如 Redis Lua 扣库存能跑 5 万 QPS,数据库订单写入只能跑 2000 QPS,瓶颈在 DB。

第二步:全链路混合压测。 构造真实用户行为链:打开页面 → 点秒杀按钮 → 提交订单。梯度加压,每 30 秒加 1000 QPS,记录 RT、错误率、各层 CPU。找到 99 分位 RT 开始恶化的拐点——这就你的水位线。

第三步:QPS 推算公式。 假设 1000 件库存、预计 20 万人抢、秒杀窗口 60 秒。峰值 QPS ≈ 20 万 × 点击率(30%)÷ 60 秒 ≈ 1000 QPS。但要按 3-5 倍冗余准备——实际按 3000-5000 QPS 设计容量。因为真人行为有尖刺,不是均匀分布。高峰可能在第 1 秒冲进来 30% 的人。

第四步:破坏性压测。 关掉一个 Redis 从节点、模拟 MQ 延迟 5 秒、杀一个应用实例。看系统表现——限流是否生效、熔断是否触发、用户体验是否可控。正常压测是及格线,混沌工程才是高分。

核心: 压测结果要有回放报告,明确写出"当前系统可承载 X QPS,瓶颈在 Y 环节,优化后可达 Z QPS"。


【面试题】3. 下单链路怎么异步化和并行化?减少 RPC 调用有哪些实际手段? ​

【标准答案】下单链路长是 RT 高的元凶——用户等结果的时间 = 所有串行 RPC 时间之和。优化方向就一个:能并行的不串行,能异步的不等待。

并行化手段: 扣库存、校验资格、风控打分三个服务彼此不依赖,可以用 CompletableFuture 同时调出。CompletableFuture.allOf(stockFuture, qualifyFuture, riskFuture).join(),总耗时 = max(三个中最慢的) 而不是三者和。三个服务各 10ms,串行 30ms,并行 10ms。

异步化手段: 订单创建、优惠券发放、积分增加不需要同步返回给用户。扣库存成功后立刻返回"抢到了",剩下的丢 MQ 异步消费。用户不需要等到订单入库才看到结果——前端显示"订单处理中",最多 2 秒轮询一次订单状态。

减少 RPC 调用: ① 合并接口——先看下游是否提供了批量接口,一次查 10 个商品而不是调 10 次。② 本地缓存——商品基本信息用 Caffeine 本地缓存,不过 RPC。③ 请求裁剪——下游返回的字段只取业务需要的,大对象序列化开销很可观。④ 异步日志——日志写操作不阻塞主链路,用异步 Appender。

效果: 一个 300ms 的下单链路,并行化 + 异步化后可以压到 50ms 以内,用户体验从"卡"变成"秒回"。


【面试题】4. 库存回滚和补偿机制怎么设计?什么情况下必须回滚? ​

【标准答案】库存扣了但订单没生成,就必须回滚——这是库存一致性的兜底线。

触发回滚的场景: ① 扣库存成功后 MQ 发消息失败——库存已经少了但订单没影,必须还回去。② 订单创建失败(DB 异常、业务校验不通过)。③ 订单超时未支付取消。④ 风控后验判定作弊取消订单。

回滚方案一:Redis 直接加回(快恢复)。 用 Lua 脚本原子做 incrby,加回时也要带限值——不能超过原始库存,防止回滚溢出。比如原始库存 1000,回滚完不能变成 1001。适合支付超时取消这种高频回滚场景。

回滚方案二:补偿消息驱动。 需要回滚的场景统一发一条"补偿消息"到 MQ,消费端执行 Redis incr + 记录回滚日志。好处是统一入口、有迹可循,坏处是多了一跳。适合对账发现差异后的批量回滚。

回滚日志(必备): 每次回滚都记录:哪个订单回滚的、回滚了几件、回滚时间、回滚原因。这是对账的依据,出问题时能追溯每一个回滚操作的来龙去脉。

关键设计: 回滚必须幂等——同一条回滚消息消费两次不能加两次库存。用"订单号 + 回滚版本号"做幂等校验,Redis 里存已回滚的订单集合,重复的直接跳过。


【面试题】5. 秒杀 URL 为什么要隐藏?怎么防提前下单? ​

【标准答案】如果秒杀 URL 提前暴露,黄牛脚本可以在活动开始前就把接口跑通,倒计时形同虚设。

URL 动态化策略: 秒杀 URL 不是固定的 /seckill/iphone15,而是每次活动生成随机后缀:/seckill/iphone15?token=a3f9c2b1d4e6。token 在秒杀开始前 1 秒才下发给前端,提前拿到也没用——服务端校验 token 有效期。

完整防提前下单三层:

第一层:服务端时间门禁。 收到下单请求时,第一步就校验 serverTime >= startTime。即使有人猜到了 URL 和 token,时间没到也拒绝。返回"活动未开始"而不是 404,不暴露接口是否存在。

第二层:动态 Token。 每个用户的 token 不同,同一用户每次进页面 token 也不同。token 包含用户 ID + 商品 ID + 场次时间戳,加密签名防篡改。后端验签通过才放行。

第三层:秒杀窗口控制。 token 只在秒杀窗口内有效(如 10:00:00 到 10:01:00),过期自动失效。即使 token 泄露了,过了窗口也没用。

前端配合: 倒计时归零前一秒,前端才异步请求获取动态 URL 和 token,拿到后立即发起下单——用户侧感觉是秒杀开始就能点,实际上提前 500ms 都拿不到地址。

一句话: URL 是秒杀的门锁,不隐藏等于敞开大门。动态 token + 时间门禁 + 窗口控制,三道锁守住。


【面试题】6. 分布式 ID 在秒杀订单中怎么生成?雪花算法和美团 Leaf 各有什么优劣? ​

【标准答案】秒杀订单 ID 要求:全局唯一、趋势递增(B+Tree 友好)、高性能、不依赖数据库自增锁。

雪花算法(Snowflake): 64 位 Long 型 ID,1 位不用 + 41 位时间戳 + 10 位机器 ID + 12 位序列号。单机每毫秒 4096 个 ID,分布式不碰撞。优点是不依赖任何外部服务,纯内存生成,极快。缺点是强依赖机器时钟——时钟回拨会生成重复 ID。

时钟回拨怎么解决? 雪花算法的经典坑。方案:① 启动时记录当前时间,发现时钟回拨就等待或抛异常。② 用 NTP 同步时设 -x 参数做微调而非跳变。③ 美团的 Leaf-snowflake 方案:用 ZooKeeper 持久顺序节点注册 workerId,启动时上报时间戳,中心节点发现时钟回拨就给一个新的 workerId。

美团 Leaf 方案: 分两种模式。Segment 模式——从数据库批量取号段(如一次取 1000 个),缓存在内存里用完再取,适合订单号。Snowflake 模式——用 ZK 解决时钟回拨。Leaf 比原生雪花多了一层号段缓存和 ZK 依赖,部署重但可靠性高。

秒杀场景选型: 自建雪花算法够用——秒杀机器都是 NTP 同步的,时钟回拨概率极低。加上数据库唯一索引兜底,即使极低概率重复也能在写入时拦下来。如果公司已有 Leaf 基础设施,直接用 Segment 模式。


【面试题】7. Nginx + Lua 能做哪些前置校验?为什么放在网关层做? ​

【标准答案】Nginx/Lua 前置校验的核心价值:在请求进入 Java 应用之前就拦掉无效流量,省掉 JVM 开销。

能做的校验有五类:

① 时间窗口校验: Lua 代码拿到当前服务端时间,对比秒杀开始/结束时间。提前来的请求直接返回 403,不往上游透传。Nginx 每秒能处理 10 万级这种轻量校验。

② 用户黑名单校验: Lua 通过 lua-resty-redis 连 Redis,查 SISMEMBER seckill:blacklist userId。命中黑名单的返回 403,毫秒级。

③ 频控令牌桶: 用 Redis 实现令牌桶算法,每个用户每秒只生成 5 个 token。Lua 先 GET 令牌数,够就 DECR,不够就 429。这个逻辑放 Nginx 层,一台 Nginx 能扛 5 万 QPS 频控。

④ URL Token 验签: 校验请求中的动态 token 签名是否合法。HMAC-SHA256 签名的校验 Lua 也能做。

⑤ 商品合法性校验: 查 Redis 秒杀商品集合,请求的商品 ID 不在集合里直接 404,防随机 ID 探测。

为什么放网关? 省资源——一个 Nginx worker 处理 10 万 QPS,同等 QPS 需要 20 台 Java 应用服务器。把这五类校验前移,Java 层收到的就是纯合法请求,专注业务逻辑。分层防御,便宜活让便宜层干。


【面试题】8. 秒杀动静分离怎么落地?CDN 能缓存到什么程度? ​

【标准答案】动静分离的原理很简单:不变的东西放 CDN,变的东西用 Ajax 异步拿。秒杀场景落地三件事:

静态资源全 CDN(源站零请求): HTML 骨架、CSS、JS、商品图片、Logo 全部推到 CDN,设置长缓存时间(30 天)。用户打开秒杀页面只和 CDN 边缘节点交互,源站一台机器都不碰。

动态数据 Ajax 异步加载(与静态分离): 页面上只有两个会变的数据——倒计时和实时库存。倒计时用服务端下发的时间戳客户端本地算,不需要持续请求;库存数字每 2 秒 Ajax 轮询一次。这两个 Ajax 请求的量级远小于完整页面刷新。

首屏直出 + 骨架屏(极致体验): 秒杀页面的 HTML 也可以 CDN 缓存,里面不含任何动态数据,就是个空壳。用户打开瞬间看到骨架屏,然后 Ajax 填充商品信息和价格。既快又灵活。

CDN 的边界: 能缓存的一定是公共的、不变的。做不到的是:用户登录状态、个性化推荐、实时库存。但只要把 95% 的页面体积(图片 + CSS + JS)放 CDN,剩下 5% 的动态请求源站轻松应对。

压测验证: 秒杀开始前用 curl 测 CDN 命中率,必须 > 99%。如果 CDN 回源率过高,说明缓存策略有问题——常见坑是 CDN 缓存 key 带了不必要的 query 参数。


【面试题】9. 热点 Key 怎么识别?发现后怎么处理?多级缓存怎么搭? ​

【标准答案】热点 Key 是秒杀系统的头号杀手——一个 Key 扛着 90% 流量,单个 Redis 节点 CPU 打满,拖死整个集群。

怎么识别? 三招:① redis-cli --hotkeys 扫描内存中访问频率最高的 Key。② 客户端埋点——Jedis/Redisson 在发送命令前统计 Key 的调用次数,滑动窗口聚合,Top 100 告警。③ 业务预判——像 iPhone 秒杀这种,不用识别也知道是热点,活动开始前就标记。

发现后怎么处理?

方案一:本地缓存前置(Caffeine)。 把热点数据缓存在 Java 进程本地,TTL 设 1-3 秒。读请求先走本地,命中后不发 Redis 请求。一个应用实例的本地缓存能卸载 90% 的 Redis 读压力。缺点是多实例间数据不一致——秒杀库存这种实时数据不适合放本地,适合商品详情、用户信息。

方案二:Redis 多副本读。 热点 Key 在多个从节点上各存一份,客户端随机选一个从节点读。写还是走主节点,从节点异步复制,延迟可接受。

方案三:多级缓存金字塔。 Caffeine 本地(L1,微秒级)→ Redis(L2,毫秒级)→ DB(L3,秒级)。热点数据在 L1 命中率 95% 以上,大部分请求不出 JVM。

方案四:Key 拆分(写热点)。 像库存这种读写都是热点的 Key,一个 Key 扛不住写,拆成 10 个分片 Key。扣减时随机选一个分片,定时汇总总量。牺牲一点准确度换性能。

一句话: 热点不可怕,怕的是没发现。识别 → 本地缓存卸载读 → 分片卸载写,三步搞定。


【面试题】10. 面试官问:"给你两周时间,四个人,从零开发秒杀系统,你怎么排期?" ​

【标准答案】这不是问技术,是问项目管理能力。两周四人必须取舍——不追求完美,只追求能上线。

第一周:核心链路 + 压力验证(跑通 > 完美)。

  • Day1-2(全员): 搭项目骨架。Spring Boot + Redis + MySQL + RocketMQ,数据库表建好,基础 CRUD 跑通。一人负责环境搭建,三人写核心代码。
  • Day3-4(分兵): A 负责 Redis Lua 库存扣减 + 防超卖;B 负责 MQ 异步下单 + 乐观锁兜底;C 负责前端页面 + 按钮防抖;D 搭 Nginx + 限流配置。
  • Day5-6: 联调全链路。写压测脚本,用 JMeter 先跑 500 QPS,看瓶颈在哪,优化到 2000 QPS。
  • Day7: 缓存预热脚本开发 + 监控大盘搭建。准备好上线检查清单。

第二周:稳定性 + 风控 + 演练(系统不会崩 > 功能多)。

  • Day8-9: 熔断降级 + 幂等性 + Token 防刷。每个都加降级开关,配到 Apollo。
  • Day10-11: 风控——布隆过滤器 + IP 黑名单 + 设备指纹。用第一周的压测流量跑风控规则。
  • Day12: 全链路压测 5000 QPS + 混沌工程(关 Redis/杀 MQ/限流突变)。
  • Day13: 应急预案文档 + 操作手册 + 灰度上线 10% 流量验证。
  • Day14: 全量上线 + 运维值班表。

如果砍需求,优先级是: 核心扣减 > 防超卖 > 限流 > MQ 异步 > 风控 > 用户体验优化。核心链路必保,其他的上线后迭代。

一句总结答完面试官: 两周四人做秒杀,不追求架构完美,追求两周后能扛住 2000 QPS 且不多卖一件货。核心链路四天跑通,压力验证两天找瓶颈,剩下时间全做稳定性加固。先活下来,再做漂亮。