ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

秒杀系统架构设计:从流量漏斗到库存扣减的完整实践

2026/10/6 4:10:56 拓冰建站 浏览量
秒杀系统架构设计:从流量漏斗到库存扣减的完整实践 1. 面试官真正在问什么秒杀系统背后的三重矛盾前两天有个朋友跟我吐槽他去面一家做电商的中厂对方问他秒杀系统架构怎么设计他张口就是“Redis扛流量、MQ削峰、数据库扣库存”自我感觉很顺。结果面试官追问了一句“库存到底在哪一层扣扣错了会不会超卖Redis挂了你的系统还剩什么”他当场卡住了。回来一看果然挂了。这不是他一个人的问题。秒杀系统在Java后端面试里出现频率极高但绝大多数人的答案停留在“高并发缓存 消息队列 限流”这种名词堆砌层面。你让他画一张完整的数据流转图讲清楚每层组件为什么选型、挂了怎么办、库存一致性怎么保证十个里面有八个讲不利索。1.1 秒杀场景的极端性平时正常瞬间流量打满秒杀系统最难的地方在于它是一个典型的“极高瞬时流量 极低实际成交”场景。假设某商品库存只有1000件开抢瞬间可能有10万甚至100万用户同时点按钮。这意味着系统在设计上要接受的第一个事实是绝大多数请求注定是失败请求。你根本不需要让所有请求都打到数据库你要做的是让绝大多数的请求尽早、尽快、尽可能低成本地返回“已售罄”或“排队中”。很多人第一次设计秒杀系统时惯性思维是把它当成一个普通的交易系统结果就是压测时数据库连接数瞬间被打满应用线程全部阻塞在数据库连接等待上整个服务雪崩。我当年第一次上线秒杀活动就吃过这个亏大促当晚支付服务跟着一起超时差点被运维同事拉进黑名单。1.2 面试官考察的四个底层能力面试官问秒杀系统架构本质上不是要你背组件清单而是看四件事。第一你有没有流量漏斗思维。从用户点击到数据库每一层都应该拦截掉大量无效流量而不是直接把压力转嫁给下游。第二你知不知道库存一致性的难点。超卖是电商事故里最致命的一种一次超卖可能让公司损失几十万面试官非常关注你对扣减时序、锁粒度、异常补偿的理解。第三你有没有稳定性兜底意识。高并发下一定会出问题Redis可能宕机、MQ可能积压、数据库可能锁等待方案里如果没有降级、熔断、限流这类保护措施说明你根本没做过真实项目。第四你能否在技术方案的复杂度和成本之间做取舍。一上来就摆出十几台K8s集群、五六个中间件的人往往不是最受欢迎的候选人。2. 流量漏斗式架构从点击按钮到数据库的全链路拆解先放一下具体的代码和参数我们从宏观链路把整个系统捋一遍。一个经得起面试追问的秒杀架构一定是按照“拦截流量 → 承接流量 → 异步消化流量 → 最终持久化”这个顺序来组织的。2.1 分层架构全景每一层都在干什么我用一张分层图来表述整条链路面试时你也可以直接这样画给面试官看第一层客户端与CDN层。秒杀商品详情页、按钮文案、倒计时全部静态化后放到CDN和浏览器缓存里。用户抢购时绝大多数请求其实只是在刷新一个静态页面根本不会打到你的应用服务器。第二层接入层LVS/Nginx。负责入口转发、单机限流、IP黑白名单。Nginx的limit_req模块在这里做第一道流量过滤比如每IP每秒只放行2个请求剩下的直接返回“请求过于频繁”。第三层应用层秒杀独立服务。校验用户登录状态、活动时间、商品是否已售罄然后尝试从Redis中扣减库存。这一层承担了绝大部分业务逻辑但它不直接写数据库。第四层缓存层Redis。保存商品库存、用户抢购状态、活动配置。库存的预扣减在这里完成通过Lua脚本保证原子性。第五层消息队列MQ。只有Redis扣减成功的那些请求才会把“创建订单”的消息发给MQ应用层异步消费消息真正去数据库落订单。第六层数据库层MySQL。最终扣减数据库库存、写入订单记录。但由于前面已经过滤掉了99%的流量数据库承受的压力其实非常小。这个漏斗的核心逻辑是流量从100万进来到接入层可能被拦掉50万到应用层只剩下20万真正拿到Redis扣库存资格的只有1万而最终落到数据库的可能只有几百条SQL。数据库永远不直接面对高并发。2.2 组件选型与理由为什么用这个不用那个面试时最怕的是“知其然不知其所以然”。很多人说用Redis但你问他为什么不用Memcached他说不出来。我用一张表格把几个核心组件的选型逻辑整理一下这些都是可以背下来但更要理解的组件推荐方案备选方案选择理由接入层负载均衡LVS NginxHAProxyLVS工作在四层转发性能极高Nginx做七层限流和rewrite两者分工配合分布式缓存Redis ClusterMemcachedRedis支持Lua脚本、持久化、数据结构丰富Memcached无法支撑原子扣减消息队列RocketMQKafka / RabbitMQRocketMQ支持事务消息、延迟消息、消息轨迹秒杀场景对可靠性要求高于吞吐极致数据库MySQL 8.xPostgreSQLMySQL是电商系统最常见选择分库分表生态成熟限流组件Sentinel / Guava RateLimiter自行实现计数器Sentinel支持流控、熔断、系统自适应保护适合分布式场景这里面有个经常被忽略的点为什么秒杀场景的MQ不用Kafka。Kafka的吞吐确实比RocketMQ高但它强调整体顺序写入在大量小消息的场景下事务和重试机制不如RocketMQ方便。秒杀消息的特点是单条体积小、数量多、下游消费逻辑简单创建订单RocketMQ在功能完备性和可靠性上更匹配。当然如果你团队已经深度依赖Kafka也不是不能用关键是你要能说出取舍的理由。2.3 秒杀系统要不要微服务化这是我在面试中几乎必问的延伸问题。我给出的建议是秒杀系统一定是独立部署的但不需要为了秒杀去强行搞微服务。理由是秒杀活动是短时高并发的特殊流量它和日常交易流量是两回事。如果你把秒杀逻辑塞在主站的订单服务里秒杀瞬间的巨大流量会干扰正常用户的交易请求甚至拖垮整个下单链路。所以至少要把秒杀服务拆出来单独部署给它独立的数据库连接池、独立的Redis、独立的MQ topic。但它内部并不需要再拆成“库存服务”“订单服务”“活动服务”这种粒度。一次秒杀请求的链路本来就短拆得太细只会增加RPC调用次数得不偿失。3. 库存扣减与多级缓存秒杀架构的主动脉如果说前面把这些组件串起来是骨架那库存扣减和缓存设计就是整个系统的主动脉。这两个点也是面试官最爱深入追问的地方下面拆开细讲。3.1 三层缓存与热点Key处理秒杀商品的库存Key天然是Redis里的超级热点。如果所有请求都直接读RedisRedis的单分片可能会被打出大量热点请求。所以我在实际项目里一般会做三层缓存。第一层是本地缓存。秒杀服务启动时把活动的开始时间、秒杀商品的基础信息、库存剩余数量定期同步到JVM内存里比如每5秒从Redis拉一次。用户请求进来时先检查本地缓存里的库存状态如果本地显示已售罄直接返回失败一个请求都不会打到Redis。第二层是Redis主缓存。真正保存库存数量、扣减操作都在这层。这里要特别注意缓存过期时间秒杀活动的库存Redis Key不应该设置过期时间或者只在活动结束后主动删除。我见过有人给库存Key设置了10分钟过期结果活动进行到第9分钟时缓存突然失效数据库被瞬间击穿。第三层是数据库兜底。缓存里的库存只是一个“预扣减”的结果真正权威的库存数据在MySQL里。缓存和数据库的最终一致性通过MQ异步同步来保证。另外热点Key还要考虑一个问题缓存击穿。如果Redis里的库存Key突然失效大量请求同时打到数据库数据库可能瞬间崩掉。标准做法是分布式锁加本地空缓存。比如查数据库得到库存后即使结果为0也要在Redis里缓存一个空标记防止下一次请求再次穿透到数据库。3.2 Redis Lua原子扣库存为什么不能先查再减这是整个秒杀系统最核心的代码逻辑。先说不推荐的做法// 错误示例先查库存再扣减 int stock redis.get(stock:1001); if (stock 0) { redis.decr(stock:1001); }这段代码在并发场景下一定会超卖。因为两个线程可能同时读到stock1然后都执行decr库存变成-1但你卖出了两件。Redis虽然单线程执行命令但上面的操作是“多条命令的组合”中间没有原子性保证。如果一定要用这种方式必须用分布式锁把“查”和“减”包起来但锁在高并发下又成了瓶颈。正确的做法是把扣减逻辑写进Lua脚本里让Redis原子地执行整个操作-- 参数说明KEYS[1] 是库存KeyKEYS[2] 是用户购买记录Key -- ARGV[1] 是用户IDARGV[2] 是本次购买数量 local stock tonumber(redis.call(get, KEYS[1])) if not stock or stock tonumber(ARGV[2]) then return -1 -- 库存不足 end if redis.call(sismember, KEYS[2], ARGV[1]) 1 then return 0 -- 用户已抢购过防止重复购买 end redis.call(decrby, KEYS[1], ARGV[2]) redis.call(sadd, KEYS[2], ARGV[1]) return 1 -- 扣减成功这段脚本里有三个关键细节先判断库存是否充足再扣减避免扣成负数。用Redis的Set数据结构记录已经抢到的用户ID配合sismember判断直接挡掉重复购买请求。这个去重操作和扣减放在同一个Lua脚本里保证了原子性。脚本返回不同的状态码-1库存不足、0重复购买、1成功Java端根据返回值决定是返回“已售罄”“您已抢购过”还是“抢购成功正在排队生成订单”。很多人在面试时只讲扣减库存不提用户去重这是一个明显的短板。真实场景中同一个用户疯狂点按钮是最常见的流量来源Lua脚本里顺手做掉成本极低。3.3 数据库最终一致性两种扣减方案对比Redis扣减成功后并不意味着订单已经创建完成。真正的订单记录需要落到MySQL里这时就涉及一个经典问题——在哪个时机扣减数据库库存。方案一叫下单扣库存。用户点抢购Redis扣减成功MQ异步创建订单同时扣减数据库库存。优点是用户体验好只要抢到就有订单缺点是可能产生大量订单后用户不支付造成库存长时间被占。通常需要引入订单超时自动取消机制把超时未支付订单的库存回补到Redis。方案二叫支付扣库存。用户抢购成功后只是锁定一个资格真正支付完成时才扣减数据库库存。优点是能避免不支付的库存占用缺点是用户支付时可能发现库存已经没了体验比较差。我的经验是电商秒杀场景一般用第一个方案但要做超时释放。具体做法是在订单表里加一个create_time字段MQ消费时启动一个延迟任务比如5分钟后扫描未支付订单如果用户没支付就把订单状态置为“已取消”同时执行库存回补操作——把数量加回Redis并异步更新数据库库存。数据库扣减本身也有讲究。最笨的方法是先查询库存判断大于0再update这也是超卖重灾区。正确的SQL就一句话UPDATE seckill_stock SET stock stock - 1 WHERE product_id ? AND stock 0;这条SQL利用数据库行锁在扣减时判断库存必须大于0只有影响行数为1时才算扣减成功。性能上不比悲观锁差而且避免了锁机制的各种死锁问题。这就是常说的乐观锁思路在低冲突场景下完全够用——经过Redis和Lua过滤后到达数据库的并发请求其实已经降到很低了远构不成冲突。3.4 消息队列削峰异步化是保护数据库的关键库存扣减成功了接下来要把“创建订单”这件事做异步化。业务流程上用户看到“抢购成功”并不需要等订单真正写进数据库他只需要知道“自己抢到了”就行订单可以慢慢生成。具体实现是这样的应用层从Lua脚本得到返回码1后直接返回“抢购成功订单排队中”。同时把一条消息发到RocketMQ的OrderTopic消息体包含userId、productId、库存扣减记录ID。一个独立的订单消费者监听这个Topic拉取消息后创建订单记录调一次上面的UPDATE SQL扣减数据库库存。如果创建订单失败消息进入重试队列。重试3次还是失败就把消息转存到死信队列走人工补偿通道。RocketMQ事务消息在这里也能起作用扣减Redis库存和发送MQ消息要保证要么都成功要么都失败。我倾向于用可靠消息事务即在库存扣减成功后先发送“半消息”执行本地事务逻辑事务通过后再commit消费者只消费commit状态的消息。这套机制能避免“库存扣了但消息没发出去”这种隐蔽故障。4. 一套可落地的秒杀场景参数与实操记录理论讲完了接下来上一套我实际搭建压测过的配置参数。以下内容基于常见的Linux x86_64环境如果你用的是鲲鹏这类ARM架构服务器记得编译安装时要选择对应的aarch64包部分组件参数上限也会因CPU架构不同有所差异。4.1 环境选型与前置准备先看服务器基础环境操作系统CentOS 7.9 / Ubuntu 22.04都行重要的是内核参数调优接入层OpenResty 1.25.3基于Nginx方便写Lua做限流应用层Spring Boot 2.7.xJava 8缓存层Redis 7.x Cluster3主3从消息队列RocketMQ 5.x1主2从数据库MySQL 8.x一主一从动手前先确认机器架构uname -a # 输出示例Linux sec-kill-01 4.18.0-372.32.1.el8.x86_64如果是x86_64就继续如果显示aarch64后面Nginx和MySQL的编译参数要按ARM架构调整Redis直接下载源码编译一般问题不大但RocketMQ的启动堆内存参数要重新设置。4.2 关键参数配置与计算过程这一块是实操重点很多人在面试时讲得头头是道让他给你一组真实参数就露馅。下面我给的参数来自一次5000并发压测的真实调优过程。Nginx接入层worker_processes auto; worker_connections 65535; http { limit_req_zone $binary_remote_addr zoneseckill_limit:10m rate10r/s; server { listen 80; location /seckill/buy { limit_req zoneseckill_limit burst20 nodelay; proxy_pass http://seckill_backend; } } }这里的关键是worker_processes设置为auto让Nginx充分利用CPU核数worker_connections调大到65535。limit_req_zone按IP限制每秒最多10个请求burst设置20个缓冲超出直接拒绝。10m的zone空间大约可以存储16万个IP状态对一场秒杀活动来说足够。Redis连接与内存Redis其实是秒杀系统压力最大的组件配置上要格外注意maxmemory 8gb maxmemory-policy allkeys-lru tcp-backlog 1024 timeout 0 databases 16maxmemory根据机器物理内存决定如果Redis所在机器只有16GB内存建议给12GB左右留一部分给操作系统和持久化。tcp-backlog不能太小否则高并发下客户端连接会被内核丢队列丢弃设置1024比较合理。关于连接数应用层连接池配置也很重要。我遇到过druid连接池初始配置只有20压测时Redis还没出问题应用服务到Redis的连接池先被打满大量线程阻塞在获取连接上。后面调整为spring.redis.jedis.pool.max-active200 spring.redis.jedis.pool.max-idle50 spring.redis.jedis.pool.min-idle20 spring.redis.jedis.pool.max-wait500msRocketMQ参数RocketMQ消费端的并发度决定了数据库承接能力。我们的订单消费者配置了20个消费线程单线程一次拉取32条消息消息消费的超时时间设置为3分钟。一旦积压生产者会被自动反压——也就是broker会触发流控限制生产者的写入速度。4.3 压测过程从被打爆到逐渐稳定我用JMeter模拟了3个阶梯的压测场景第一阶段2000并发预期TPS在8000左右第二阶段5000并发第三阶段10000并发。第一阶段很顺利TPS达到了预期。进入第二阶段后马上暴露问题用户点击“立即抢购”后部分请求响应时间从30ms飙升到3秒然后大量超时。定位过程很有代表性看Nginx access.log发现不少请求返回502。查应用服务发现Tomcat线程池被打满——默认最大线程数只有200而压测并发是5000请求排队全部挤在Tomcat等待队列里。调大Tomcat参数server: tomcat: max-threads: 600 max-connections: 20000 accept-count: 1000第二轮压测Tomcat不再是瓶颈但Redis出现了慢查询日志。原因是秒杀商品库存Key的访问QPS太高单分片接近极限。我把库存Key做了分片——按productId的hash分成16个子Key每个子Key数量等于总库存的1/16扣减时随机命中一个子Key。这样热点被分散到16个Key上Redis单分片的压力瞬间下来了。第三轮压测Redis问题解决后MySQL成了新瓶颈。大量订单写入导致主库CPU飙升。缓解策略是把订单表按user_id做水平分表拆成16张表同时把订单状态更新拆成批量SQL批量更新。三轮压测下来最终TPS稳定在1.2万左右数据库主库CPU峰值72%Redis峰值80%所有组件都在安全水位内。这套参数至今还在我手里的另一个项目中服役。4.4 限流与降级兜底关键时刻能救命的开关秒杀系统里有个铁律宁可错杀不可错放。所以除了Nginx限流应用层也要有兜底。我在秒杀服务里集成了Sentinel设置了两条规则热点参数规则对商品ID维度每秒最多放行2万次访问超过的直接抛出BlockException。系统自适应规则当系统整体负载超过CPU核数的80%时触发降级后续请求直接返回“系统繁忙请稍后再试”。同时还做了一个人工降级开关维护在配置中心里。每次秒杀活动上线前运维会确认降级开关是开的一旦监控面板显示某个组件异常立即打开开关把流量全部导向静态的“活动已结束”页面。这个操作成本极低但能把故障影响面控制在最小范围。5. 面试现场这样答从50分冲到90分前面讲的都是“怎么做”最后回到面试本身。梳理一下面试官在听你回答时脑子里真正在评判什么。5.1 五个高频致命错误第一个错误是只谈Redis和MQ不提数据库怎么扛。面试官顺着问“那数据库怎么防止超卖”你答不上来前面讲得再好也白搭。第二个错误是把扣库存放在数据库层。你要理解经过层层拦截后数据库确实只承受极小的写流量但你不能说“数据库扛得住所以直接扣数据库”这暴露了你对流量漏斗的设计没有完整思考。第三个错误是不识别重复请求。只做了限流和扣库存但同一个用户狂点10次按钮怎么办限流是IP维度用户换IP又能刷Lua脚本里用Set记录用户ID才是业务维度的防重复。第四个错误是没有售后补偿意识。系统在极端场景下一定会出问题——消息丢了、订单没生成、用户钱扣了库存没减方案里如果没有补偿机制面试官一听就知道你没有线上经验。第五个错误是不会说“如果组件挂了怎么办”。这是很多面试者最害怕的追问但恰恰是我们这种做过真实项目的人最能体现价值的地方。5.2 一套高分回答框架我总结了一套回答模板面试时按这个顺序讲基本不会跑偏第一步讲整体方案“我会把秒杀设计成一个独立部署的服务接入层用Nginx做IP限流同时把商品详情页静态化到CDN尽量不让静态流量打到后端。”第二步讲缓存设计“秒杀商品的库存提前预热到Redis通过Lua脚本原子扣减库存同时用Set记录用户ID防止重复购买。本地缓存每5秒同步一次Redis拦截已售罄状态的请求。”第三步讲削峰填谷“只有Redis扣减成功的请求才发送MQ消息异步创建订单。订单消费端业务逻辑简单即使瞬时消息量大也可以通过消费者线程池和broker流控来消化。”第四步讲数据一致性“数据库库存扣减用UPDATE stock stock - 1 WHERE stock 0只有影响行数为1才成功订单超时未支付会释放库存回补Redis和数据库保证最终一致。”第五步讲稳定兜底“接入Sentinel限流核心组件依赖全部接入监控。Redis如果宕机打开降级开关让流量直接打到静态页面MQ如果积压会触发生产者流控并通过消费者端自动扩容加快消费。”按这个框架回答逻辑是完整的覆盖了流量、缓存、异步、一致性和故障恢复五个核心维度面试官很难挑出明显的漏洞。5.3 三个高频追问的应对思路追问一“Redis库存减了但数据库扣减失败了怎么处理”答Redis扣减是预占资格数据库扣减失败后订单创建会重试。若重试仍失败消息进入死信队列人工补偿。同时Redis里的库存已经扣了需要在下单失败时执行回补把数量加回去把用记释放掉。追问二“如果MQ积压了几十万条订单消息用户一直收不到订单通知怎么办”答先看为什么积压——如果是消费端异常先修复消费端如果只是流量过大可以临时增加消费线程数和Topic分区数。用户侧可以做主动轮询用户点击“查看订单”时触发服务端查询消息积压期间展示“订单处理中”状态。追问三“如何防止黄牛用脚本刷秒杀”答产品层面做滑块验证码、答题验证、预约资格技术层面做IP和账号维度的限流识别异常高频访问同时监控同一设备ID或同一收货地址的批量下单行为命中规则的直接拒绝。6. 压测踩坑实录与常见问题速查下面把我在线上秒杀系统中真实遇到过的坑列出来按问题、原因、解决方案整理全部来自实操记录。问题现象根本原因解决方案压测开始2分钟后Redis连接异常大量Connection refused客户端连接池太小Redis tcp-backlog配置不足调大Jedis连接池到200Redis tcp-backlog设为1024客户端启用连接空闲检测用户看到“抢购成功”但订单一直没生成RocketMQ消费者线程阻塞在数据库写操作上消费吞吐跟不上订单消费者拆分线程池数据库写入改为批量插入消费失败重试次数降到2次快速失败转入死信压测过程中MySQL CPU突然飙升大量订单表UPDATE操作行锁竞争查询没有走索引订单表按user_id分16张表库存UPDATE语句WHERE条件加联合索引(product_id, stock)秒杀开始瞬间应用线程全部阻塞Tomcat默认线程池太小请求排队max-threads调到600accept-count设为1000整个秒杀链路异步化改造Redis内存快速增长直至OOM用户购买记录Set存储了大量用户ID没有设置过期时间活动结束后删除Set活动中对Set采用定期清理(如30分钟不活跃用户移除)给Set设置活动结束的TTL再分享一个我们在压测中发现的小细节压测前先做缓存预热。如果不预热Redis里的库存Key首次查询会落到数据库相当于人为制造了缓存穿透压测结果完全失真。正确步骤是先启动应用运行一个数据初始化脚本把商品库存、活动信息、用户资格全部写入Redis然后再开始压测。6.1 日志采样保证排查能力的最便宜手段秒杀系统的日志量极大全量打印肯定不行但不打印又没法排查问题。我的做法是采样日志加关键链路埋点。在入口处按请求ID生成一个traceId采样率设置为1%每100个请求记录一个完整链路日志同时把Redis扣减失败、MQ消息发送失败、数据库SQL异常这类关键事件全都单独打error日志。这样排查问题时可以通过traceId快速串联起整个请求链路又不会因为日志写入拖垮系统性能。6.2 几个不一定写进方案但很有用的实践技巧第一个技巧是提前算好“库存回归”的时间。每次秒杀活动结束后数据库里会残留一些中间状态的记录——用户抢到了但没支付、支付了但订单生成失败。活动结束后要跑一个对账脚本对比Redis里的库存、MySQL里的库存和实际卖出数量找出差异并修正。我在第一个秒杀项目里就因为没有写对账脚本活动结束后发现库存少了37件排查了整整一天。第二个技巧是给秒杀活动留“灰度验证”的余地。线上正式活动前先做一个1万人的白名单测试在全量活动开始前验证整条链路。很多人觉得这是产品的事其实架构师才应该盯着这个环节因为只有灰度压测才能暴露真实网络环境下CDN、地域节点、用户真实操作路径带来的问题。第三个技巧是不要用Java的Long类型作为Redis的库存计数。Java的Long在Redis序列化后可能被转成字符串数值操作时容易出现类型转换问题。直接用Integer或String或者在Lua脚本里用tonumber做转换避免踩坑。6.3 复盘一次线上事故的完整处理过程最后讲一个我处置过的真实故障。一次618大促秒杀活动刚开始3秒监控系统发出告警订单消费者消费速率从2000条/秒骤降到200条/秒消息积压量快速攀升。当时我的排查顺序是这样的先看消费者日志发现大量“DataSource connection failure”异常说明数据库连接池出问题了。看数据库监控发现连接数接近上限一个隐藏的定时任务正在跑全表统计把数据库连接吃光了。紧急处理杀掉定时任务立即把数据库连接池上限调大同时暂时停掉秒杀消费者让数据库连接恢复。确认数据库稳定后重启消费者进程让它继续消费积压的消息。活动结束后把定时任务的执行时间从整点改成凌晨低峰期并做成分库分表后的单表扫描避免再次爆发。这个事故带来两个教训一是依赖系统的定时任务一定要跟核心业务链路做隔离不能和秒杀共享同一个数据库连接池二是线上系统必须有快速的开关机制我当时就是靠配置中心的一个开关把消费者停掉的这比临时改代码重启快得多。从第一线到面试桌聊聊我真实的体会马上要收尾了我说点不太会写在技术方案里的体会。做秒杀系统这几年最大的感触是面试时讲出来的方案和你真正上线跑过的方案是两回事。真正的秒杀系统不是由某个“绝妙设计”撑起来的而是由一层层防御、一遍遍压测、一个个开关堆出来的。那些看似平平无奇的细节——连接池配多大、线程数设多少、缓存什么时候预热、日志怎么采样——才是决定系统能不能活过三分钟的关键。如果你正在准备面试我建议你画一张完整的架构图从浏览器到数据库把每一层的数据流转、失败处理、超时设置都写在图里然后反复问自己三个问题每个环节如果我挂了会发生什么每个关键请求如果失败了用户会看到什么我需要哪些手段恢复到正常状态这三个问题都答得出来秒杀架构这道题你就已经超过了大多数人。另外给自己留一个压箱底的小技巧面试时主动说出自己在真实项目中犯过的错和修复过程。今天这篇文章里写的Tomcat线程池打满、Redis热点Key分片、定时任务吃光连接池随便选一个讲清楚比背二十个理论概念更有说服力。面试官要的不是一个完美的架构师而是一个经历过故障、知道系统会怎么死的人。