真实面经与复盘 列表全部文章
真实面经与复盘

面经复盘:电商高并发方向一场完整面试:秒杀、库存、订单、缓存一致、MQ 削峰、分库分表、限流降级

根据公开面经整理与改编:电商/高并发后端岗一场完整面试,从秒杀整体设计到库存扣减、订单、缓存一致性、MQ 削峰、分库分表、限流降级,逐题给出回答要点、追问链与踩坑。

更多

照例先声明:本文是“根据公开面经整理与改编”的一场典型面试,不是我本人的亲历,也不指向任何具体公司或面试官。我读了近一年公开的秒杀、高并发、系统设计类面经与技术文章,发现电商方向的面试有一个特别稳定的“套路”:从一个秒杀题起头,然后顺着“库存 → 订单 → 缓存 → MQ → 分库分表 → 限流降级”一路追下去,像剥洋葱。所以我把它们串成了一场完整的面试,问答是拼的,题目和追问是反复出现的,回答思路是我的点评。

电商面试还有一个特点:面试官特别在意“钱和货”。少卖是损失机会,超卖是赔钱加投诉,重复扣款是事故。所以你会发现,几乎每一个方案的追问最后都指向同一个问题:“它出错了怎么办?你怎么发现?怎么修?”这也是本文的主线。

一、面试流程总览

这场面试按一小时算,节奏大致是:开场与项目(10 分钟)→ 秒杀总体设计(15 分钟)→ 库存与订单细节(15 分钟)→ 缓存、MQ 与分库分表(15 分钟)→ 限流降级与压测复盘(5 分钟)。下图是它的时间线和请求漏斗。

上半:面试时间线(约 60 分钟) 开场 · 项目 10 分钟 · 数字与角色 秒杀总体设计 15 分钟 · 漏斗思路 库存 · 订单 15 分钟 · 超卖与幂等 缓存 · MQ · 分库分表 15 分钟 · 一致性与扩展 限流 · 降级 5 分钟 · 压测与预案 下半:秒杀请求漏斗(离数据库越远越好) CDN / 前端 静态化 · 防重复点击 网关限流 令牌桶 · 验证码 Redis + Lua 原子扣减 · 一人一单 MQ 削峰 异步下单 · 可重试 MySQL 兜底 唯一索引 · 乐观锁 10 万请求 → 网关放行 1 万 → Redis 判定 10 个有资格 → MQ 里 10 单 → 库里 10 行 每一层都在“丢掉”绝大多数请求,这就是漏斗

二、开场与项目(约 10 分钟)

考察目标:面试官先摸清你的“量级”:你做过的系统到底多大?后面的深度会照着这个量级来调节。

Q1:先介绍下你做过的最高并发的系统,QPS 多少,你负责什么?

参考回答要点:给出数字 + 峰值形态 + 你的职责 + 一个技术难点。例如“大促零点下单接口峰值约 X 千 QPS,我负责库存与下单链路,难点是热点商品库存扣减”。数字要真实,被追问“压测怎么做的、瓶颈在哪”必须答得出。

追问:“平均 QPS 和峰值差多少倍?”“瓶颈最后在哪一层?”——电商的答案往往是“热点商品的单行/单 key 争用”,而不是机器数量。

踩坑:夸大数字。面试官会用“那每秒写库多少次、连接池多大、Redis 单实例多少 QPS”来验算,露馅很快。

三、第一板块:秒杀总体设计(约 15 分钟)

考察目标:看你的架构框架:是否理解“削峰、隔离、异步、兜底”,是否知道每一层的职责与失败模式。

Q2:设计一个秒杀系统,10 万人抢 10 件商品

参考回答要点:先澄清需求,再分层。澄清:商品数量、活动时长、是否一人一单、是否需要排队、支付时限、是否允许少量超时未支付回补。核心原则:层层漏斗,把绝大多数请求挡在离数据库最远的地方。

  1. 入口层:活动页静态化走 CDN;按钮防重复点击与倒计时(服务端时间校准);验证码/答题打散瞬时流量。
  2. 网关层:按 IP/用户/接口限流;黑名单与风控拦截脚本请求。
  3. 预热:活动开始前把商品信息、库存写入 Redis,并在应用本地缓存商品静态信息。
  4. 资格判定:Redis + Lua 一次完成“库存是否充足、用户是否已抢、扣减库存、记录用户”,返回是否抢到。
  5. 异步下单:抢到资格的请求投递到 MQ,消费者创建订单;前端拿到“排队中”,通过轮询或推送获取最终结果。
  6. 数据库兜底:订单表 (用户, 活动/商品) 唯一索引防重复;库存表条件更新 where stock > 0 防超卖。

