秒杀系统从0到100万QPS:高并发架构演进全复盘
做秒杀系统这几年我最大的感受是每一个上过线的秒杀方案几乎都是在用户流量冲垮系统之后才被迫想出来的。标题里提到的“从 0 到 100 万 QPS”听起来像是一条线性增长的曲线实际走一遍才会明白这中间每一次架构演进都是在跟物理资源较劲、跟数据一致性较劲、也跟自己的认知局限较劲。这篇文章我想用一次完整的技术复盘形式把秒杀系统从最早的单机应用一路做到百万级 QPS 的演进过程拆开来讲。内容会覆盖单体架构、缓存加速、消息队列削峰、限流防刷、库存拆分、全链路异步化以及压测和监控这些配套基建。适合正在做电商后端、对高并发感兴趣的开发者也适合那些被领导临时安排“下周上线一个秒杀活动”的运维和架构同学。我会把每一版方案的选型理由、关键代码、踩坑经验都拆开讲清楚尽量让你看完之后能直接对照自己的项目落地。1. 秒杀系统为什么难一场蓄谋已久的流量海啸1.1 秒杀场景的真实业务画像秒杀系统的业务特征和日常的电商交易完全不一样。日常购物是均匀流量用户在一天不同时段零散下单系统承受的是平稳的请求流。秒杀则是典型的瞬时洪峰活动开始那一瞬间几十万甚至上千万用户同时按下“立即抢购”请求在同一秒内涌入。数据库每秒写入量瞬间从几百跳到几十万系统在几十秒内要处理平时一整天的流量。更麻烦的是库存极其有限。常规秒杀库存可能只有几百到几千件意味着超过 99% 的请求注定“买不到”。但这 99% 的请求同样会消耗 CPU、内存、数据库连接它们不会因为注定失败就自动消失。这就引出了秒杀系统的第一个核心矛盾用大量的系统资源去处理注定失败的请求还要保证少量成功请求的数据正确性。其次是热点高度集中。所有用户访问的是同一个商品详情页扣减的是同一行库存记录写的是同一张订单表。这不是普通的读多写少场景而是“一热到底”的场景一个商品击穿所有层级的缓存一个库存字段成为整个分布式系统争抢的锁。1.2 秒杀技术难点的三个层次把秒杀的技术难点拆开大致可以分成三个层次第一层是资源瓶颈。单机应用有天然的 QPS 上限一台普通配置的应用服务器处理动态请求的能力在几千到一万 QPS 之间数据库的读写能力更低。流量超过这个阈值系统直接不可用。第二层是数据一致性。库存不能超卖同一用户不能重复购买订单创建和库存扣减必须一致。这些约束在高并发场景下所有简单的方案都会失效。比如先查库存再扣减并发下一定会超卖比如用数据库行锁保证一致性能又扛不住。第三层是用户体验。秒杀页面要在一秒内打开点击“抢购”后要尽快知道结果不能白等。用户不会关心你系统内部多复杂他们只关心按钮响应快不快、结果准不准。所有异步化、削峰填谷的设计最终都要在这条体验红线下寻找平衡。1.3 重新定义秒杀系统的性能指标谈秒杀性能不能只盯着 QPS 这一个数字。QPS 是入口流量指标但真正能反映系统健康度的是“有效请求率”——最终成功完成业务处理的请求数占总请求数的比例。一个系统可能入口 QPS 一百万但真正能抢到的只有几百人剩下绝大多数请求如果都能在网关层被快速拦截、在缓存层被快速返回系统就是健康的。反之如果一百万个请求全部落到了数据库哪怕只成交几百单数据库也会瞬间被打死。所以秒杀系统的架构设计一句话总结就是把有限的资源用在确定有价值的请求上让无效流量在最外层就被识别和拦截。后面的每一版架构演进本质上都是在让这一判断执行得更早、更高效。2. 第一版架构单体应用直接怼就是一个字“干”2.1 业务起步阶段的真实选择很多团队做秒杀最初的架构根本没有传说中那么复杂。早期流量小商品数量少活动频率低一个单体应用加一台数据库就能跑完整个流程。我见过的最小可用方案是这样的Spring Boot 应用对外提供“获取商品详情”和“提交秒杀订单”两个接口MySQL 里一张商品表存库存一张订单表记录购买记录整个服务部署在一台 4 核 8G 的云服务器上。购买流程简单直接先查库存是否大于零再执行 UPDATE 扣减库存接着 INSERT 一条订单记录。代码逻辑非常清晰但问题恰恰出在“清晰”上——这个流程在并发场景下会踩出一串连环坑。2.2 单机架构的 QPS 天花板第一版单机架构能扛多少流量我用实际压测数据说话。4 核 8G 机器上的 Spring Boot 应用单接口纯逻辑处理不走数据库大约能扛 3000-5000 QPS。一旦把“查库存 扣库存 写订单”这个完整链路走完QPS 直接掉到 800-1200因为数据库成为瓶颈。MySQL 单机的写能力大约在 1000 TPS 左右读能力稍好缓存未命中的情况下也就 3000-5000 QPS。Tomcat 默认线程池配置一般是 200 个线程意味着同时只能处理 200 个请求其余线程排队等待。当 5000 个请求同时涌入应用服务器和数据库的连接池被瞬间占满大量请求在等待队列里超时最终表现就是用户一直转圈、页面打不开后台日志全是连接超时。2.3 第一版踩过的三个经典坑第一个坑也是所有秒杀新手必踩的坑超卖。两个用户同时发起购买请求都读到库存等于 1都判断“可以买”都执行了扣减库存变成 -1。这个问题的根因是“先查后扣”的竞态条件解决办法是让“查和扣”变成原子操作但第一版我们根本没意识到这是并发问题。第二个坑连接池耗尽。Tomcat 默认 200 个线程数据库连接池默认 20 个连接。5000 个请求进来前 20 个获得数据库连接其余 1980 个在排队。每个数据库操作耗时从正常 5 毫秒飙到 500 毫秒连接持有时间变长排队队伍越来越长最终形成雪崩效应系统吞吐量不仅没上去反而因为线程阻塞降到了 200 QPS 以下。第三个坑页面静态资源直接穿透到后端。活动开始前我们做了一个简单的 HTML 页面里面塞了几个商品数据用模板引擎渲染结果每个用户每次刷新页面都会触发一次商品信息查询。流量一上来商品查询接口的 QPS 比购买接口还高好几倍数据库的读压力成为压垮系统的第一根稻草。这一版架构给我们的直接教训是单机应用只适合验证业务流程根本不适合承载任何规模的秒杀流量。但这并不代表第一版没价值它最大的价值是逼着我们搞清楚了瓶颈到底在哪——先是数据库读后是数据库写再是应用线程池每解决一层瓶颈系统就能往前走一步。3. 第二版架构缓存先行把热点读请求挡在门外3.1 动静分离让页面不再是性能负担第一版崩掉的直接诱因是页面渲染触发的数据库读压力。第二版做的第一件事就是动静分离。商品详情页在活动开始前完全静态化商品标题、图片、规格参数、详情描述全部通过静态资源部署到 CDN用户打开页面时 CDN 直接返回 HTML 文件完全不经过应用服务器。页面上剩余动态元素只剩两个当前时间倒计时和“立即抢购”按钮的状态。这两个状态通过接口异步获取活动结束时按钮置灰页面变成只读状态后续请求就不会再往后端转。这一层改造的收益非常直接原来打开页面需要经历 DNS 解析、连接应用服务器、查询数据库、渲染模板耗时可能在 200-500 毫秒静态化之后页面打开时间压到 50 毫秒以内而且就算有几十万人同时刷新页面压力全部由 CDN 扛住应用服务器和数据库完全感知不到。3.2 二级缓存策略本地缓存挡一层Redis 挡二层商品信息是秒杀场景下读取频率最高的数据。第二版我们设计了两级缓存来挡读流量第一级是进程内本地缓存。应用服务器启动时把商品 ID、名称、图片等极少变动的基础信息加载到本地内存处理请求时先查本地缓存命中直接返回。本地缓存的访问速度是纳秒级的每秒可以支撑几十万次读取但问题是每台服务器保存一份副本数据更新时需要广播或者容忍短暂不一致。第二级是 Redis 分布式缓存。本地缓存未命中的请求再去查询 Redis。Redis 单实例读性能大概在 8-10 万 QPS加上集群能力后读能力还能继续扩展。数据库只在 Redis 未命中时访问一次且回源后立即回填缓存。这一版上线后商品查询接口的数据库 QPS 从峰值 3000 降到了不到 100整个页面的读压力基本被缓存层吸收干净。3.3 缓存穿透、击穿与雪崩老生常谈但必须处理缓存上线后随之而来的是三个经典的缓存问题。缓存穿透用户持续请求一个不存在的商品 ID缓存和数据库都没有数据每次都直接打到数据库。解决方法是缓存空值并设置较短的过期时间或者用布隆过滤器在缓存前拦截不存在的 ID。缓存击穿某个热点商品缓存过期的一瞬间并发请求同时发现缓存未命中全部穿透到数据库。由于秒杀场景天然就是热点商品这个问题简直是量身定制。解决办法是对热点数据设置“逻辑永不过期”缓存不设置过期时间后台线程定期检查商品库存并主动更新缓存。缓存雪崩大量商品缓存同时过期导致所有请求瞬间落到数据库。解决办法是在设置缓存过期时间时加入随机值避免过期时间成片重叠。这三个问题处理完后系统的读链路才算真正稳定。但与此同时我们发现写链路——也就是真正的下单流程——依然停留在最原始的“数据库直接扣减”阶段它成为整个系统新的天花板。缓存解决了“大多数人浏览”的问题但解决不了“少数人抢购”时候的数据争抢问题。4. 第三版架构削峰、限流、防超卖三件套4.1 消息队列削峰填谷从“直接打库”到“排队进场”当秒杀流量真正形成规模直接写数据库的方案无论如何优化都会触顶。我们在第三版引入了消息队列核心思路是把“提交请求”和“处理订单”两个动作拆开用队列缓冲瞬时流量。用户点击“立即抢购”后请求先进入消息队列我们用的是常见的 RocketMQ / Kafka 这类成熟组件后端消费者以恒定速率从队列里拉取消息创建订单、扣减库存。这样数据库承受的写入压力从“峰值 10 万 TPS”变成“均匀的 2000 TPS”数据库在可控范围内完成所有写入。有个细节值得注意消息队列的消费者线程数要和数据库的处理能力对齐。消费者开少了消息积压越来越严重用户迟迟拿不到结果消费者开太多了数据库连接数超限照样出问题。我们当时的做法是先设置一个保守的消费者线程池压测观察数据库指标确认稳定后再逐步增加并发度直到数据库 CPU 达到一个安全阈值比如 60%就停止加量。4.2 库存预扣与 Redis Lua 脚本原子性是防超卖的解药引入消息队列后下单流程变成两步请求进队列消费者异步处理。但这带来一个新问题——库存什么时候扣如果等消费者从队列取出消息后再扣库存存在一个问题大量无效购买请求已经占用了队列资源。比如 100 万人同时抢 1000 件商品消息队列里会堆积 100 万条消息消费者逐条处理处理到 1001 条时库存已经为零剩余 99 万条消息变成了无效消息白白消耗资源。所以我们把扣减库存的动作提前到了入队之前。用户请求到达应用服务器后先在 Redis 执行库存扣减成功才允许消息入队失败直接返回“已抢完”。Redis 的单线程模型天然保证了扣减操作的原子性在 Redis 使用 Lua 脚本可以一步完成“检查库存充足 扣减库存 返回结果”从机制上杜绝了超卖。-- 扣减库存的 Lua 脚本 local stock tonumber(redis.call(GET, KEYS[1])) local purchase tonumber(ARGV[1]) if stock purchase then redis.call(DECRBY, KEYS[1], purchase) return 1 end return 0这段脚本看起来简单实际解决了秒杀场景里最难的数据竞争问题。用户请求到达应用服务器执行这段脚本返回 1 表示扣减成功返回 0 直接结束。整个过程在 Redis 内部原子完成不会出现两个请求同时读到库存 1、同时扣减的情况。数据库里的库存字段这时候就只是一个存档值最终通过异步消费者把 Redis 的扣减结果同步到 MySQL。4.3 限流策略令牌桶、滑动窗口与排队等待库存扣减前置后系统已经能保证“成功就是成功失败就是失败”但还有一个问题没解决如何防止少数用户把流量刷爆。有些用户会写脚本在活动开始前一秒用多台设备、多个账号同时发起请求试图用量取胜。如果不做限流这些脚本请求就会占用大量系统资源把正常用户挤掉。我们当时的防线有三层第一层是网关层限流。Nginx 级别配置令牌桶算法按 IP 限制请求速率超过速率直接返回 503。这一层能挡掉明显异常的流量但误杀率也高同一个公司局域网出口 IP 可能被误伤所以阈值要留足余量。第二层是用户维度限流。同一个账号、同一个手机号、同一个设备指纹只允许一次有效购买请求后续请求一律直接返回“您已参与”。这一层要在应用服务器实现先查 Redis 里是否已有这个用户的购买记录。为了性能和原子性双重保障也建议用 Lua 脚本来做。第三层是排队等待机制。不直接拒绝而是返回“前方拥挤请稍后重试”让用户过几秒再点击。这一层本质上是把请求分散到时间轴上用一次“软失败”换取后续请求的成功率。三层限流配合下来入口 100 万 QPS 流量被大幅削减真正落到 Redis 做库存扣减的请求下降到几千到几万系统压力完全可控。这里我强调一下限流不是不让你做业务而是把流量控制在系统能安全处理的范围内宁可让部分用户稍后重试也不能让系统全盘崩溃。4.4 网关层拦截Nginx 与 OpenResty 的高效过滤在应用服务器处理限流之前还有一层更快、更前置的拦截网关层。我们在 Nginx 层直接做了几件小事第一拦截活动开始前和结束后的所有请求活动时间外的请求一秒都不放行第二检测 User-Agent 特征拦截明显的脚本爬虫请求第三做上面说过的 IP 级令牌桶限流。更进一步我们用了 OpenRestyNginx 加 Lua 扩展实现网关层的动态拦截逻辑。比如把一个“黑名单用户名单”加载到 Nginx 层共享内存中用户被判定为恶意刷单后后续请求在 Nginx 层直接返回 403根本不会进入应用服务器。这层逻辑如果用应用服务器来做每个请求都要经过一次网络传输耗时增加 10-30 毫秒在 Nginx 层做几乎零成本。到这里第三版架构的轮廓已经完全清晰了静态化页面吸收浏览流量缓存吸收查库存流量限流控制入口并发Redis 库存预扣保证不超卖消息队列削峰让数据库平稳写入。这套方案上线后我们的秒杀系统已经可以从容应对几十万 QPS 的入口流量成交数据也完全正确。但距离“100 万 QPS”这个目标还有几个深层问题需要解决。5. 第四版架构百万 QPS 的终极形态5.1 分层治理接入层、逻辑层、数据层的彻底解耦接近百万 QPS 的流量规模第一件事是把系统的每一层都拆到足够细并保证每一层都能独立扩展。接入层采用负载均衡设备加 Nginx 集群负载均衡设备负责流量分发和健康检查Nginx 集群负责按域名、按路径做路由转发。这一层所有节点都是无状态的流量大了直接加机器即可。逻辑层应用服务器按照业务拆成独立服务商品查询服务、秒杀入口服务、订单处理服务、用户校验服务。每个服务独立部署、独立扩缩容。商品查询服务面对的流量最大部署的节点最多订单处理服务接收的是消息队列推送的数据实例数则可以相对少一些。服务之间通过 RPC 框架通信接口超时时间要设置得足够短避免一个慢服务拖垮整条链路。数据层的拆分是重头戏。Redis 从单集群拆成多集群每个集群负责独立业务域比如库存集群、用户购买记录集群、限流计数集群。MySQL 从单库拆成多库订单库、库存库、用户库分离每个库再按主键做水平分片订单表按用户 ID 取模分到不同库。这样单个数据库的写入吞吐从几千 TPS 降到了几百 TPS安全系数大大提高。5.2 库存拆分与分布式锁替代让热点不再集中第三版解决了“正确性”问题但性能上还有一个明显的隐患所有库存扣减都集中在 Redis 的同一个 key 上。Redis 单线程模型下单个 key 的 QPS 上限大约在 10 万级别扣减操作再快也会在百万 QPS 场景下成为瓶颈。解决思路是库存分片。假设库存 1000 件拆成 10 个 key每个 key 存 100 件库存用户请求根据某种规则比如用户 ID 哈希、随机数路由到其中一个 key 执行扣减。这样理论上 Redis 的扣减能力提升了 10 倍。但库存分片引出一个新问题某个分片的库存提前扣完了但其他分片还有库存导致明明总库存还有用户却抢不到。我们的处理方案是如果一个分片扣减失败就做“二次路由”去尝试另一个仍有库存的分片如果所有分片都无库存再返回“已抢完”。这个方案有一点代价——极端情况下库存分布不均会在 Redis 上产生额外的查询请求但对整体性能的影响远小于单 key 争抢的瓶颈。同时在数据库层面我们不再依赖 MySQL 行锁来保证不超卖这是第二版到第三版之间用的方案吞吐量太低而是让数据库成为 Redis 扣减结果的“落库存档”。Redis 扣成功消费者异步更新数据库库存Redis 扣失败数据库不产生任何操作。这样的设计里数据库只是记录结果不参与实时决策性能自然就上去了。5.3 全链路异步化从“同步等待”到“最终一致”百万 QPS 场景下同步调用的每一毫秒延迟都会在流量放大效应下变成致命的性能缺口。第四版我们把整条业务链路的设计思路换成了“异步优先、最终一致”。用户请求到达入口时入口只做三件事校验用户基本信息、执行限流判断、调用 Redis 预扣库存。全部通过后返回“排队成功正在处理”同时把订单创建消息发给消息队列。用户界面收到“排队成功”后前端轮询查询订单状态接口直到买到或售完的结果返回。这就是典型的“请求受理”与“业务处理”分离模型。用户感知的是立即得到受理反馈哪怕要等几秒钟看结果系统内部则在后台异步排队处理。承诺一致性从“强一致”降级为了“最终一致”但用户在等待体验上并没有明显的下降——因为轮询结果通常会在 1-3 秒内返回这还在用户可接受的等待范围内。支撑这个模型的关键是消息队列必须足够稳定并且做到消息不丢失。生产环境里有两类消息库存预扣成功后的“订单创建消息”和预扣失败后的“流量统计消息”。前者直接决定用户成败必须开启生产者同步发送确认机制保证消息真正到达 broker 后才返回成功给前端后者丢失无所谓不会影响业务正确性。5.4 多级缓存与降级容灾百万流量下的保命符系统规模到了百万 QPS任何一个小环节的故障都会被放大成雪崩。第四版我们在缓存和容灾上做了两个关键增强。缓存方面把原本的两级缓存升级成了三级本地缓存应用内存微秒级响应、Redis 分布式缓存毫秒级响应、数据库兜底。热度最高的商品基础信息直接加载到每台应用服务器的本地内存中保证 99% 的请求在应用进程内就能完成读取完全不经过网络请求。本地缓存的更新策略是“主动失效 定时刷新”后台定时任务每隔几百毫秒检查一次商品库存变化有变化就更新本地缓存同时监听一个 Redis 通道数据库库存变化时主动广播失效消息让各节点尽快更新本地缓存。这套方案的收益极其明显单机的本地缓存可以支撑每秒几万次读取相当于每台服务器都是一个微型读缓存服务器集群后整体读能力几乎无限扩展。容灾方面核心是“能降级就降级”。我们把所有非核心功能都做成了可开关的降级策略秒杀结束后一键关闭入口Redis 集群异常时切换为数据库降级模式直接查库性能下降但保底可用消息队列堆积严重时触发限流保护拒绝新增请求但保证存量消息正常处理。每个秒杀活动上线前我们都会做一次故障演练模拟真实生产环境里的单个组件故障验证降级策略是否真的生效。这一点我强烈建议每个团队都做因为只在代码里写了降级逻辑、从来没演练过真正出故障时大概率手忙脚乱甚至可能把降级开关都找错地方。5.5 监控、压测与容量规划百万 QPS 不是拍脑袋算出来的最后一块也是从第一版到第四版全程都需要的监控体系和压测能力。没有这两样上面的所有架构优化都是空谈。监控要覆盖整个链路从最外层 Nginx 的请求数、错误率、响应耗时到应用服务器的 JVM 指标、线程池状态到 Redis 的命中率、慢查询再到消息队列的积压数量和消费速率最后到 MySQL 的慢查询、连接数、主从延迟。核心指标汇总到一个大屏上活动运行期间所有运维和后端同事盯着一块屏看任何指标异常都能在 1 分钟内发现。这里有一个实践层面的建议不要只监控平均值要监控 P99 延迟。平均响应时间 200ms 听起来还挺健康但如果 P99 是 3 秒说明最差的那 1% 用户已经卡到快砸手机了。活动期间我们最关注的是 P99 和错误率一旦 P99 超过设定的红线阈值立即触发告警并要求负责人评估是否需要降级。压测是唯一能在活动前发现容量瓶颈的手段。我们不等到活动当天才看系统能不能扛住而是在上线前用压测工具按真实峰值流量进行多轮压测。做法是从低到高逐步加压先按预计峰值的一半压观察各层指标定位第一个瓶颈。修复瓶颈后再压到预计峰值的 80%。最后压到预计峰值的 1.2 倍留出安全余量。每轮压测都要记录系统的极限 qps、延迟分布、资源利用率并回填容量规划表。例如一台 8 核 16G 的应用服务器能处理 1.2 万 QPS那么 100 万 QPS 的入口流量理论上需要 80 台左右的应用服务器再考虑到限流拦截率假设 60% 被网关层拦掉实际只需要 40 台。Redis 库存集群按单机 8 万 QPS 计算配合库存分片8-10 个分片节点能覆盖 100 万级别的扣减请求。这些数字都要靠压测得出每台机器的真实能力只会比理论值低不会更高。6. 实战心得与常见问题速查6.1 我踩过的几个大坑第一个大坑只做缓存没做降级结果缓存一抖动系统就崩。有一次活动刚开始Redis 集群因为热点 key 过大导致网络抖动所有缓存查询超时应用服务器逐出大量异常数据库回源请求暴涨最后整个秒杀链路瘫痪了 5 分钟。后来我们才把降级策略补全制定了“读 Redis 超时直接降级查库”的兜底方案。这里有个取舍降级查库会让数据库压力变大但总比整个页面打不开要好。第二个大坑消息队列消费能力没跟上导致用户已经收到“购买成功”的推送但订单状态在数据库里还是待处理。秒杀场景下用户等待时间是有限的异步处理下单通常要控制在 1 秒内出结果。我们当时消费者的处理逻辑过于冗长——创建订单、扣减库存、更新用户积分、发短信通知全部同步串行执行后来把发送短信、更新积分这些非关键动作全部改成异步执行只保留创建订单和管理库存这两个核心步骤在消费者线程中同步完成消费速率瞬间提升了好几倍。第三个大坑本地缓存更新不及时导致页面显示还有库存用户点击后却提示“已售罄”。这种场景下用户体验是非常差的。我们最终的处理方案是秒杀期间页面上的库存显示改成“已售罄”做一个悲观展示宁可少显示几件库存也不要让用户白点一次。库存数据在活动期间本身就是近似值接受的误差让用户体验牺牲最小这才是合理的取舍。6.2 常见问题排查思路速查表问题现象可能原因排查思路接口响应缓慢线程池耗尽、数据库慢查询、Redis 慢操作先看应用线程池活跃数再看数据库慢查询日志最后看 Redis 慢命令库存显示还有但下单失败缓存与数据库库存不一致对比 Redis 与 MySQL 库存检查本地缓存刷新任务是否正常用户重复下单幂等校验漏处理检查用户 ID 幂等 key 的 SETNX 是否在请求链路最前置处消息队列消息积压消费者速率低于生产速率看消费者消费耗时优先优化消费者逻辑而非盲目加消费者数量服务雪崩某个下游服务超时导致上层线程堆积立即开启降级开关切断非关键链路恢复后逐步放开这几个问题在处理秒杀系统时反复出现基本可以当成一个快速诊断清单来用。6.3 给新做秒杀系统的同学几点实在建议如果你们团队第一次做秒杀我的建议不是直接上百万 QPS 架构而是根据业务量级选择合适的方案。如果预期流量在 1 万 QPS 以下单体应用加 Redis 缓存加数据库行锁是完全可行的没必要为了炫技引入一整套中间件。如果流量到了几万 QPS再考虑引入消息队列做异步化但库存扣减可以继续留在 Redis 原子上。只有当流量达到几十万甚至上百万 QPS才值得做库存分片、全链路异步、服务拆分这类高级架构。不要迷信集群数量每加一层组件就多一分运维成本和故障概率。我见过一些团队一个秒杀活动只有几千 QPS却上了 MQ、Redis Cluster、分库分表——这不是架构演进这是给自己挖坑。还有一点最重要日志打点一定要从一开始就做好。秒杀系统排查问题最怕的就是没有现场。我平时在新接一个秒杀项目时第一件事不是看代码逻辑而是先看日志里有没有打印请求 ID、用户 ID、库存余量、消息队列消息 ID 这些关键字段。有了这些排查问题才不至于两眼一抹黑。写在最后也是踩坑踩出来的一点体会做秒杀系统这么多年我自己最深刻的一点体会是架构不是设计出来的是流量逼出来的。每一次演进都是被线上事故推着往前走——先有超卖才有库存预扣先有机器被打垮才想到页面静态化先有数据库写爆才引入消息队列削峰。如果你正在做秒杀系统不用焦虑自己的架构不够高级只要它在当前流量下稳定正确然后给未来留好扩展空间就是好架构。最后分享一个平时不太容易注意到的小技巧秒杀活动上线后把数据库里所有非核心数据表的慢查询告警阈值调低比如从默认的 1 秒调到 200 毫秒。因为秒杀期间查询量大慢查询一旦出现就会迅速叠加等 1 秒的慢查询堆积成百上千条时数据库基本已经危险了。提前在 200 毫秒量级发现并处理能给系统争取宝贵的救援时间。这个习惯帮我避过好几次线上事故建议你也试试。