资讯动态

秒杀系统高并发设计全解析:从流量漏斗到库存扣减的完整链路

发布时间:2026/10/4 2:25:27 来源:尧图企业网站定制
开门见山先说结论。秒杀系统本质上是一道“极端流量下的资源调度题”它的难点不在业务逻辑而在你怎么用最少的资源、最稳的姿态去扛住瞬时高并发流量还得保证库存不超卖、用户不骂娘。很多团队一上来就堆服务器、上缓存、上队列看着挺热闹结果压测一跑还是崩。我做过几年电商中台也亲手操盘过几次百万级流量的秒杀活动这篇就把完整的链路拆开讲从客户端到数据库逐层说清楚每层为什么要这么设计里面的坑在哪怎么避一次都讲明白。1. 秒杀系统的整体设计思路先把流量漏斗立起来秒杀系统最大的特征就是“瞬时流量极高但有效请求极少”。我习惯把这个特点叫做“流量洪峰下的有效请求稀释”。假设一场秒杀有10万并发进来真实能抢到商品的可能只有几百人。那剩下的几万个请求本质上都是无效流量而系统的设计目标就是把这些无效流量尽快识别、尽快挡住让真正有资格下单的请求走完整链路。这就决定了系统必须做成“漏斗式”结构层层过滤而不是把压力集中在某一个点上。1.1 流量漏斗的核心层级划分我设计秒杀链路的时候一般分五层接入层、拦截层、服务层、队列层、数据库层。每一层只干一件事层层卸力。接入层处理的是TCP连接和HTTP请求。Nginx在这里做第一道关卡它可以扛住大量的并发连接但你得注意worker_connections和keepalive的配置这一层的基本功要是没做好后面全白搭。我在实战中习惯把Nginx的keepalive_requests调高到1000左右worker_connections按每worker能撑的并发连接数去估算同时打开gzip和HTTP/2减少传输开销。拦截层是业务逻辑的第一道闸门。用户请求进来之后先判断是否登录、是否在活动白名单里、是否已经参与过该场秒杀、当前时间是否在活动窗口内。这些判断全部走缓存不碰数据库。这里有个很多人忽视的细节用户维度的拦截可以用布隆过滤器或者Redis的SETNX做去重防止同一用户用脚本刷接口。我之前遇到过一场活动一个账号用并发工具同时发起几千个请求如果不是在拦截层先做了用户频控后面缓存和队列全被打爆。服务层负责核心的库存扣减和订单生成。这里的关键是“本地化原子化”。我会在秒杀开始前把商品库存预热到Redis服务层只跟Redis交互用原子性的Lua脚本完成扣减避免多个线程同时读到老库存导致的并发覆盖。库存扣完的直接返回“已售罄”不再往下走。队列层是削峰填谷的关键。扣减成功之后异步发送订单创建消息给MQ由下游消费者落地数据库订单。用MQ的好处是下游处理不过来的请求会自然排队不会瞬间压垮数据库。我在设计时会把队列分主题比如order_create、stock_deduct_notify、user_notify每个主题按业务优先级设置不同的消费速度避免一个业务阻塞拖垮所有链路。数据库层只做最终的落库和对账。数据库承担的是最终一致性任务而不是扛并发任务。它需要保证的是事务正确而不是处理速度。所以到数据库这一层的流量必须是已经被前面拦截过、稀释过的“有效请求”。1.2 为什么不能只用数据库扛并发很多没经验的同学会问直接数据库扣库存行不行我的回答是单机MySQL能支撑的QPS大概在几千到一万左右看起来不低但秒杀场景是同一瞬间几十万人同时点按钮请求会在1秒内集中在商品行上。一张表、一个行的锁竞争会让数据库的并发能力断崖式下跌。更重要的是数据库事务是重量级操作每次扣减都要做行锁、日志、事务提交这些开销在极端并发下会被无限放大。而且数据库扛并发还有个致命问题——连接数。MySQL默认最大连接数一般是150左右即使你调到2000每个连接还要占用内存和CPU。请求一多连接就会排队客户端连接超时然后重试又加剧数据库压力。这就是雪崩的起点。所以秒杀系统必须遵循一个基本原则数据库只处理结果不处理过程。中间加缓存、加队列、加限流都是为了保护数据库这个最脆弱的环节。1.3 秒杀系统的性能目标怎么拆解设计秒杀系统之前要先定义性能指标。我一般关注四个数字并行用户数、系统QPS峰值、响应时间P99、成功率。举个例子假设一场秒杀有10万人参与活动时长5分钟全部请求集中在开始后1秒内。那峰值QPS大约是10万但你不可能按10万QPS去设计所有环节那样成本太高。正确做法是接入层扛10万并发连接拦截层每秒只放行1万到缓存服务层每秒只处理3000到5000个有效请求队列下游订单创建的TPS控制在1000以内。这里的关键是“漏斗收口”每一层都要有明确的目标值然后通过压测去验证。如果没有数据支撑你就不知道哪个环节是瓶颈优化也只能靠猜。我在压测时必看的关键指标包括P99响应时间、卡死率、数据库连接池使用率、Redis命中率、MQ堆积数。这些指标能帮你精准定位问题而不是看个QPS数字就觉得系统很稳。2. 库存扣减是秒杀的核心方案怎么选才不出事库存扣减是秒杀系统最容易出事故的环节。超卖、少卖、重复扣减每一条都是P0故障。我在这个环节踩过不少坑也总结了几套经过验证的方案下面分别说清楚它们的适用场景和实现细节。2.1 数据库悲观锁扣减为什么我不推荐最简单的做法是在数据库层面使用SELECT FOR UPDATE给商品行加锁然后先查库存再判断是否足够最后扣减。这种方案能保证不会超卖因为同一时间只有一个事务能拿到行锁其他事务都在等待。但问题在于行锁竞争太激烈。假设一个商品有1000件库存10万并发进来那10万个事务全部要排队获取同一行锁。你能想象的画面就是一条长龙在数据库门口系统性能和直接崩溃没什么区别最终响应时间飙升大量超时和重试。就算你用悲观锁压测结果显示数据库的CPU直接被锁等待耗尽应用的连接池也全部被事务占用整个数据库被拖垮。所以我的建议是悲观锁只适合低并发的后台管理系统比如运营手动调整库存。秒杀场景千万不要用它不是你扛得住并发的不变量。2.2 乐观锁加版本号同样扛不住极端流量乐观锁的思路是不加行锁更新时带上版本号条件比如UPDATE stock SET count count - 1, version version 1 WHERE product_id ? AND version ?。如果更新行数为0说明版本变了重试或返回失败。乐观锁的问题在于写冲突后的重试。在秒杀场景里大量请求会同时读到同一个版本号然后同时提交更新。虽然数据库层面最终只允许一个成功但失败的请求需要重试。重试意味着额外的查询和更新请求数据库的压力并没有减轻只是从锁等待变成了重复执行。压测结果往往乐观锁的TPS表现和悲观锁差不多因为重试带来的额外开销抵消了锁的优势。我实际测过在10万并发场景下乐观锁版本号会导致数据库CPU瞬时打满而且雪上加霜的是很多ORM框架的重试机制不够成熟会导致数据错乱和日志爆炸。所以我也不推荐把它用在秒杀核心路径上。2.3 Redis Lua脚本原子扣减秒杀库存扣减的主流方案这是目前业界最主流的做法也是我强烈推荐的。核心思路是预先把商品库存写入Redis的String类型扣减时借助Lua脚本保证原子性。Lua脚本在Redis中是原子执行的不存在多个客户端并发修改导致数据不一致的问题。下面我贴一段生产级代码示例Java RedisTemplatepublic static final String STOCK_LUA local stock redis.call(get, KEYS[1]) if not stock then return -1 end if tonumber(stock) 0 then return 0 end redis.call(decrby, KEYS[1], ARGV[1]) return 1; Object result redisTemplate.execute( new DefaultRedisScript(STOCK_LUA, Long.class), Collections.singletonList(stock:product: productId), 1 );这段脚本的逻辑是先读取库存如果不存在返回-1说明还没预热如果库存小于等于0返回0说明已经卖完否则执行减库存的原子操作返回1扣减成功。执行结果是0的请求就可以直接返回“已售罄”不需要再走后续逻辑。这里有个细节就是Dubbo或SpringCloud场景下你必须保证DefaultRedisScript是单例的不要每次请求都创建一个脚本对象。Redis会缓存脚本的SHA指令频繁创建脚本会导致不必要的网络开销和服务器缓存压力。2.4 扣减失败了怎么办回滚机制怎么设计Lua扣减成功之后订单创建流程仍然可能失败比如MQ消息丢了、订单落库失败、用户取消支付等。这种时候就需要一套完备的“库存补偿”机制。我常用的方案是记录一组库存操作流水表。每次扣减成功就向stock_operation_log表插入一条流水包含唯一请求号可以用UUID或雪花ID、商品ID、扣减数量、扣减结果、对应订单号、创建时间。然后系统每天定时扫描流水表找出那些状态为“已扣减但没有有效订单”的记录执行库存回补。流水表的作用不只是补偿它还能帮助你做对账和分析。没有流水表的秒杀系统出了问题你只能哭。有了流水表你可以精确到每一个请求找人追查问题和数据一致性。另外还要注意一个点库存回补必须走Redis Lua脚本的原子操作比如incrby确保并发环境下回补也是安全的。同时要记录回补流水防止同一个订单被重复回补导致库存虚高。2.5 队头阻塞问题与库存分片思路如果全场秒杀的商品SKU数量很少只有一两个爆品那么所有请求都会顶到同一个商品的库存Key上。即使Redis单实例可以支撑10万QPS但所有请求都打在一个Key上会导致Redis的CPU被单Key的读写操作用满性能反而下降。我在工程上采用的方案是“库存分片”。把一个商品的库存拆成多个Key例如1000件库存拆成5个分片每个分片放200件。用户请求进来时对用户ID取模映射到其中一个分片再进行扣减。这样就把同一商品的并发压力分散到多个Key上Redis的整体吞吐量可以成倍提升。分片方案要注意的问题是每个分片的库存是独立的可能出现某个分片已卖完而另一个分片还有货的情况。这时可以在分片扣减失败后再尝试扣减其他分片用一个“二分片策略”先扣用户映射的主分片失败就轮询其他分片。最终库存统计是所有分片的库存之和所以不会超卖。我实际测过分片前后的差距同一商品100万请求不分片时Redis CPU达到80%以上分片后CPU降到20%左右性能提升是数量级的。建议所有QPS预估超过5万的秒杀活动都采用库存分片方案。3. 接口限流不能拍脑袋要分层分级去控限流这个词特别容易被误解很多人一提到限流就想到RateLimiter加一个令牌桶然后全局共享一个速率。这在秒杀场景是不行的因为你需要的不是匀速放行而是在保住后端的前提下让“该进来的人”能挤进来。限流必须跟业务状态结合分层分级去做。3.1 限流的几个层次网关层、应用层、数据库层我推荐在三个层次分别配置限流策略网关层Nginx/API Gateway负责连接级别的限流。Nginx有limit_req_zone和limit_conn_zone两个模块可以分别限制请求速率和并发连接数。我用得最多的是limit_req_zone配置示例如下limit_req_zone $binary_remote_addr zoneseckill_limit:10m rate20r/s; server { location /api/seckill { limit_req zoneseckill_limit burst30; limit_conn seckill_conn 10; proxy_pass http://seckill_backend; } }上面配置的含义是同一IP每秒最多20个请求瞬时突发不超过30个。这个配置能挡掉大多数脚本刷量。但IP限流有个缺陷——用户如果用了NAT出口比如公司出口IP多个用户共用同一个IP很容易被误杀。所以实际操作时我会把IP限流的值调得相对宽松主要目的是防脚本和低水平攻击真正的精细化控制放在应用层。应用层限流需要结合用户ID和业务状态。这里我用的是Google Guava的RateLimiter或者Redis的INCREXPIRE做固定窗口计数。核心思路是每个用户每分钟最多请求N次每场秒杀每个用户最多请求M次。这些计数器全部放在Redis用INCR和EXPIRE完成原子操作。public boolean allowRequest(Long userId, Long activityId) { String key seckill:limit: activityId : userId; Long count redisTemplate.opsForValue().increment(key); if (count ! null count 1) { redisTemplate.expire(key, Duration.ofSeconds(60)); } return count ! null count MAX_PER_MINUTE; }数据库层限流其实不是传统意义的“限流”而是通过连接池参数来保护数据库。我把数据库连接池的活跃连接数上限调低比如最大连接20同时设置公平模式让有效请求有机会获取连接而不是被大量无效请求占用。这样即使流量冲到底层数据库也不会因为连接耗尽而全局崩溃。3.2 令牌桶和漏桶怎么选令牌桶和漏桶是最经典的两种限流算法它们的本质区别在于如何处理“突发流量”。令牌桶允许一定程度的突发因为桶里可以积攒令牌而漏桶强制匀速输出不管上游如何突发下游的处理速度恒定。秒杀场景我推荐令牌桶。因为秒杀开始那一刻会有一波集中的请求洪峰令牌桶可以让一部分突发请求先放行让用户体验到“瞬间可抢”的感觉同时整体速率又被有效控制。如果你用漏桶用户看到的是开始的一瞬间就全部超时体验极差。实现令牌桶可以用Google Guava的RateLimiter也可以自己用Redis的Lua脚本实现分布式令牌桶。GuavaRateLimiter是单机版本的适合单体应用如果有多台机器必须改用Redis分布式的版本否则每台机器单独限流的速率加起来会超过预期。我一般直接用Redis Lua实现分布式令牌桶代码不复杂但能保证多节点一致性。3.3 分级限流用户越“尊贵”放行率越高秒杀活动中普通用户、会员用户、内部测试账号的优先级完全不同。我在工程上会给不同用户等级设置不同的限流阈值比如会员每秒限流10次普通用户每秒2次。这背后有业务考量会员是平台的核心资产要保证他们的体验同时会员的转化率也更高。实现分级限流也很简单在应用层限流时先查用户等级然后根据等级选择对应的限流Key前缀和限流值。类似这样String level userService.getUserLevel(userId); int maxQps switch (level) { case vip - 20; case normal - 5; default - 1; };这里的“用户等级”必须走缓存不能用数据库查询。因为限流本身就是为了保护后端资源如果在限流逻辑里又引入数据库查询反而得不偿失。用户等级信息在登录时写入Redis即可过期时间设置成活动结束即可。4. 缓存策略与异步削峰是秒杀系统的左膀右臂流量拦截和库存扣减都完成后缓存和异步削峰发挥作用的核心区域来了。这两块如果设计不好前面的功夫都白搭。因为即便前面拦掉了大部分流量剩下每秒几千的有效请求如果全部同步走数据库依然会卡脖子。4.1 商品详情和活动页面的缓存设计警惕热点Key商品详情、活动规则、参与人数等读多写少的数据一定要走缓存。我用Redis存商品信息时会做一个粗粒度缓存和细粒度缓存两级。粗粒度缓存存整个商品DTO的JSON字符串包含价格、库存、标题、图片等粒度大适合列表页和详情页细粒度缓存存可变字段比如库存、状态等粒度小适合秒杀核心链路实时读取。缓存预热是秒杀前的必备动作。我会在活动开始前10分钟把商品库存、商品详情、活动规则、白名单等数据一次性批量写入Redis。预热脚本可以用定时任务也可以手动触发。热点Key问题是缓存设计里最隐蔽的坑。在秒杀场景里一个爆款商品的缓存Key会被高频访问可能某一瞬间几十万请求全部打在这个Key上。如果这个Key恰好过期或者被淘汰就会造成缓存击穿大量请求穿透Redis直达数据库。我采取的措施是第一活动期间热点Key不设过期时间由后台程序控制失效第二即使Key不存在也用一个空值缓存占位防止击穿第三加分布式锁控制缓存的加载过程只允许一个线程回源数据库。Redis的-XX:MaxRAMPercentage和maxmemory-policy配置我建议提前设好。内存淘汰策略我习惯用allkeys-lru但在秒杀期间要注意如果某些冷业务数据被淘汰了可能会引起其他非核心接口的缓存穿透所以秒杀活动期间我会把Redis内存预分配好减少淘汰概率。4.2 MQ削峰怎么配置才不会把下游打挂库存扣减成功之后订单创建流程走MQ异步化。这里的设计难点不是发消息而是“消费速度的控制”。如果秒杀开始瞬间生成了好几万条订单消息消费者服务如果全速拉取几万条消息同时开始创建订单数据库照样会被打爆。我在项目中是这样做的订单消费程序每次拉取消息的数量配置在20到50之间同时消费线程数设置成数据库连接池大小的一半。假设数据库连接池是20个连接那我消费者线程最多开10个。这样每个消费者线程拿一条消息去创建订单数据库同一时间的并发写压力就被控制住了。MQ选型上我推荐用RocketMQ或Kafka。RocketMQ的延迟消息和消费失败重试机制做得好Kafka吞吐量更大适合超大数据量场景。如果团队规模小RabbitMQ也行但要把prefetch count调低到20以下防止消费者无限拉取消息。消息内容的粒度也要注意。不要把整条订单数据全塞到消息里而是只塞关键业务字段比如用户ID、商品ID、数量、地址ID、活动ID。消息体积越小MQ的吞吐量越高消费反序列化也更快。4.3 扣减成功但订单创建失败这里最容易踩坑异步化之后系统的一致性模型从强一致变成了最终一致。这也意味着扣减成功和订单创建之间可能出现短暂的不一致。我遇到过一个真实场景用户点击秒杀Redis扣库存成功但MQ消息因为网络抖动丢失导致用户明明看到“秒杀成功”订单却没有创建成功。针对这个问题我的方案是双重保障。第一重MQ发消息时开启confirm确认机制如果发送失败就回滚Redis库存。在Spring中可以用CorrelationData做事务消息。第二重落库定时任务对账每5分钟扫描一次stock_operation_log表查找那些扣减成功但超过5分钟还没有订单的记录触发补偿创建订单或回补库存。这里最重要的一点是不能只依赖MQ的可靠性因为再可靠的消息队列在网络分区或服务宕机时也可能丢消息。必备的兜底措施就是定时对账。4.4 消息积压和消费延迟的应对措施秒杀期间的瞬时流量还是会有可能把MQ打积压。如果消费者线程数配置合理一般不会积压太久但一旦下游数据库出现慢查询或锁等待消费速度就会骤降消息堆积量会急剧上升。我的习惯做法是监控MQ的消费堆积量设置一个阈值比如积压超过5000条就触发预警。同时消费者服务配置弹性扩容机制如果积压持续增长自动增加消费者实例。但这里必须注意加消费者实例的前提是数据库能扛得住否则加再多消费者只是把压力更快地传导到数据库得不偿失。更稳妥的做法是限流降级。可以主动暂停某些非核心消息的消费比如“通知用户”“更新用户积分”这些不关键的逻辑把消费资源集中在“创建订单”这个核心流程上。我的消费端代码里专门设计了一个“降级开关”大促期间可以动态关闭非核心消息的处理。5. 压测出来的数据才可信四处都稳定才算稳设计做得再好没有压测验证一切都是纸面功夫。我见过太多系统上线前“自认为稳定”一上线就崩溃的案例。压测不只是看看QPS数字更是暴露问题的手段。我平时压测秒杀系统重点关注四个维度的数据功能正确性、性能指标、稳定性、扩容性。这四个维度都要测缺一个都容易出事故。5.1 压测前要准备什么压测之前先准备一套生产环境的影子库或者压测专用环境。如果能直接用生产环境的备份导入到测试环境最好因为数据分布、索引结构、缓存数据量都要接近真实场景。压测工具我推荐JMeter或wrkJMeter适合复杂业务流wrk适合简单的HTTP压测。压测脚本要覆盖完整的链路注册登录、浏览商品、发起秒杀、下单扣库存、创建订单。只测单一接口没有意义因为秒杀系统的瓶颈往往出现在链路中的某个环节而那个环节只有在完整流量下才会暴露。我压测之前会把所有日志级别改成INFO关闭DEBUG日志因为大量日志输出会严重影响性能。同时要把Redis、MySQL、MQ的各项指标监控打开比如Redis的hit rate、MySQL的慢查询、MQ的堆积量。没有监控数据的压测等于闭着眼睛开车。5.2 我实测过的几组性能数据我记得有一次压测一个包含秒杀模块的电商系统模拟10万并发用户同时发起秒杀请求。最终结果是这样的环节配置QPS峰值P99响应时间Nginx接入层4核8G6万21ms应用层限流拦截8核16G x 42万35msRedis库存扣减8核16G1.5万12msMySQL订单落库8核16G主库3000180ms看到这个数据你会发现峰值QPS最高的环节是应用层的拦截因为大部分请求都卡在这里真正打到Redis和MySQL的量已被大幅稀释。MySQL的P99是180ms看着不高但这是在QPS只有3000的前提下。如果你不做什么拦截、限流、异步化直接让10万QPS打到MySQL那P99响应时间绝对过千毫秒然后界面就卡死。5.3 压测暴露的经典问题与调优手段压测最常见的现象是流量一上来数据库连接池被打满CPU飙到90%以上应用线程全部阻塞在线程池的等待队列里。我遇到这种情况先查三个地方第一数据库连接池的最大连接数是不是配得过大比如50个并发写就能吃满第二是不是有慢SQL在拖慢一切EXPLAIN一看发现某个索引没建第三Redis的连接池是不是配置成了无限等待导致应用线程全部阻塞。调优手段上除了前面提到的缓存、限流、分片还有一个常用的手段是“线程池隔离”。把秒杀相关接口的线程池独立出来设置独立的最大线程数和队列容量。这样即使秒杀接口被流量打满也不能影响其他正常业务的接口。我用ThreadPoolExecutor做隔离时核心线程数、最大线程数、队列容量、拒绝策略都要单独测试一遍。默认的AbortPolicy会在队列满时抛异常我建议用CallerRunsPolicy让超限的请求直接在调用线程执行避免用户请求直接被拒绝。6. 常见问题与排查技巧实录这些坑你大概率会踩这一部分我整理几个在秒杀系统上线过程中最常碰到的故障场景和排查思路。这些全部都是真实事件我把通用性的沉淀出来你对照着排查能省很多时间。6.1 为什么库存没扣完但系统显示“已售罄”这个现象非常经典。多个用户一起抢明明数据库里还有库存但前端显示已售罄。我排查时先看Redis里的库存数量再看数据库库存数量然后看操作流水。最常见的场景是Redis里的库存数量在扣减之后没有正确回写数据库或者发生了一些补偿逻辑的重复执行。比如定时对账任务把未支付的订单库存回补了但订单后来又被支付了导致数据库库存数量虚高。还有一种情况是分片库存分配不均。一个用户映射到分片A时分片A已经卖完但分片B还有库存。用户直接看到“已售罄”印象就很差。解决方法是开启分片轮询机制让用户在某个分片买不到时自动尝试下一个分片直到所有分片真正卖完。6.2 Redis缓存和数据库数据不一致了怎么办缓存与数据库不一致是秒杀系统一个很难避免的坑。我用的方案是优先保证Redis库存的正确性数据库异步同步。也就是说Redis是唯一的库存权威数据源数据库的库存只作为备份和统计使用。用户看到的库存、扣减结果都以Redis为准。这样即使数据库出现延迟也不会影响用户的秒杀体验。但注意最终还是要保障一致性的。我建议写一个“库存同步任务”每10秒流水对比一次Redis和数据库的库存差值不一致的以Redis为准去校正数据库。为什么不反过来以数据库为准因为在秒杀链路里Redis经过Lua原子扣减准确性更高。6.3 日志打印把CPU打满了也算经典事故有一次压测我发现应用CPU利用率特别高但业务没有明显卡顿查了很久才发现是日志惹的祸。当时秒杀接口的关键路径上打了好几条业务日志而且日志框架的异步写入队列积压严重导致CPU全部耗在日志序列化和写入上。后来我做了三件事第一秒杀QPS高的接口只打印核心业务日志比如扣减结果、订单号、耗时其他地方一律不打第二日志框架改成异步写入用log4j2的AsyncLogger第三敏感字段脱敏。这些优化之后CPU占用直接降了20%。日志这件事在平时看着不起眼但在秒杀这种高并发场景任何细微的资源浪费都可能被放大成故障。6.4 前端页面超时频繁后端却看起来很稳定有时候后端各项指标都正常但用户的请求依然大量超时。我排查下来问题往往出在网关或负载均衡的连接超时设置上。Nginx的proxy_read_timeout如果设置的太短比如默认60秒理论上不会超时但你如果用的是默认的proxy_connect_timeout配合KeepAlive就可能出现上游服务连接被复用却不释放的问题。这里我给出的排查思路是从客户端到Nginx到应用逐个跳点看日志记录每一跳的时间消耗定位到底在哪一层耗时最高。千万别只盯着应用层的日志因为超时可能发生在任何一跳上。6.5 我一个压测CPU数据的小技巧最后分享一个命令行小技巧排查CPU问题的时候别只看系统整体的CPU占用率要看具体哪个线程在消耗CPU。用top找到PID后再用top -H -p PID查看线程级别然后用jstack导出线程快照搜索CPU消耗最高的线程号再对应代码定位。这个方法在排查死循环、锁竞争、日志占用CPU的时候特别有效精确到代码行效率很高。另外如果你的系统用了SpringBoot建议把spring.jpa.open-in-view关掉这个配置默认是true会让每个请求在渲染前都占用一个数据库连接高并发下的连接池瞬间就满了。这是我见过无数团队踩过的隐性大坑。7. 秒杀系统上线后的全局复盘秒杀系统做完压测、修复问题之后别急着收工上线后的数据复盘才是最有价值的环节。我每次做完一场秒杀活动都会拉数据回来看包括用户参与人数、点击量、成功订单数、支付转化率、各环节的耗时分布、系统监控的告警记录。这些数据能帮你验证之前设计时做的预估是否准确。比如我预估某个环节的QPS是1万实际跑了2万那就说明我的拦截策略可能没有完全生效或者有外部流量进来。这时候就要分析流量来源进行调整。秒杀系统不是做完就结束了而是一个持续迭代的过程。我在实践中最大的感受是秒杀系统的设计要记住一个核心原则——“尽量把压力消化在源头不要传递到下游”。每一层都多拦截一点下游就轻松一分整体系统才会稳。这是我的个人经验你可以视作参考但最重要的是记住一个目标保证核心流程的正确性让用户每一笔订单都可追可查让库存每一件都对得上。这是秒杀系统的底线也是你作为系统设计者的底气。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