尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

SpringBoot+Redis+MQ构建秒杀系统:防超卖与异步下单实战

发布时间:2026/9/29 19:26:25

资讯中心
01
ARTICLE

SpringBoot+Redis+MQ构建秒杀系统:防超卖与异步下单实战

SpringBoot+Redis+MQ构建秒杀系统:防超卖与异步下单实战
简介基于SpringBoot、MyBatis与MySQL构建的商城秒杀系统源码包面向具备一定Java Web基础的后端开发者与高并发场景学习者重点展示秒杀活动中高并发访问、缓存一致性、异步削峰与分布式协调等典型问题的工程化处理思路。压缩包共258个文件整体约451KB文件类型以170个XML为主覆盖MyBatis映射、Spring及相关中间件配置另有48个Java源码按Controller、Service、Mapper等分层实现业务逻辑12个JSP与JS、CSS负责页面展示SQL脚本可用于初始化数据库表结构目录结构清晰便于按模块研读。源码集成了Redis、RabbitMQ、ZooKeeper、Redisson等主流中间件能够直观看出缓存、消息队列、分布式协调和分布式锁在秒杀链路中的配合方式也能帮助读者理解各中间件在真实业务中的职责边界适合课程设计、毕业设计或需要快速搭建秒杀原型的开发者参考。当前已有361人学习下载。1. 先搞明白为什么秒杀系统是SpringBootMybatisMySQL项目里的硬骨头电商大促期间真正把系统打垮的往往不是复杂的业务逻辑而是短时间内集中在同一个商品、同一个接口上的并发流量。一份基于SpringBootMybatisMySQL、配合Redis和消息队列两类中间件构建的秒杀系统源码解决的就是这个流量洪峰问题。它不只包含一套可以跑通的下单流程还把库存防超卖、Redis预减库存、MQ异步落单、幂等兜底这几条秒杀系统的生命线完整实践了一遍。对于想搞懂高并发下单流程、或者正在准备电商方向后端面试的Java开发来说这套源码的价值在于你能直接看到中间件是怎么被织进SpringBoot项目里的——不是贴几个配置就完事而是真正用Redis挡住了大部分请求用消息队列把数据库压力削平。我在拆这份源码时感触最深的一点是秒杀系统最难的从来不是代码写不写得出来而是并发一上来哪条链路先扛不住你是否提前知道。2. 秒杀系统的架构骨架SpringBootMybatisMySQL与两类中间件的分工2.1 单体应用加中间件秒杀项目的三层职责划分秒杀系统最常见的误区是一上来就拆微服务。实际上一份能落地、能压测的秒杀源码绝大多数是基于单体应用加中间件的组合。原因很好理解秒杀的业务边界非常清晰商品、库存、订单都在同一个数据库里单体架构能让事务控制简单得多。SpringBoot负责把HTTP接口和Spring容器管理起来Mybatis负责把库存扣减和订单写入落到MySQLRedis和消息队列则把直击数据库的压力挡在外面。我拆这份源码时第一件事是看它的依赖关系。SpringBoot作为基础框架不用多说Mybatis负责数据访问层MySQL是唯一的数据持久化节点。中间件部分值得注意Redis承担了秒杀前置判断和库存预扣减消息队列则承担了下单请求的异步缓冲。分层上的逻辑很清晰Controller层只做参数校验和用户身份确认不碰任何业务Service层里秒杀下单被拆成了预减库存和异步落单两段Mapper层只通过Mybatis与MySQL交互。这里要注意的是MySQL在整个系统里仍然是最终的数据权威。Redis里的库存可以被清掉重建消息队列里的消息可以被重发但订单表和库存表的最终一致性必须由MySQL的事务来兜底。所以表设计是整个架构的地基。参数和配置上我看到这份源码在application.yml里把数据源连接池、Redis连接池和MQ的连接参数分得很开。我用表格整理一下这份源码里最关键的几组配置项配置分类关键参数说明MySQLinitial-size5, max-active50连接池初始连接与最大连接数压测时要重点调RedismaxTotal100, maxIdle20Lettuce连接池参数预减库存的吞吐瓶颈在这MQ队列并发消费线程数8~16消费端线程数决定落库速度不宜超过连接池上限这里有个容易忽略的细节MySQL连接池的max-active必须大于MQ消费线程数否则异步落单时数据库连接会被消费者线程抢光。具体的原因我在第5章的踩坑里会展开。2.2 表结构与核心字段设计库存和订单表该怎么建秒杀的表设计比普通商城多几个关键字段。我对照源码里的SQL脚本和Mapper文件整理了四张核心表秒杀商品表、商品表、秒杀订单表、用户表。秒杀商品表和普通商品表分离这是电商系统里很成熟的做法因为秒杀商品的库存、开始时间、结束时间、秒杀价格都属于活动数据不应该污染普通商品表。秒杀商品表的关键字段设计如下CREATE TABLE seckill_product ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 秒杀商品ID, product_id BIGINT NOT NULL COMMENT 关联普通商品ID, seckill_price DECIMAL(10,2) NOT NULL COMMENT 秒杀价格, stock_count INT NOT NULL COMMENT 可秒杀库存, start_time DATETIME NOT NULL COMMENT 秒杀开始时间, end_time DATETIME NOT NULL COMMENT 秒杀结束时间, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, PRIMARY KEY (id), KEY idx_product_id (product_id), KEY idx_start_end (start_time, end_time) ) ENGINEInnoDB COMMENT秒杀商品表;这份源码里有两个字段让我印象很深。第一个是version这是乐观锁实现的关键字段第3章我会专门讲它的用法。第二个是idx_start_end这个联合索引秒杀活动查询基本上都会带上开始时间和结束时间做范围过滤这个索引能避免全表扫描。秒杀订单表的设计上除了常规的用户ID、商品ID、订单状态之外源码里加了一个seckill_token字段这个字段用来做幂等判断。用户一次秒杀请求生成一个唯一令牌订单表对该字段建了唯一索引这样即使消息队列重复投递消息数据库层面的唯一索引也能挡住重复下单。CREATE TABLE seckill_order ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 用户ID, product_id BIGINT NOT NULL COMMENT 商品ID, seckill_token VARCHAR(64) NOT NULL COMMENT 秒杀令牌唯一, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_token (seckill_token) ) ENGINEInnoDB COMMENT秒杀订单表;这段DDL看着简单但它同时解决了两个问题一是查询订单时通过uk_token可以快速定位二是作为幂等约束挡住了重复数据处理。很多自己写秒杀练习的开发者会忽略唯一索引这一层只靠业务代码判断订单是否存在结果在并发场景下被重复消息钻了空子。需要强调的是秒杀表和订单表必须都是InnoDB引擎。MyISAM不支持行级锁高并发下update操作会直接锁表MySQL的并发能力会断崖式下降。如果你拿到这份源码后想自己改表第一件事就是确认引擎。2.3 一次秒杀的完整调用链从HTTP请求到订单落库把框架和表结构讲清楚后我按源码的调用顺序把一次秒杀请求经历的完整链路梳理一遍。这一步对后续读代码非常有帮助因为你拿到源码后最困惑的就是Controller里找不到任何下单逻辑。用户请求秒杀接口/seckill/{productId}带上用户身份信息。Controller层校验用户登录态和请求参数同时检查当前时间是否在秒杀活动窗口内。通过后进入Service层先查Redis里的商品库存。此时Redis中预存了活动开始时加载的库存量如果库存小于等于0直接返回已抢光。Redis库存充足时执行Lua脚本完成判断用户是否已秒杀过库存预减记录用户秒杀标识三个原子操作。预减成功后Service层不直接写MySQL而是把下单请求封装成消息发送到消息队列接口立即返回正在排队。消息队列的消费者收到消息后调用Mybatis的Mapper执行库存扣减和订单插入。扣减库存使用乐观锁更新确保数据库库存不会变成负数。用户通过查询接口轮询订单状态看到订单从排队中变成待支付秒杀流程完成。这条链路里最反直觉的设计是第4步接口收到请求后不创建订单而是马上返回。我看到不少刚接触秒杀系统的同学会在这个地方卡住担心消息发出去之后消费者挂了怎么办。实际上这份源码里有对应的兜底机制消息发送失败时会回补Redis库存并返回系统繁忙消费者处理失败时消息会进入重试队列。这些事情我都会在第5章仔细讲。我建议你拿到源码后按这个顺序去读SeckillController→SeckillService→SeckillRedisService→SeckillMQProducer→SeckillMQConsumer→SeckillMapper。这个顺序和实际请求方向是一致的能避免在代码里绕圈子。3. 防超卖的核心Mybatis里悲观锁与乐观锁的两种落地写法3.1 超卖问题的本质一行库存数据的并发更新超卖是所有秒杀系统第一个要解决的问题。它的本质很单纯数据库里只剩1件库存但两个用户的请求同时检查库存都看到库存大于0然后都执行了扣减最终库存变成-1。用MySQL的行锁和事务可以避免这个结果但加锁的粒度不同系统的吞吐能力也完全不同。Mybatis作为数据访问层本身不提供并发控制能力它把SQL原样传给MySQL执行。所以防超卖的核心其实是两件事第一SQL语句必须写成能够原子化扣减的形式第二事务隔离级别和锁的时机要设计对。这份源码里给出了两种实现我逐个拆开讲这样你能在面试里把乐观锁和悲观锁的区别这个问题回答得落到实处。3.2 悲观锁实现select for update搭配条件更新悲观锁的思路是先锁再查再改我看到这份源码在秒杀接口的备选方案里写了这种实现方式。核心SQL分为两步第一步用select for update锁住秒杀商品行第二步执行条件更新select idselectSeckillForUpdate resultTypeSeckillProduct SELECT id, product_id, stock_count, version FROM seckill_product WHERE id #{id} FOR UPDATE /select update iddeductStockByPessimistic UPDATE seckill_product SET stock_count stock_count - 1 WHERE id #{id} AND stock_count gt; 0 /update这段XML里最关键的是FOR UPDATE。MySQL的InnoDB引擎在执行这条语句时会对id #{id}这一行加排他锁其他事务想读这一行或者更新这一行都必须等当前事务提交后才能继续。加上stock_count 0这个条件等于在数据库层面做了双重校验。对应的Service层方法大致是这样Transactional(rollbackFor Exception.class) public boolean seckillByPessimisticLock(Long productId) { SeckillProduct product seckillMapper.selectSeckillForUpdate(productId); if (product null || product.getStockCount() 0) { return false; } int rows seckillMapper.deductStockByPessimistic(productId); return rows 0; }注意这里的处理顺序先锁行拿到当前库存判断库存是否大于0再执行扣减。Transactional保证了这三步在同一个数据库事务里事务提交时排他锁才释放。如果中途库存不足抛出了异常事务回滚锁自然释放。悲观锁方案的优点是逻辑简单、绝对安全适合并发量不是特别夸张的秒杀场景。缺点也很明显select for update会让所有请求串行排队MySQL每秒能处理的TPS取决于行锁的切换效率通常在几千这个量级。如果你做压测发现接口TPS上不去先怀疑这里。3.3 乐观锁实现version字段加条件更新乐观锁的思路是不锁行但更新时校验版本号。这份源码里实际启用的是这个方案因为它的秒杀架构里已经有Redis挡掉了大部分流量真正落到MySQL的并发并没有想象中那么大乐观锁足够用性能上还比悲观锁好。对应的Mapper方法是update iddeductStockByOptimistic UPDATE seckill_product SET stock_count stock_count - 1, version version 1 WHERE id #{id} AND stock_count gt; 0 AND version #{version} /updateService层需要先查一次当前的version然后执行更新Transactional(rollbackFor Exception.class) public boolean seckillByOptimisticLock(Long productId) { SeckillProduct product seckillMapper.selectById(productId); if (product null || product.getStockCount() 0) { return false; } int rows seckillMapper.deductStockByOptimistic(productId, product.getVersion()); return rows 0; }这段代码的关键在version #{version}这个条件执行更新时把之前查到的版本号带入如果这期间有另一个事务已经改过这一行版本号就变了MySQL执行update时匹配不到任何记录rows返回0当前请求就失败了。这就完成了数据没被改我才改被改过我就放弃的CAS逻辑。从代码里能看到一个细节stock_count 0这个条件被保留了下来。这是双层防线即使业务代码在查询后到更新前之间出现了异常数据库也能保证库存不会变成负数。我在面试中问过不少人为什么乐观锁还要加库存条件很多人答不上来其实这就是代码兜底数据库兜底的工程习惯。乐观锁的问题在于失败率。高并发下两个事务同时带着同一个version去更新只有一个会成功另一个只能返回已抢光。所以在秒杀这类场景里乐观锁通常配合Redis预减库存使用把失败尽量挡在Redis之前。3.4 两种锁怎么选一个真实的取舍逻辑这份源码的注释里也记录了这个取舍过程。我的判断标准是MySQL毛并发低于2000写请求时两种方案都能跑超过这个量级悲观锁可能反而不如乐观锁好用因为行锁排队会让响应时间直线上升但你也不能只依赖乐观锁因为失败请求要立刻返回不能占用数据库连接。所以实际项目中我见过最多的方案是Redis预减库存乐观锁落库。Redis先过滤掉90%以上的无效请求剩下的请求用乐观锁保证不出超卖。这种组合的成本最低代码也最容易维护。悲观锁更适合一次性扣减、并发量可控的后台操作比如运营在后台手动调整库存而不是面向C端用户的高并发秒杀。如果你拿这套源码做课程设计或者面试项目我建议你把两种实现都保留然后在方案说明里明确写出为什么线上默认用乐观锁而不是悲观锁这比单纯扔出一堆代码更有说服力。4. 预减库存与异步下单Redis和消息队列在秒杀里的协作4.1 Redis在秒杀项目里扮演什么角色先说清楚一件很多新手会搞混的事Redis在秒杀系统里不是数据库的替代品而是数据库的守门员。它承担三件事一是秒杀开始前把商品库存预热到内存中避免查询库存直接打到MySQL二是用Lua脚本原子性地完成库存预扣三是在Redis里记录秒杀成功的用户标识挡住同一个用户重复秒杀。这份源码里的Redis存储设计是分开的我用表格把这几个key的职责列出来Key类型作用失效策略seckill:stock:{productId}String秒杀库存预扣计数活动结束后删除seckill:user:{userId}:{productId}String标记用户已秒杀成功活动结束后删除seckill:product:{productId}Hash秒杀商品信息缓存预热时写入这里有一个细节seckill:stock在源码里用的是String类型值是预热时的初始库存数每成功预减一次就decr一次。你可能会问为什么不直接用数据库的字段值原因是秒杀开始瞬间会有大量读请求如果把读库存的流量放给MySQL连接池很容易被打满。Redis的单线程模型在处理这种简单计数时每秒可以扛十几万次操作比数据库高两个量级以上。活动时间窗口的判断也放在Redis层。源码里在预热时会检查当前时间是否在秒杀活动时间段内接口层再接到来请求时还会用当前时间再做一次校验。4.2 Lua脚本把判断、扣减、标记变成原子操作Redis预减库存最大的坑是判断和扣减之间不能有网络间隙。你写Java代码先查库存再扣减如果两次操作之间另一个请求把库存扣光了当前请求就会拿着过期的库存数据执行扣减最后把库存扣成负数。解决这个问题的标准做法是Lua脚本。这套源码的SeckillRedisService里内置了一段Lua逻辑我拆解如下-- KEYS[1]: seckill:stock:{productId} -- KEYS[2]: seckill:user:{userId}:{productId} -- ARGV[1]: 商品库存预扣上限 if redis.call(EXISTS, KEYS[2]) 1 then return 0 -- 该用户已秒杀过直接拒绝 end local stock tonumber(redis.call(GET, KEYS[1])) if not stock or stock 0 then return 0 -- 库存不足 end redis.call(DECR, KEYS[1]) redis.call(SET, KEYS[2], 1, EX, 3600) return 1 -- 预扣成功Java侧调用这段脚本的代码如下public boolean trySeckill(Long productId, Long userId) { String stockKey seckill:stock: productId; String userKey seckill:user: userId : productId; Long result redisTemplate.execute( seckillLuaScript, Arrays.asList(stockKey, userKey), String.valueOf(redisStockLimit) ); return result ! null result 1L; }Lua脚本在Redis里执行时是原子的Redis是单线程处理命令脚本执行过程中不会被其他客户端的命令插入所以判断用户已秒杀检查库存扣减库存记录用户这四个动作整体不可分割。这里有几个参数值得注意。第一个参数是KEYS[2]的过期时间源码里设了3600秒也就是1小时。这个时间要大于整个秒杀活动的持续时间加上订单支付的保留时间否则用户秒杀成功后标识提前过期可能造成同一个人在活动期间重复秒杀。第二个参数redisStockLimit是预扣库存的上限它通常等于数据库里的实际库存不建议把预扣上限设得比数据库库存大否则会有部分用户在Redis层看到有库存但数据库已经无货造成体验问题。4.3 消息队列异步落单削峰的关键手段预减库存成功之后如果直接让请求去写MySQL数据库依然扛不住。所以这份源码里选了一个更聪明的做法接口层返回排队中把真实的库存扣减和订单创建放到消息队列的消费端去做。生产者这边的逻辑很简单把秒杀令牌、用户ID、商品ID封装成一个消息对象发送到指定队列public boolean sendSeckillMessage(SeckillMessage message) { try { String messageBody JSON.toJSONString(message); rabbitTemplate.convertAndSend(SECKILL_EXCHANGE, SECKILL_ROUTING_KEY, messageBody); return true; } catch (Exception e) { log.error(秒杀消息发送失败, e); return false; } }生产者的代码虽然短但有一个不算显眼的细节消息发送失败时返回falseService层拿到false后会执行Redis库存回补把之前预减的库存加回去。如果不做这个回补用户会卡在排队中但永远不会出结果。消费者这边的核心代码是把消息还原成下单操作RabbitListener(queues SECKILL_QUEUE, concurrency 8-16) public void onSeckillMessage(SeckillMessage message) { try { seckillService.createOrderWithOptimisticLock(message); } catch (DuplicateOrderException e) { log.warn(重复下单忽略该消息token{}, message.getSeckillToken()); } catch (Exception e) { log.error(下单失败稍后重试token{}, message.getSeckillToken()); throw new AmqpRejectAndDontRequeueException(e); } }这里concurrency 8-16是RabbitMQ监听容器的消费者并发范围它的作用是让消息消费线程在8到16之间动态伸缩。这个参数不是随便写的它必须和MySQL连接池的大小匹配。如果连接池max-active是50消费者并发设到20以上理论上可行但一旦数据库响应变慢连接池会被消费线程占满其他业务查询全部超时。消费失败的处理上这套源码使用AmqpRejectAndDontRequeueException来放弃无法处理的消息同时配合定时任务扫描Redis中预扣成功但长时间没有生成订单的用户做订单状态补偿。这种设计能处理消费者宕机、数据库超时等最常见的故障情况。4.4 前端怎么拿到秒杀结果轮询与令牌查询异步下单有一个代价接口不能同步返回订单号。这套源码在Controller层留了一个查询接口用户在等待页面轮询订单状态GetMapping(/order/result) public ResultOrderVO queryOrder(Long productId, Long userId) { OrderVO order seckillOrderMapper.selectByUserAndProduct(userId, productId); if (order null) { return Result.error(排队中请稍后); } return Result.ok(order); }前端轮询的时间间隔一般是1到2秒源码里写的是1.5秒。轮询接口本身压力不大因为订单创建是异步的查询只会落在MySQL的主键索引或唯一索引上。有一个容易踩的小坑轮询接口不能查Redis而要直接查MySQL订单表。因为消息队列可能存在消费延迟Redis里标记的是预扣成功不等于订单已创建。只有在数据库里能查到订单记录才能给用户明确的成功结果。5. 避坑指南秒杀系统最常见的四个翻车现场5.1 Redis库存和MySQL库存对不上现象秒杀结束后Redis里显示还有库存但数据库显示已经卖完或者反过来数据库还有库存但Redis提前显示已抢光导致大量用户看得到买不到。原因这套源码中有两个地方会造成Redis与MySQL库存不一致。第一个是消息发送失败后没有回补Redis库存第二个是消费者重复消费消息导致数据库库存扣得比Redis多。另外手动改数据库库存而不同步更新Redis也会造成偏差。解决最直接的办法是活动结束后以MySQL的实际库存为准把Redis里的预扣库存key直接删除并在活动页显示已结束。如果想在活动期间修正偏差我在实践中会写一个定时任务每隔30秒对比一次Redis剩余库存和数据库剩余库存差值超过阈值就用数据库的值重建Redis库存。需要提醒的是这个定时任务的扫描频率不能太高否则会给数据库增加额外的读压力。5.2 消息队列重复消费导致重复下单现象同一用户的秒杀订单表里出现了两条记录或者用户秒杀成功了一次却收到了两个不同的订单号。原因RabbitMQ在消费者处理成功但还没确认消息时发生网络闪断消息会被重新投递或者生产者在服务超时后重试发送也会产生重复消息。数据库表的seckill_token唯一索引虽然能挡住重复插入但如果Mapper的insert语句没有对唯一索引冲突做处理会导致消费者抛出异常并触发消息重投陷入死循环。解决我在第2章特意强调了uk_token唯一索引它就是这道防线的关键。消费者在插入订单时捕获DuplicateKeyException把它当作正常情况处理而不是作为一种异常继续重试。然后在下单方法里需要实现一个先查后插再加唯一索引兜底的三层策略先根据token查询订单查到就说明是重复消息直接返回查不到再插入订单如果插入时唯一索引报错同样视为重复下单。5.3 压测时数据库连接池被消费线程打满现象压测刚开始时接口响应正常几十秒后大量请求超时数据库连接池报Getting connection timeout同时MQ消费端堆积了大量未消费的消息。原因消费者并发线程数配得太高或者MySQL连接池太小。秒杀消息消费线程每个占用一个数据库连接如果max-active是20消费并发线程却设置了30那么连接池很快就被消费线程占完后续的商品查询、订单查询、后台统计全部排队等待。加上Redis预减库存的速度远高于消费落库的速度消息在队列里持续堆积问题会像滚雪球一样放大。解决先把消费者并发上限调整到连接池最大连接数的一半以下比如连接池50就设置8到16。其次给不同业务配置独立的DataSource秒杀落库使用单独的连接池避免和后台查询互相争抢。压测时还要重点观察活跃连接数的监控曲线如果长时间维持在最大值附近说明消费能力已经到顶需要增加消费者实例而不是继续调大线程数。5.4 秒杀成功后用户却收不到订单结果现象用户在秒杀页面点击后提示排队中等了十几秒之后依然没有结果订单查询接口一直返回排队中数据库里也查不到订单记录。原因这类问题90%以上是消息根本没有被消费或者消费了但下单事务回滚了。潜在原因有RabbitMQ队列绑定关系在项目重启后丢失消费者抛出的异常没有正确进入重试策略消息被直接丢弃数据库在异常后无法连接导致事务回滚。解决我的排查习惯是分三步走。第一步看RabbitMQ控制台的Queue状态确认Queue中的Ready消息数是不是持续增长如果增长说明消费者没有正常消费先看消费者日志里有没有报错。第二步看消费者日志中createOrderWithOptimisticLock方法有没有抛出异常重点检查DuplicateKeyException是否被当作异常处理了。第三步直接查订单表用userId和productId条件搜索确认订单是否真的没创建创建了但状态不对也是常见情况。我做过一个习惯性的总结凡是秒杀结果丢失的故障七成出在消费幂等没做好另外三成出在消息投递确认配置不当。6. 压测验证与上线前的检查清单让秒杀系统经得起真实流量6.1 JMeter压测模型与参数一种验收方法是JMeter。秒杀压测最有代表性的模型是1000个线程并发请求同一个商品ID持续60秒。我在复查这套源码时会把线程数分三档测100、500、1000记录两个指标TPS和接口响应P99。为什么P99比平均响应时间重要平均响应时间很容易被少量极快请求拉低而P99能真实反映最差一批用户的实际体验。秒杀接口的目标是P99低于500毫秒如果超过这个值说明Redis或MySQL层面的排队已经开始影响体验了。6.2 关键监控指标压测时重点看四块指标阈值危险信号Redis QPS不超过Redis单实例能力上限达到上限后命令排队响应变慢MySQL活跃连接数不超过连接池80%长时间占满说明消费线程配置不合理MQ队列积压活动结束后应被消费完毕Ready堆积且持续增长接口P99小于500ms超过1s说明链路有严重瓶颈6.3 上线前的强制检查项根据这份源码给到的工程配置我在上线前会强制自己走一遍检查清单秒杀活动时间与Redis库存预热时间是否匹配预热必须提前至少10秒完成消息队列的持久化开关是否打开队列和交换机是否设置了durableMySQL的max_active连接数是否大于MQ消费线程数乐观锁的version字段是否在每次扣减后都进行了更新秒杀订单表的唯一索引是否已创建。其中唯一索引这条我赔过学费。有一次我准备发布秒杀活动前一天的压测全过上线当天发现同一用户重复秒杀后能创建两张订单查了半天才发现是因为测试环境用的SQL脚本和正式环境不完全一致正式环境的表缺少uk_token索引。从那以后我每次改动表结构都会在发布脚本里同时提供DDL和校验SQL校验SQL直接查information_schema.statistics表确认索引是否存在。秒杀系统这类高并发场景任何一道防线缺失都会在流量真正到来的时候暴露出来希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。