公开的架构文章里有个数据我觉得很直观:把瞬时上万 QPS 的写请求放进队列,数据库按自己的承受能力(比如两千 QPS)消费,用户端换来的是几十秒内的可接受延迟;同时把“订单 + 库存”同步处理、把“优惠券、积分、推送”等次要动作异步化,响应时间可以从数百毫秒降到几十毫秒。

追问:“为什么不直接用数据库行锁扣库存?”——热点行锁串行化,10 万请求排队会占满连接池,DB 先崩;“为什么不用 Redis 分布式锁包住 DB 操作?”——锁本身是单点争用,吞吐上不去,而且锁过期后仍有超卖风险,不如让 Redis 原子操作直接承担判定。

加分:主动画出漏斗、给出每层“放行比例”的估算,并说明“抢不到的请求尽早返回,不占后端资源”。

Q3:Redis 里怎么扣库存才不会超卖?

参考回答要点:单个 DECR 是原子的,但业务需要“检查 + 判重 + 扣减 + 记录”多步,必须放进 Lua 脚本,让它们作为一个整体在 Redis 单线程里执行:

-- KEYS[1] = stock:{sku}   KEYS[2] = bought:{sku}   ARGV[1] = userId
if redis.call('sismember', KEYS[2], ARGV[1]) == 1 then
    return 2                                  -- 已抢过
end
local stock = tonumber(redis.call('get', KEYS[1]) or '0')
if stock <= 0 then
    return 0                                  -- 没库存
end
redis.call('decr', KEYS[1])
redis.call('sadd', KEYS[2], ARGV[1])
return 1                                      -- 抢到

Java 侧用 RedisScript 执行,返回值决定后续逻辑。

Long r = redis.execute(SCRIPT, List.of("stock:{1001}", "bought:{1001}"), String.valueOf(userId));
if (r == 1L) mq.send("seckill-order", new OrderMsg(userId, 1001L, requestId));
return r == 1L ? Result.queueing(requestId) : Result.fail(r == 2L ? "已抢购" : "已售罄");

追问:

  • “Redis Cluster 下 Lua 里的多个 key 怎么办?”——Lua 涉及的 key 必须落在同一个槽,用 hash tag(如上面的 {1001})保证同槽;
  • “单个热点 key 撑不住?”——把库存拆成 N 段(stock:1001:0…N-1),随机/取模分段扣减,售罄判断要额外处理“某一段没了但别的段还有”;
  • “Redis 挂了或者主从切换,库存会不会多卖?”——异步复制下切换可能丢最近的扣减记录,所以数据库层的条件更新与唯一索引是最后防线,并在活动结束后做 Redis 与 DB 的库存对账。

踩坑:先 get 再判断再 decr(三次网络往返,非原子);或者用 DECR 后发现负数再 INCR 回补,高并发下短暂为负会误导别的读取。

Q4:怎么做到“一人一单”?

参考回答要点:多层:Lua 里用 Set 判重(快速拦截);订单表建 (user_id, sku_id/活动ID) 联合唯一索引(终极保证);消息消费端以 requestId 或业务唯一键做幂等,重复消息命中唯一索引时视为成功而不是报错重试。

追问:“Redis 里记录用户的 Set 会很大吗?”——按活动维度且活动结束后过期清理;参与人数极大时可改用 bitmap 或布隆过滤器做一层预判,但布隆有误判,不能作为唯一依据。

Q5:抢到资格但订单没创建成功,或用户没支付,库存怎么办?

参考回答要点:库存的“预占 → 确认/释放”模型:抢到时预占(Redis 扣减),订单创建成功且在支付时限内支付,则确认;创建失败(消息重试耗尽进死信)或超时未支付,则释放库存(Redis 加回,数据库库存回补),并从“已抢用户”集合移除,允许其再次参与(视业务规则)。回补必须幂等:用订单号做回补幂等键,避免一个订单被回补多次导致库存虚增。

追问:“超时未支付怎么触发?”——延迟消息(MQ 延迟队列)为主,低频扫描兜底;取消动作要用状态机条件更新(where status = 'UNPAID'),避免与支付回调并发时“已支付又被取消”。

