news 2026/9/29 19:25:24

SpringBoot+Redis+RabbitMQ构建高并发秒杀系统实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Redis+RabbitMQ构建高并发秒杀系统实战解析

简介:一份基于SpringBoot、MyBatis、MySQL及多种中间件构建的商城秒杀系统源码包,面向已掌握Java Web基础知识、希望深入高并发场景下秒杀业务落地的开发者。项目整合Redis缓存、RabbitMQ消息队列、ZooKeeper统一协调调度中心、Redisson分布式锁等核心中间件,完整呈现从商品库存预减、流量削峰、异步下单到订单一致性的实现思路。压缩包共258个文件,以170个XML配置文件、48个Java源文件、12个JSP页面为主,另含JS/CSS静态资源及SQL初始化脚本,整体仅451KB,目录按api、model、server等模块划分,便于快速定位与部署。目前已有361人学习下载。通过此源码可深入理解秒杀系统在缓存、消息、锁协同下的设计要点,掌握防超卖、缓存击穿、串行化处理等关键难点的落地解法,熟悉分布式环境下数据一致性保障手段,也适合作为中小型电商项目的参考脚手架,便于二次开发与功能扩展。

1. 秒杀系统的本质:用 SpringBoot 搭骨架,却要小心“架构幻觉”

把“基于SpringBoot+Mybatis+Mysql+中间件构建的商城秒杀系统源码.zip”这个标题拆开看,它其实是绝大多数 Java 从业者第一次接触高并发时必经的经典组合:Web 层用 SpringBoot 收请求,持久层用 Mybatis 拼 SQL,数据落在 Mysql,中间件负责扛流量和削峰。这类项目真正难的不是 CRUD,而是秒杀特有的三座大山——瞬时流量打垮数据库、库存超卖、重复下单。网上不少类似源码把 Redis 和 MQ 加进来就算“高并发”,但跑起来一压测就露馅:要么库存被扣成负数,要么订单重复落库,要么 Mysql 连接池直接被拖死。这篇笔记我会按一条可复现的落地路径拆解这个项目——先建表写 SQL,再用中间件把流量挡住,最后告诉你哪些参数不调必翻车、哪些坑是血泪经验换来的。适合正在做毕业设计、跳槽准备高并发项目经验,或者公司真要上一套秒杀活动但没时间从零设计的后端开发。

2. 数据库建模与 Mybatis 持久层:秒杀的 SQL 先行,锁边界先踩

2.1 订单表别设计成第三范式,冗余字段要大胆加

秒杀系统的表结构通常只有三张核心表:秒杀商品表、订单表、支付流水表。很多新手从商城系统复制过来一张订单主表、一张订单明细表,还带了商品快照外键,这在日常下单没问题,但秒杀场景下是给自己埋雷。为什么?因为秒杀订单不需要复杂的多商品购物车逻辑,用户一次只能抢一件,订单明细表属于纯浪费。

我一般在项目中是这样设计的:miaosha_goods表存秒杀商品,核心字段是goods_id(关联普通商品)、miaosha_price、stock_count、start_time、end_time;miaosha_order表直接冗余商品名称、秒杀价格、商品主图 URL,哪怕商品后续改了信息,用户看到的始终是下单那一刻的快照。这样查询订单列表时不会被迫 JOIN 商品表,少一次关联就少一点数据库压力。

