黄山市网站建设_网站建设公司_UI设计师_seo优化
2026/1/3 15:05:23 网站建设 项目流程

💥 前言:数据库不是许愿池

很多新手做秒杀,逻辑是这样的:

  1. 用户点击 -> 2. 查询数据库库存 -> 3. 如果库存 > 0 -> 4. 减库存,生成订单。

在并发只有 10 的时候,没问题。
但在并发 10 万的时候,MySQL 会瞬间死锁、CPU 飙升 100%,整个系统宕机。

要实现“亿级并发”(虽然我们只有一台破电脑,但架构要向那个方向设计),必须遵循一个原则:把数据库当大爷供着,尽量别烦它。


🏗️ 一、 架构设计:漏斗模型

秒杀的本质是**“限流”“异步”**。

流量漏斗图 (Mermaid):

异步削峰

1. 拦截重复请求
2. 读缓存 (10W QPS)
3. 发送秒杀成功消息
4. 匀速消费 (1000 QPS)
5. 最终落地

海量用户 (100W QPS)

Nginx / CDN

Redis (Lua 脚本扣减库存)

RabbitMQ (削峰填谷)

后端消费者

MySQL 数据库


🚀 二、 第一道防线:Redis + Lua 实现原子扣减

Redis 是单线程的,速度极快。但如果我们分两步操作(先 Get 库存,再 Decr 扣减),在高并发下依然会有线程安全问题(超卖)。

这时候,Lua 脚本登场了。它能保证多条 Redis 命令像一条命令一样原子执行

1. 编写 Lua 脚本 (seckill.lua)
放在src/main/resources下:

-- 参数 KEYS[1]: 商品库存 Key-- 参数 ARGV[1]: 购买数量 (通常是 1)localstock=tonumber(redis.call('get',KEYS[1]))-- 如果库存不存在或小于购买数量,返回 -1if(stock==nil)or(stock<tonumber(ARGV[1]))thenreturn-1end-- 扣减库存redis.call('decrby',KEYS[1],tonumber(ARGV[1]))return1-- 成功

2. Spring Boot 调用 Lua

@AutowiredprivateStringRedisTemplateredisTemplate;publicbooleanseckill(StringuserId,StringgoodsId){// 1. 预加载 Lua 脚本DefaultRedisScript<Long>redisScript=newDefaultRedisScript<>();redisScript.setScriptSource(newResourceScriptSource(newClassPathResource("seckill.lua")));redisScript.setResultType(Long.class);// 2. 执行脚本 (原子操作)Longresult=redisTemplate.execute(redisScript,Collections.singletonList("seckill:stock:"+goodsId),"1");// 3. 判断结果if(result!=null&&result==1){// 抢单成功!但这只是在 Redis 里成功了returntrue;}returnfalse;}

这一步,我们挡住了 99% 的流量。只有库存数量的人能通过。


🌊 三、 第二道防线:RabbitMQ 削峰填谷

即使通过了 Redis,如果瞬间有 1 万个“成功”请求同时打向 MySQL 去创建订单,数据库一样会挂。
我们需要一个蓄水池—— RabbitMQ。

1. 生产者:发送消息 (Controller 层)

if(redisService.seckill(userId,goodsId)){// Redis 扣减成功后,不直接操作数据库,而是发个消息SeckillMessagemessage=newSeckillMessage(userId,goodsId);rabbitTemplate.convertAndSend("seckill_exchange","seckill_route",message);returnResult.ok("排队中...");// 立即返回给前端,不让用户等}else{returnResult.error("秒杀结束");}

2. 消费者:慢慢处理 (Service 层)

消费者可以根据数据库的能力,设置Qos(预取数量),例如每次只取 50 条处理,慢慢写入 MySQL。

@RabbitListener(queues="seckill_queue")publicvoidreceive(SeckillMessagemessage){// 1. 再次判断数据库库存 (防止 Redis 和 DB 数据不一致)Goodsgoods=goodsMapper.getGoods(message.getGoodsId());if(goods.getStock()<=0){return;}// 2. 创建订单 & 扣减真实库存// 这一步必须加事务orderService.createOrder(message.getUserId(),message.getGoodsId());}

🎨 四、 前端 Vue3 的配合:把压力留在浏览器

后端再强,也怕前端疯狂F5刷新。前端需要做两件事:

  1. 按钮置灰:点击“立即秒杀”后,按钮立刻 Disable,防止用户连点。
  2. 答题/验证码:在点击前强制用户进行图形验证或算术题,拉长用户请求的时间差。
  3. 轮询结果:因为后端返回的是“排队中”,前端需要每隔 2 秒轮询一次接口,查询订单是否创建成功。
// Vue3 伪代码consthandleSeckill=async()=>{btnDisabled.value=true;// 1. 禁用按钮constres=awaitapi.startSeckill(goodsId);if(res.code===200){// 2. 进入轮询状态startPollingResult();}else{message.error("抢光了!");btnDisabled.value=false;}}

🛡️ 五、 总结与进阶

通过这套架构,我们实现了:

  1. Redis Lua:原子性扣减,防止超卖,抗住高并发读。
  2. RabbitMQ:异步下单,削峰填谷,保护脆弱的 MySQL。
  3. Vue3:前端限流,优化用户体验。

如果真的有“亿级”并发,还需要做什么?

  • 服务降级 (Sentinel):流量实在太大,直接熔断非核心业务。
  • 分库分表:订单表数据量太大,需要 Sharding。
  • CDN 静态资源缓存:把 CSS/JS/图片全部推到边缘节点。

Next Step:
别光看,去你的 IDE 里建两个 Spring Boot 项目(一个发消息,一个收消息),装个 Docker 版的 Redis 和 RabbitMQ,亲自跑通这个流程。面试时,这绝对是你的杀手锏

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询