四、第二板块:订单与库存落库(约 15 分钟)

考察目标:看你对“交易核心数据”的严谨度:ID、状态机、事务边界、幂等、对账。

Q6:订单号怎么生成?为什么不直接自增?

参考回答要点:自增 ID 在分库分表后不再全局唯一、还会暴露业务量。方案:雪花算法(时间戳 41 位 + 机器 10 位 + 序列 12 位,趋势递增,注意时钟回拨)、号段模式(批量取号,双 Buffer 预加载减少取号抖动)、或在订单号里嵌入分片基因(例如把用户 ID 的低几位拼进订单号),使得“按订单号查”与“按用户 ID 查”能路由到同一分片。

追问:“时钟回拨怎么办?”——小幅回拨等待追平,大幅回拨拒绝服务并告警,或切换 workerId;“订单号要不要带日期业务含义?”——可读性好但注意别泄露敏感信息。

Q7:下单流程里,库存、订单、优惠券、积分的事务怎么处理?

参考回答要点:不要追求一个大分布式事务。核心强一致的放一个本地事务(订单 + 库存扣减,可以在同一库或同一服务内),次要的走异步最终一致(优惠券核销、积分、通知)。异步部分用事务消息或本地消息表保证“至少一次”,消费端幂等。失败补偿:优惠券核销失败要有回滚/重试机制并可人工介入。

追问:“RocketMQ 事务消息怎么工作?”——先发半消息(对消费者不可见)→ 执行本地事务 → 提交或回滚半消息;Broker 长时间收不到结果会回查生产者本地事务状态。要点是回查接口必须能根据业务数据判断事务是否成功;“为什么不用 Seata?”——强一致成本高、对高并发热点不友好,除非资金类必须强一致,才谨慎使用并接受吞吐折损。

Q8:库存扣减在数据库层怎么写?

update inventory
set stock = stock - #{n}, version = version + 1
where sku_id = #{skuId} and stock >= #{n};
-- 影响行数为 0 表示库存不足,直接失败,不需要先查再改

参考回答要点:一条 SQL 同时完成校验和扣减,避免先查后改的竞态;配合 InnoDB 行锁,热点商品仍是串行,所以只用作“通过 Redis 层后的最终写入”,把 DB 的写压力降到“有资格的请求数”。还要有库存流水表(谁、何时、因何、扣了多少),用于对账和回补幂等。

追问:“热点行还是慢怎么办?”——库存分桶(把一个 sku 的库存拆到多行,扣减时挑一行)、合并写入(批量聚合扣减)、或者在应用内排队串行化同一 sku 的扣减;“对账怎么做?”——定时对比“初始库存 − 已确认订单量 − 预占中 = 当前库存”,发现偏差告警并按流水修复。

Q9:订单状态机怎么设计?为什么重要?

参考回答要点:订单有待支付、已支付、已发货、已完成、已取消、退款中等状态,状态流转必须有白名单(如待支付只能到已支付或已取消),每次流转用条件更新(where id=? and status=?)并写状态变更日志。这样并发的“支付回调”“超时取消”“用户取消”不会互相踩踏,重复回调也天然幂等。

追问:“支付回调重复、乱序怎么办?”——以支付平台流水号做幂等,回调里不信任金额,主动查单核对;“状态卡在中间态怎么办?”——补偿任务扫描长时间处于中间态的订单,主动查询上游结果并推进。

五、第三板块:缓存一致性、MQ 与分库分表(约 15 分钟)

考察目标:看你对“三件套”的组合使用能力,以及每一个的失败模式。

Q10:商品详情走缓存,库存变了缓存怎么保证一致?

参考回答要点:先分层看待数据:商品静态信息(标题、图片)可接受较长缓存与最终一致;库存、价格敏感,展示可以用缓存近似值(“仅剩少量”),真正下单时以库存判定为准,不以展示为准。缓存更新采用 Cache Aside:更新数据库后删除缓存,设置 TTL 兜底;多业务入口修改同一份数据时,更好的做法是订阅数据库 binlog(如 Canal)由独立组件统一失效缓存,业务代码不再关心缓存维护,并配合失败重试。

追问:“为什么先更新库再删缓存,而不是先删缓存?”——先删缓存后,并发读会把旧值回填,形成长期脏数据;“删缓存失败怎么办?”——重试队列/binlog 订阅重投,加 TTL 兜底;“热点 key 过期瞬间被打穿?”——互斥重建或逻辑过期(返回旧值,后台异步刷新)。