CREATE TABLE `miaosha_goods` ( `id` bigint NOT NULL AUTO_INCREMENT, `goods_id` bigint NOT NULL COMMENT '普通商品id', `miaosha_price` decimal(10,2) NOT NULL COMMENT '秒杀价', `stock_count` int NOT NULL COMMENT '库存,-1表示无限制', `start_time` datetime NOT NULL, `end_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_goods_id` (`goods_id`), KEY `idx_start_end` (`start_time`, `end_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `miaosha_order` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `goods_id` bigint NOT NULL COMMENT '关联普通商品', `order_id` bigint DEFAULT NULL COMMENT '支付订单id', `goods_name` varchar(120) NOT NULL COMMENT '商品名称快照', `miaosha_price` decimal(10,2) NOT NULL COMMENT '下单时秒杀价快照', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0新建 1支付成功 2取消', `create_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_goods` (`user_id`, `goods_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段 DDL 里最关键的是uk_user_goods唯一索引。它不是随便加的,而是用来从数据库层面拦截“同一个用户在同一秒杀活动中对同一商品重复下单”。就算应用层加锁漏了,或者消息队列重投了,这条唯一索引都会让第二次 INSERT 直接报Duplicate entry,配合捕抓异常转换成“您已抢购过该商品”的提示,这是最后一道兜底,绝不能去掉。

2.2 Mybatis XML 里的原子扣减:WHERE stock_count > 0 才是核心

秒杀扣库存的 SQL 几乎每个人都会写错。新手常见的做法是先 SELECT 查库存,判断大于 0,再执行 UPDATE 扣减,最后 INSERT 订单。这个流程在并发量只有几百的时候看似没问题,但一旦 QPS 上了几千,“查询到的库存”和“实际扣减时的库存”之间会插进无数个并发请求,最后一个请求可能拿不到库存却依然执行了 INSERT——此时库存表已经变成负数。

正确做法是把判断和扣减合成一条原子 UPDATE。Mybatis 的 XML 里面这样写:

<update id="reduceStockByCondition"> UPDATE miaosha_goods SET stock_count = stock_count - 1 WHERE id = #{goodsId} AND stock_count > 0 AND start_time &lt;= NOW() AND end_time &gt;= NOW() </update>

这段 SQL 的逻辑用一句话讲就是:能更新成功返回行数为 1,说明你拿到了库存;返回行数为 0,说明库存不足或不在活动窗口内。根本不需要 SELECT 先查一遍。stock_count > 0这个条件在 InnoDB 行锁机制下,会锁住这条商品记录直到事务提交,所以并发扣减是串行化的,不会扣成负数。

有一个隐蔽的坑要提醒你:UPDATE 返回的行数不是“受影响的行数”,而是“匹配到且被修改的行数”。如果数据库配置了sql_safe_updates,或者 Mybatis 的useAffectedRows设置不同,返回值的含义可能不一样。我踩过的版本坑是在 MySQL 驱动 8.0 以后,JDBC 默认useAffectedRows=false,UPDATE时如果stock_count值没变(比如并发完全串行时),返回行数是匹配行数而不是真实修改行数。好在扣减场景每次都会变化,一般不受影响,但如果你把这条 SQL 改造成 “UPDATE 库存为 0 则视为锁定” 这类写法,返回含义必须心里有数。

2.3 事务边界:只把“扣库存+写订单”包在一起,前置校验一律不要进事务

把事务范围划大是秒杀系统性能杀手。很多源码里,从校验用户是否登录、校验是否重复秒杀、到扣库存、生成订单、更新支付流水,一整串操作全部包在@Transactional里。这会导致事务持有数据库连接的时间被拉长,连接池很快被打满,后面的请求全部排队等连接,表现就是接口越来越慢,最终超时。

我一般是这样拆的:前置校验(Redis 查用户是否已秒杀过、本地内存判断活动是否开始)放在事务之外;事务里面只做两件事——扣库存的 UPDATE 和 INSERT 订单;事务提交之后,再发 MQ 消息通知支付服务。核心结构如下:

@Service public class MiaoshaService { @Autowired private MiaoshaGoodsMapper goodsMapper; @Autowired private MiaoshaOrderMapper orderMapper; @Transactional(rollbackFor = Exception.class) public OrderResult doMiaosha(Long userId, Long goodsId) { // 1. 原子扣减库存,行锁保护 int rows = goodsMapper.reduceStockByCondition(goodsId); if (rows <= 0) { throw new MiaoshaException(StockNotEnough); } // 2. 生成订单,唯一索引兜底重复下单 MiaoshaOrder order = new MiaoshaOrder(); order.setUserId(userId); order.setGoodsId(goodsId); order.setStatus(0); order.setCreateTime(new Date()); try { orderMapper.insert(order); } catch (DuplicateKeyException e) { // 库存扣了但订单插入失败,回滚让库存释放 throw new MiaoshaException(RepeatMiaosha); } return OrderResult.success(order.getId()); } }

这段代码最容易被忽略的是DuplicateKeyException的处理。如果没有唯一索引,重复下单可能直接把订单表写爆;有了唯一索引,第二次插入会抛异常,但此时库存已经扣掉了。这里特意让异常向上抛,触发事务回滚,库存自动加回来。如果不回滚就会出现“用户看着库存少了一件,实际订单却不存在”的数据不一致。事务回滚在这里就是用户的后悔药,释放被无效应答占用的库存。

3. Redis 预减库存与限流拦截:把瞬时压力前置到内存层

3.1 为什么必须做库存预热:数据库行锁扛不住万级并发

前面那套纯数据库的事务方案在每秒几百并发时是可行的,但秒杀的真实场景往往集中在开抢后的前几秒,比如某件商品放 3000 件库存,开抢瞬间可能涌入几万请求。这些请求如果全部打到 Mysql,即使每条 UPDATE 只锁一行,InnoDB 的并发处理能力也会被锁等待拖垮。更麻烦的是大量请求都在等一下把锁,后面排队的连接把连接池占满,整个应用的其他接口全部不可用。

所以常见的秒杀方案必须引入 Redis 做库存前置。做法是活动开始前把商品的库存数加载到 Redis,用户在接口层先对 Redis 的库存做预减,预减成功才允许进入后端的数据库事务;预减失败直接返回“已抢光”,完全不碰数据库。这样数据库承担的并发量从几万降到几百,才扛得住。

3.2 Redis 预热与 Lua 原子脚本:预减库存的正确姿势

预热最简单的做法是在 SpringBoot 启动时用ApplicationRunner加载商品库存,或者在后台管理端发布活动时主动把库存推送到 Redis。注意 Redis 的库存数据必须和数据库的stock_count保持同源,活动的初始库存以数据库为准,Redis 只是它的缓存副本。

预减库存不能简单的decr一句完事,因为decr会把库存减成负数,必须配合判断。这里推荐用 Lua 脚本保证原子性,否则“判断+扣减”两步操作在并发下会有时间窗口:

-- 判断库存并扣减的 Lua 脚本 local stock_key = KEYS[1] local requested = tonumber(ARGV[1]) local current = tonumber(redis.call('get', stock_key)) if current == false then return -1 end if current < requested then return 0 end redis.call('decrby', stock_key, requested) return 1

Java 侧用DefaultRedisScript执行这段脚本,得到 1 表示预减成功,拿到入场资格;得到 0 表示库存不足,直接返回秒杀失败。这里要说明一个边界:为什么不用 Redis 事务MULTI/EXEC?因为 Lua 脚本是原子执行的,脚本执行期间不会被其他客户端命令插入,而WATCH/MULTI/EXEC在库存热点上会造成大量重试,性能差一个量级。

参数上需要特别注意 Redis 的键过期时间。常见做法是给库存 key 设置活动结束时间作为过期时间,但预减成功后如果用户下单失败,Redis 里的库存不会自动加回,这会导致数据库实际库存比 Redis 多,活动结束后对不上账。我一般用两种方案处理:一是直接让 Redis 库存承担“超卖风控”职责,Redis 扣减成功但数据库事务失败时,由事务的补偿逻辑对 Redis 的 key 做incr回补;二是干脆不设置过期时间,活动结束后写一个对账脚本,按数据库实际库存校正 Redis。方案二更稳,但需要多做一步清理。

3.3 限流拦截器:别让无意义的请求打到业务层

预减库存挡掉了大部分库存不足的请求,但活动刚开始那几秒,即使库存充足,也会有一批请求既不买商品也不是恶意攻击,而是抢购软件在刷接口。所以秒杀系统还需要一层限流,拦截器通常做两个维度:一是单位时间内单 IP 的请求数上限,二是单用户的对同一商品的重复请求拦截。

@Component public class MiaoshaRateLimitInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate redisTemplate; private static final int MAX_REQUESTS_PER_SECOND = 5; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userId = request.getHeader("userId"); String goodsId = request.getParameter("goodsId"); if (userId == null || goodsId == null) { response.setStatus(400); return false; } // 简单计数器,key 为 ip:userId:goodsId,窗口一秒 String key = "rate:" + request.getRemoteAddr() + ":" + userId + ":" + goodsId; Long count = redisTemplate.opsForValue().increment(key); if (count != null && count == 1) { redisTemplate.expire(key, 1, TimeUnit.SECONDS); } if (count != null && count > MAX_REQUESTS_PER_SECOND) { response.setStatus(429); return false; } return true; } }

这里用的是最简单的计数器限流,够用但不够精确,因为计数器算法在窗口切换瞬间会允许两倍于阈值的请求通过。如果活动价值高,建议直接换成 Redisson 的RRateLimiter(令牌桶),或者 Guava 的RateLimiter做单机限流再加一层 Redis 分布式限流。我自己的项目里是单机限流和分布式限流同时启用:单机限流拦掉同一个 JVM 内的重复刷,分布式限流控制整体 QPS 不超过数据库能力的 70%。压测时你会发现限流参数是玄学,调小了误伤正常用户,调大了数据库还是会被打穿,所以阈值必须根据压测数据反推,不能拍脑袋定。

4. RabbitMQ 异步下单:把强一致变成最终一致,削峰填谷的完整链路

4.1 生产者:预减库存成功后投递消息,不落数据库

Redis 预减成功还不能直接下单,因为真正扣库存和生成订单的操作是重事务,如果让用户请求同步等待事务完成,接口响应时间会包含数据库锁等待时间,体验很差。秒杀的正确姿势是:Redis 预减成功之后,立刻把“用户+商品”信息封装成消息投递给 RabbitMQ,接口直接返回“抢购成功,正在排队处理订单”。用户收到成功提示后,后台消费者异步完成真实扣库和订单落库。

项目里常见的队列结构是这样的:交换机direct,路由键miaosha.order.create,队列miaosha_order_queue,消费者在另外一个服务或者同一个服务里独立部署。消息体要尽量精简,只放必要字段,避免把整个用户对象序列化进去,毕竟 MQ 的消息要过磁盘的,消息越大吞吐越低。

public class MiaoshaMessage { private Long userId; private Long goodsId; private Long requestId; // 防重令牌,唯一标识一次抢购 // getter/setter 省略 }

生产者发送前需要配置confirm回调,确保消息真正到达 Broker。没有确认机制的话,Redis 库存扣了但消息丢了,用户永远等不到订单,这种事故在秒杀场景是致命的——你没法给用户交代“系统繁忙”之外的答案。

4.2 消费者:手动 ACK 与库存二次校验是必选项

消费者拿到消息后开始执行数据库事务。但这里有个很多人忽略的问题:消息从投递到消费之间是有延迟的,可能消费者拿到消息时活动已经结束,或者 Redis 预减成功的那批请求已经超过了数据库真实库存。因此消费者里必须再执行一次库存校验,不能盲目信任 Redis 的结果。

@Component @Slf4j public class MiaoshaOrderConsumer { @Autowired private MiaoshaService miaoshaService; @RabbitListener(queues = "miaosha_order_queue", ackMode = "MANUAL") public void onMessage(Channel channel, @Payload MiaoshaMessage message, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException { try { // 消费者事务:扣库存 + 建订单 miaoshaService.doMiaosha(message.getUserId(), message.getGoodsId()); // 成功,手动确认 channel.basicAck(deliveryTag, false); } catch (MiaoshaException e) { // 业务失败,比如库存不足,直接确认丢弃,不回补 Redis(这里要配合补偿策略) channel.basicAck(deliveryTag, false); log.warn("消费秒杀订单失败, reason={}, userId={}, goodsId={}", e.getMessage(), message.getUserId(), message.getGoodsId()); } catch (Exception e) { // 未知异常,不确认,让 MQ 重投 log.error("消费秒杀订单异常", e); channel.basicNack(deliveryTag, false, true); } } }

手动 ACK 是必须的,因为默认的自动确认模式下,消费者一旦从队列取出消息就视为已消费,如果此时消费者宕机或者数据库事务失败,消息就永久丢失了。手动 ACK 配合basicNack的requeue=true才可能让消息重投。但注意:不要把业务失败的消息也nack回去重投,比如“库存不足”这种业务异常,重投一百次也是一样的结果,只会造成无效的数据库压力。正确的做法是把业务失败的消息也 ack 掉,只对系统异常重投。

4.3 消费幂等:消息重投如何不产生重复订单

RabbitMQ 的重投虽然能解决消息丢失,但会带来重复消费的问题。消费者把消息取出后处理到一半挂了,Broker 没收到 ACK,消息会被重新投递到其他消费者或本消费者——此时上一次执行可能已经插入了订单,只是没来得及 ACK。如果消费者直接再执行一次扣库存和插单,库存会多扣一件,订单表出现两条一模一样的记录。

幂等方案在项目里用两层:第一层是数据库的唯一索引,这在第二章已经讲过了;第二层是消费者在执行业务前先用requestId查 Redis 或数据库判断是否处理过。实际效果是:唯一索引兜底,Redis 标记避免无谓的数据库插入尝试。这里推荐的做法是把requestId也作为miaosha_order表的一个冗余字段加上唯一索引,这样即使 Redis 数据被清掉,数据库依然能挡住重复。

5. 秒杀避坑:五个最常见的翻车现场与排查路径

5.1 现象:库存显示已抢光,但数据库还有货

这个翻车场景特别经典:前端一直提示“已售罄”,后台一查stock_count还有很多。原因是 Redis 预减库存时扣得太狠——某个用户的请求预减成功了,但 MQ 消息投递失败或者消费者事务回滚,Redis 的库存没有同步回补。Redis 和数据库库存失去平衡,且没有对账机制。

解决:消费者事务失败时,必须把失败消息体里的goodsId收集起来,异步回补 Redis 库存。回补动作必须保证幂等,即同一个requestId只回补一次。我落地的方案是回补前先查 Redis 里是否存在“已消费”标记,存在则跳过。另外每天凌晨跑一次对账脚本,以数据库实际库存为准重建 Redis 的库存值,双保险。

5.2 现象:同一个用户同一商品下单成功两次

这个问题的隐蔽性很强,表面看唯一索引已经加了,但压测时依然出现了两条订单。排查下来是唯一索引建错了列——只给user_id建了普通索引,没有把user_id和goods_id组合起来建唯一索引。另一个可能:订单表拆了分表,唯一索引被分到不同的物理表里,数据库层的唯一约束失效。解决:回到第二章的 DDL,重新确认唯一索引的组合字段,不要想当然。分表场景下唯一约束要靠 Redis 预减时加分布式锁,锁粒度为userId:goodsId,但分布式锁本身也有误删问题,所以压测前的表结构审查不能省。

5.3 现象:Redis 突然连接不上,秒杀接口直接 500

活动进行中 Redis 挂了,所有请求瞬间全部打到数据库,数据库也扛不住,整个服务雪崩。这种事故的原因多半是 Redis 单点部署且没有配置降级。秒杀场景对 Redis 的依赖很重,但你不能假设 Redis 永不宕机。

解决:给 Redis 增加主从哨兵架构,同时秒杀接口要配置降级开关——Redis 不可用时,直接返回“活动太火爆,请稍后重试”,而不是抛异常让服务 500。注意这里的降级不是让请求落到数据库,因为降级时数据库未必能扛住;正确的降级策略是让用户稍后重试,等 Redis 恢复后再参与秒杀。如果你觉得“稍后重试”太影响体验,也可以降级到纯数据库模式,但此时必须把限流阈值下调到数据库能承受的 20%,这是活命策略。

5.4 现象:数据库连接池爆满,Mysql 报 Too many connections

连接池满往往不是单一方造成的,而是事务范围过大、慢 SQL、以及连接池最大连接数配置过高三方面共同作用。秒杀项目里常见的问题是maxActive直接配了 200,但数据库本身的max_connections只有 151,应用还没打满数据库就先拒绝了。

解决:连接池参数必须按压测结果调。以 HikariCP 为例,maximumPoolSize建议设置为数据库核数 * 2 + 磁盘IO等待系数,但秒杀场景我一般控制在 30 到 50 之间,锁竞争多的场景连接数大了反而时延更高。同时排查是否有慢查询拖长事务时间,开启慢查询日志定位哪些 SQL 执行超过 500ms。秒杀项目里九成是MiaoshaOrderMapper.insert插入时并发插入同一索引页造成锁等待,可以适当调大innodb_buffer_pool_size减少磁盘 IO。

5.5 现象:用户反馈“抢到了但一直没生成订单”

消息队列积压了。秒杀开始时流量瞬间冲高,MQ 消费者消费速度跟不上生产速度,队列越积越长,用户的订单迟迟不落库。常见原因:消费者配置了prefetch=1,每次只取一条消息,而每条消息处理包含一次事务提交的耗时,消费能力被严重限制。

解决:把prefetch调整到 50 到 100,让消费者每次拉取一批消息在本地处理。同时确认消费者线程数——@RabbitListener的concurrency参数在秒杀场景下直接调到 10 到 20 是合理的,但线程数增加会加大数据库并发,必须同步校对连接池大小。另一个补救措施是对活动进行分批次开抢,比如 10 万件库存拆成 10 场,每场间隔五分钟,给 MQ 充足的时间消化存量消息,这也是运营侧最省心的消峰策略。

6. 用 JMeter 验证秒杀系统:压测参数与性能瓶颈定位

6.1 压测场景与线程组设计

项目上线前必须压测,否则你根本不知道该给运营报多少库存。JMeter 用阶梯线程组模拟瞬间流量,比普通线程组更贴近秒杀特征。我习惯把线程数设置在预估峰值的 1.5 倍,比如预估 5000 人同时抢,就压 7500 并发,持续时间 60 秒,让流量以 1 秒内全部注入的方式压。

# JMeter 压测计划关键参数示例(非脚本文件,参数解释用) Threads (users): 7500 Ramp-up period: 1 Loop count: 1 HTTP Request: POST /miaosha/doMiaosha Parameters: goodsId=1001&userId=${__Random(1,100000)}

这里Ramp-up period=1意味着 7500 个线程在 1 秒内全部启动,真实的秒杀流量曲线比这个更陡,所以 1 秒陡增已经足够暴露问题。别把 Ramp-up 拉到 60 秒,那压出来的是匀速负载,跟秒杀场景是两回事。

6.2 三个核心指标怎么看

压测结果里最值得盯的是三个:TPS、平均响应时间、错误率。秒杀场景下 TPS 看峰值而不是平均值;响应时间看 99 分位而不是平均值,因为秒杀请求本来就是大量快速失败、少量成功,平均值会被成功响应拉低;错误率则要区分 HTTP 500 和业务返回“已抢光”——后者不是错误,前者的比例必须控制在 0.1% 以内。

如果 99 分位响应时间超过 800ms,优先看是否卡在数据库行锁等待,方法是进入 Mysql 执行SHOW ENGINE INNODB STATUS查看TRANSACTIONS段落的锁等待计数。如果锁等待高,优先优化的是事务时间而不是加机器。

6.3 从压测结果反推中间件参数

压测之后你会拿到一组数据:数据库事务吞吐上限、Redis 预减的 QPS 上限、MQ 消费速率上限。这三个数决定了整个系统的最大容量。容量规划公式建议这样算:预估峰值流量 * 超卖风控系数(0.8) <= min(Redis 预减 QPS,数据库事务吞吐上限)。如果压测结果显示数据库事务只有每秒 300 笔,那么活动库存就不能超过 300 除以活动持续秒数,或者你必须把 MQ 消费者扩容一倍。

我习惯把每次压测的数据整理成一张表:并发数、Redis 预减 TPS、数据库事务 TPS、MQ 积压数、接口 99 分位耗时、错误率。调整 2 到 3 组参数后再压一轮对比,比如先调prefetch,再调连接池,最后调消费者线程数。这个方法论比盲目堆机器靠谱得多,也方便你向团队解释为什么容量是这么定的。

6.4 上线前最后一道检查清单

秒杀系统上线前我会花半小时过一遍自己的固定检查项:Redis key 的过期时间是否覆盖整个活动周期;MQ 队列是否设置了死信队列,消费失败的消息必须能追踪;数据库唯一索引是否生效(用SHOW INDEX FROM miaosha_order确认);秒杀接口是否加了登录拦截和限流;活动结束后是否有补偿任务把 MQ 积压的消息强制消费完毕。这里最容易被忽略的是死信队列——消费者反复重投失败的消息会被无限循环,占满队列,影响后续正常消息的消费。

这套方案的落地经验我大概复用了三次,每次都在避坑那一章里多记几条。秒杀系统没有完美方案,只有“你清楚每个中间件在流量顶峰时怎么死、死之后怎么让用户体面地知道失败了”这一条准则。希望这篇笔记能帮你在自己的项目里少趟几个坑,压测顺利,上线不背锅。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 19:25:24

电磁兼容标准体系全解析:从基础标准到产品认证的工程实践指南

1. 电磁兼容标准体系到底在管什么1.1 从一次辐射骚扰测试超标说起我第一次真正意识到电磁兼容标准体系的重要性&#xff0c;是在一个电源适配器项目上。产品功能一切正常&#xff0c;温升合格&#xff0c;安规也过了&#xff0c;结果送到实验室做辐射骚扰测试&#xff0c;30MHz…

作者头像 李华
网站建设 2026/9/29 19:25:09

AI工程从零开始:数据、训练到部署的完整实践路线图

前阵子好几个朋友都在问我同一个问题&#xff1a;AI工程到底从哪里开始&#xff1f;网上的资料倒是一大堆&#xff0c;但要么是纯算法理论推导&#xff0c;要么是某个工具的使用手册&#xff0c;中间那条“从零到能落地”的路径反而没人系统性地讲清楚。我干脆把自己从只会调参…

作者头像 李华
网站建设 2026/9/29 19:24:39

CLI-Anything:用YAML声明式配置打造统一命令行工具框架

1. 项目概述&#xff1a;CLI-Anything 到底想解决什么问题如果你写过几个真正的命令行工具&#xff0c;大概会有这种感觉&#xff1a;一个脚本从"能用"到"好用"之间&#xff0c;差的不是功能本身&#xff0c;而是围绕功能的一整套"壳"——参数怎…

作者头像 李华
网站建设 2026/9/29 19:24:27

Spring Boot+uniapp校园兼职小程序后端系统实战解析

简介&#xff1a;这是一套基于Java语言和Spring Boot框架&#xff0c;并结合uniapp技术开发的大学生校园兼职微信小程序后端系统源码&#xff0c;适合正在学习微信小程序全栈开发、准备计算机毕业设计或课程设计的读者参考使用。系统完整覆盖后台管理、商家、用户三类角色&…

作者头像 李华
网站建设 2026/9/29 19:23:49

ORB-SLAM3单目+IMU紧耦合实战:从数据加载到位姿优化全流程解析

1. 为什么单目IMU是SLAM落地最值得啃的组合单目相机便宜、功耗低、结构简单&#xff0c;但纯单目SLAM有两个绕不开的硬伤&#xff1a;尺度不确定和快速运动易丢跟踪。前者导致你拿到的轨迹是个“相对形状”&#xff0c;没有真实米制单位&#xff1b;后者在手持快速转身、机器人…

作者头像 李华
网站建设 2026/9/29 19:23:08

superpowers实战:为Codex CLI构建可控的授权与知识体系

以前用Codex CLI干活的时候&#xff0c;我总觉得它像个聪明但手很欠的实习生——脑子确实灵光&#xff0c;但一上来就改文件、跑命令、往pom.xml里塞依赖&#xff0c;拦都拦不住。后来发现社区里有人在整"superpowers"这类增强工具&#xff0c;专门治这个毛病。说白了…

作者头像 李华