Q11:缓存穿透、击穿、雪崩,电商里各怎么防?

问题电商场景防护
穿透恶意请求大量不存在的商品 ID参数校验、缓存空值(短 TTL)、布隆过滤器
击穿爆款商品缓存过期瞬间大量请求打到 DB互斥重建、逻辑过期、热点永不过期 + 主动刷新
雪崩大批商品缓存同一时间过期,或 Redis 整体不可用TTL 加随机值、多级缓存(本地 + Redis)、限流降级、Redis 高可用

加分:提到热 key 探测,对超级爆款在应用本地缓存做短 TTL 副本,把 Redis 单 key 的压力摊到每个应用实例。

Q12:MQ 削峰具体怎么做?积压了怎么办?

参考回答要点:削峰的本质是把“瞬时写”变成“匀速写”:生产者快速入队、消费者按数据库能承受的速率消费。设计时要估算:峰值入队速率、消费能力(消费者数 × 单消费者吞吐)、可接受的最大延迟。分区/队列数决定并行度上限,要提前规划。

可靠性三件套:消费者处理成功后才 ACK;失败重试到上限进入死信队列,由人工或补偿任务处理;数据库唯一索引保证重复消息不会产生重复订单。

积压处理:先判断原因——生产暴增(预期内)还是消费变慢(异常)。临时扩容消费者(受分区数限制,必要时先扩分区或转发到临时的高分区 topic);排查慢消费根因(下游慢、数据库锁等待);对低优先级消息降级或延后处理。同时有积压量、消费延迟的告警,而不是等用户投诉才发现。

追问:“顺序怎么保证?”——同一订单/同一 sku 的消息路由到同一分区,单分区顺序消费;“消息丢了怎么发现?”——生产端发送结果检查与重试、业务侧对账(抢到资格数 vs 订单数)。

Q13:订单表要分库分表了,你怎么设计?

参考回答要点:先说触发条件(单表体量、写入瓶颈、容量增长,而不是“行数过千万就分”),再说设计:

  1. 分片键:订单场景通常按 user_id 分片,因为“我的订单”是最高频查询;订单号里嵌入分片基因,使按订单号查询也能路由。
  2. 分片算法:取模/哈希路由简单但扩容要迁移;一致性哈希或“逻辑分片数远大于物理库数”(如 1024 个逻辑表映射到若干库),扩容时只迁移逻辑分片到新库,不改路由规则。
  3. 跨维度查询:商家要按商家 ID 查订单,而分片键是用户 ID,怎么办?——建异构索引:把订单数据通过 binlog/MQ 同步一份按商家分片的表(或 ES),商家端查询走这份;代价是最终一致。
  4. 全局唯一 ID:雪花/号段,不使用库自增。
  5. 跨库事务与 join:尽量避免;订单与订单明细用相同分片键放同一库;必须跨库的用最终一致方案。
  6. 中间件:ShardingSphere 一类的客户端分片/代理方案,注意 SQL 兼容范围、分页与排序在多分片上的归并成本。

追问:“怎么平滑迁移,不停机?”——存量数据全量迁移 + 增量同步(binlog)→ 数据校验 → 灰度切读 → 双写 → 切写 → 观察后下线旧表;“分页查询跨分片怎么办?”——业务上限制翻页深度,或用游标;“数据倾斜怎么办?”——大商家/大用户单独处理,或调整分片键与路由。

踩坑:分片键选了订单号却没有分片基因,导致按用户查订单要扫所有分片;或者一上来就分库分表,没有先考虑归档与读写分离。

六、第四板块:限流降级与压测复盘(约 5 分钟)

Q14:限流算法有哪些?你怎么选?

参考回答要点:

算法特点适用
固定窗口实现最简单,窗口边界可能出现 2 倍突刺粗粒度限制
滑动窗口更平滑,需要记录时间片或请求接口/用户级精确限流
漏桶输出速率恒定,平滑流量,不允许突发保护下游,整流
令牌桶允许一定突发(桶容量),平均速率受控入口限流,最常用

分布式限流可用 Redis + Lua 实现计数或令牌桶,网关层用 Sentinel、Nginx 限流等组件。要区分单机限流(简单、无网络开销、总量随实例数变化)与集群限流(全局精准、依赖中心组件)。

追问:“限流阈值怎么定?”——压测得到系统容量,乘 70%–80% 的安全系数;“被限流的用户体验?”——快速失败提示、排队页,而不是让请求超时。

Q15:降级和熔断怎么做?举例说明

参考回答要点:熔断是针对下游异常的自我保护:错误率或慢调用比例超过阈值时断开一段时间,快速失败,再半开探测恢复;降级是主动放弃非核心功能保核心:大促时关闭推荐、评价、积分展示,商品详情走静态兜底页。要点是预先定义降级开关与优先级:哪些是核心链路(下单、支付、库存),哪些可降级,降级后的兜底内容是什么,开关能否秒级生效。

追问:“熔断后怎么恢复?”——半开状态放少量探测请求,成功则恢复;“下游超时重试会不会加重问题?”——会,重试要有上限、退避与预算,避免重试风暴。

Q16:怎么证明你的系统能扛住大促?

参考回答要点:全链路压测(使用影子库/影子表与压测标记隔离真实数据)、容量评估(各层水位与扩容预案)、故障演练(Redis 主从切换、MQ 积压、下游超时)、上线前检查清单(限流/降级开关、告警、值班、回滚方案)。大促期间盯核心指标:下单成功率、库存对账差异、MQ 积压、DB 连接与慢查询。

六点五、加时赛:面试官临场追加的几道细节题

公开面经里很常见的一种情况:主线问完,面试官会在剩下的时间里从你的回答里挑几个“看着不太扎实”的点追问。下面是这类加时题里出现频率较高的几道。

Q17:怎么防止脚本刷单、黄牛?

参考回答要点:没有一招制敌,是多层组合:(1)账号维度:实名/绑定手机、账号等级与历史行为,新号限制;(2)设备与 IP 维度:设备指纹、IP 频率限制、代理与机房 IP 识别;(3)行为维度:验证码/滑块/答题,把“瞬间高频点击”变成“需要人类思考”的动作,同时起到打散流量的作用;(4)业务规则:一人一单、限购数量、收货地址/手机号维度限购;(5)事后:异常订单标记与拦截,风控团队复核。技术侧要意识到:风控规则要放在漏斗靠前的位置,而且不能依赖数据库查询,否则风控本身就会成为瓶颈,通常用 Redis 计数与本地规则引擎实现。

追问:“误伤正常用户怎么办?”——分级处置(提示验证 → 限速 → 拒绝),并提供申诉通道;“规则怎么快速更新?”——规则下发到配置中心,热更新,不发版。

Q18:秒杀开始前,库存怎么初始化到 Redis?会不会有“Redis 数据和库不一致”的时刻?

参考回答要点:活动前由定时任务或运营操作触发“预热”:从库存表读取可售库存写入 Redis,同时写入一个“预热完成”标记,秒杀入口在标记存在时才放行。预热与活动开始之间,库存不应被其他渠道修改(可以锁定该 sku 的库存变更入口,或者把活动库存单独划拨出来,与普通渠道库存隔离)。“活动库存独立划拨”是很实用的做法:从总库存里划出 N 件给秒杀,秒杀内部的超卖风险不会波及普通销售,回补也有明确去处。

追问:“活动过程中运营想加库存怎么办?”——通过受控接口增加 Redis 与数据库库存,并写库存流水,不允许直接手改缓存;“活动结束后 Redis 剩余库存怎么处理?”——把未售出的数量回写数据库并核对,然后删除 Redis 相关 key。

Q19:用户点了抢购,怎么让他知道结果?轮询会不会把服务打垮?

参考回答要点:抢到资格时返回一个请求 ID,订单异步生成。结果通知的方式:(1)前端轮询(间隔逐步拉长,如 1 秒、2 秒、3 秒,并设置总时长上限),服务端查询走 Redis 中的结果 key(请求 ID → 状态),不直接查库;(2)WebSocket/长连接推送,适合体验要求高的场景,但连接管理成本更高;(3)短信/站内信作为兜底通知。轮询接口本身也要限流,且结果 key 要有过期时间。

追问:“结果 key 写到哪一步?”——消费者创建订单成功后写“成功 + 订单号”,失败写“失败 + 原因”;未消费到时状态是“排队中”;“用户一直看到排队中怎么办?”——设定最长等待,超时提示并引导查看“我的订单”,同时后台补偿任务扫描长时间未处理的请求。

Q20:主从复制延迟下,读写分离会带来什么问题?电商里怎么规避?

参考回答要点:典型问题:用户刚下单,立刻查订单列表,从库还没同步,看不到订单。规避:(1)关键读走主库(如“下单后的订单详情页”);(2)写后一段时间内该用户的读强制走主库(用 Redis 记录标记,几秒过期);(3)用“等待复制位点”的方案,但实现复杂;(4)把强一致读的范围收窄到最小,其余读走从库。库存判定、余额校验这类写前校验的读绝不能走从库。

追问:“怎么监控复制延迟?”——数据库延迟指标告警,延迟超阈值时应用自动降级为读主库或提示;“延迟根因?”——大事务、从库单线程回放(可使用并行复制)、从库资源不足。

Q21:大促期间 Redis 出现大 key、热 key,怎么发现和处理?

参考回答要点:发现:大 key 用 redis-cli --bigkeys(生产使用需注意对性能的影响,建议在从节点或低峰期)、MEMORY USAGE、慢日志;热 key 靠客户端或代理侧统计、监控单节点 QPS 倾斜。处理大 key:拆分(一个大 Hash 拆成多个小 Hash)、用 UNLINK 异步删除而不是 DEL、避免一次性 HGETALL。处理热 key:本地缓存副本、key 打散加后缀读时随机、读写分离(读从节点)。事前预防:设计阶段就限定集合类 key 的上限,并加监控告警。

追问:“集群里某个节点负载明显高于其他节点?”——热 key 或大 key 落在同一槽,排查倾斜并按上述方法打散;“能不能在线迁移槽?”——可以,但要在低峰进行并留意迁移期间的性能影响。

Q22:库存对账具体怎么做?发现差异怎么办?

参考回答要点:三方对账:Redis 库存、数据库库存、订单/库存流水。基本恒等式:初始库存 = 当前可售 + 预占中 + 已确认售出 + 已回补的净变动。做法:活动中低频抽检(对比 Redis 与数据库差值是否在“在途消息量”范围内),活动结束后全量对账;差异处理:先判断方向——Redis 少于数据库(可能多扣、回补丢失)会导致少卖,通常能接受并补回;Redis 多于数据库(可能少扣)会导致超卖风险,需要立即关闭入口或降低放行量。修复必须基于流水,按订单号幂等地重算,禁止“拍脑袋改数字”。所有修复留痕并复盘:是消息丢失、回补重复,还是脚本 bug。

加分:提到把“对账差异数”作为大促监控大盘的核心指标之一,而不是事后才查。

Q23:(开放题)如果只给你两周时间把一个老的下单接口改成能扛大促,你先做什么?

参考回答要点:这道题考的是优先级判断,而不是技术堆砌。我会这样排:

  1. 第 1–2 天:摸底。梳理下单链路的调用图,压测出当前极限与瓶颈,列出所有同步远程调用与数据库写入点。没有数据不要动手。
  2. 第 3–5 天:砍同步。把非核心动作(积分、推送、日志)改成异步;把事务里夹带的远程调用移出事务;修复明显的慢 SQL 与缺索引。这一步往往拿到最大的收益,风险却最小。
  3. 第 6–8 天:保护。接入限流、超时、熔断与降级开关,并设定核心链路的优先级;给热点商品加库存预扣的缓存层,但保留数据库条件更新兜底。
  4. 第 9–11 天:验证。再次压测,做故障演练(Redis 不可用、MQ 积压、下游超时),准备对账脚本与回滚方案。
  5. 第 12–14 天:灰度与值班。小流量灰度、观察指标,制定大促值班表与应急手册。

加分:明确说出“不做什么”——比如两周内不做分库分表,因为风险高且收益不确定,先用缓存、异步与限流解决 80% 的问题。能取舍,才像高级工程师。

Q24:你觉得高并发方案里最容易被忽视的是什么?

参考回答要点:我会答“可观测与恢复能力”。方案再漂亮,如果没有链路追踪、按业务维度的指标(下单成功率、库存差异、消息积压)和明确的应急手册,出问题时团队只能靠猜。第二个容易被忽视的是数据修复能力:能不能按流水重放、能不能安全地回补、能不能在不停服的情况下修数据。这两点决定了事故是十分钟的小插曲,还是一整夜的灾难。

Q25:秒杀结束后,你会做哪些收尾和复盘?

参考回答要点:收尾是很多人忽略、但面试官爱听的部分:(1)数据收尾:关闭入口,处理 MQ 中残余消息,等待超时未支付订单释放库存,然后把 Redis 中的剩余库存回写数据库并对账;(2)资源收尾:清理活动相关的 Redis key(设置过期而不是手动大批量删除)、缩容临时扩出的实例、恢复限流与降级配置;(3)复盘产出:看四类数据——流量曲线(峰值是否符合预估)、错误与超时分布、各层拦截比例(漏斗是否合理)、用户投诉与风控命中。把“预估与实际的差距”写进下次活动的容量模板里。

追问:“如果复盘发现某一层拦截比例远低于预期怎么办?”——说明漏斗在这一层没有发挥作用,例如网关限流阈值过高、验证码没有真正打散流量,需要调整阈值或前置更早的过滤;“怎么让复盘不流于形式?”——每个结论落到具体的改进项,有负责人与截止时间,下次活动前检查完成情况。

这一题看似“软”,实则很能区分“做过完整一轮活动”和“只写过一段代码”的候选人。前者会自然提到对账、清理与容量模板,后者往往在活动开始的那一刻就结束了叙述。

七、这场面试暴露的通用规律

规律表现怎么应对
秒杀是入口,追问是主体从 Redis 扣库存追到回补、对账、分片每个方案都准备“失败了怎么办”
多层兜底思维Redis 判定 + 唯一索引 + 条件更新 + 对账不依赖任何单点保证正确性
一致性分层看待库存/订单强一致,积分/通知最终一致核心放本地事务,次要走异步幂等
削峰不是加队列这么简单分区数、消费速率、积压、死信会估算峰值与消费能力
分库分表看分片键用户维度与商家维度冲突分片基因 + 异构索引 + 逻辑分片
限流降级要预设阈值、开关、兜底内容压测定阈值,开关可秒级生效

八、复盘清单与准备建议

  1. 手写一遍秒杀 Lua 脚本,并讲清 Cluster 下的 hash tag。
  2. 画出“预占 → 确认/释放”的库存状态图,标出每个失败点的补偿手段。
  3. 背下分库分表三件事:分片键、全局 ID、跨维度查询(异构索引)。
  4. 准备一张“降级清单”:你的项目里哪些功能可降级、开关在哪、兜底是什么。
  5. 准备一次“库存/订单对账”的真实做法:对账频率、差异如何处理。

小林 coding 的系统设计题清单里总结过秒杀的“层层漏斗”原则和“抢购场景:入口限流 → Redis 原子检查扣减 → 一人一单校验 → MQ 异步下单 → 数据库乐观锁兜底”的路径,我在这场面试里基本都按它展开。要注意的是,这些方案在“教科书版本”和“生产版本”之间差着一堆细节:降级开关、灰度、对账、告警、值班。面试里能讲到这些细节,你就从“会背”变成“做过”。

最后给一个心理建设:高并发面试里,面试官问“如果 Redis 挂了怎么办”,并不是想让你说“不会挂”,而是想看你是否承认“它会挂”,并且知道系统在那种情况下会退化成什么样子、谁来发现、怎么恢复。坦诚地讲清楚退化路径,比假装完美更有说服力。

一句话备忘:电商高并发 = 漏斗挡流量、Lua 原子判定、MQ 削峰异步落库、唯一索引与条件更新兜底、预占释放加对账收尾,限流降级要提前设开关;每个方案都要能回答“它出错了怎么办”。

再次声明:本文是公开面经与技术文章的整理改编,不是某一场具体面试的实录。方案里的阈值、数字均为示意,请以你自己的压测与业务为准。

参考资料

— 感谢读到这里 —发现错误,或想补充?告诉我 →
TURN KNOWLEDGE INTO SOMETHING REAL

有想法,也想把它做出来?

从技术方案到上线交付,聊聊你的下一个项目。

聊聊需求

一起聊聊

搜索文章

输入关键词,按标题和摘要即时筛选。

浏览分类文章归档

阅读设置

字号
行宽
专注模式

专注模式会隐藏页头与侧栏,只留正文。设置保存在当前浏览器。

稍后阅读

保存在当前浏览器,留给下一次安静阅读。

总浏览 -- 次 · 总访客 -- 人 · 今日浏览 -- 次 · 今日访客 -- 人