[TOC] #### 1. 前言 --- 晚上 8 点整,秒杀开始 1 万个用户同时点「立即抢购」,你的服务器准备好了吗 ? #### 2. 业务为什么难做 --- 秒杀的难点不在于「卖东西」,而在于「瞬间涌入的流量」,平时一天 1000 个订单,服务器轻轻松松 秒杀那一瞬间,1 秒钟 1 万个请求,这 1 万个请求如果直接打到数据库: + 扣库存 1 万次 + 创建订单 1 万次 + 数据库连接数爆满 + CPU 飙到 100% + 开始报错、超时 + 用户看到 500 错误,疯狂重试 + 请求更多,数据库更扛不住 + 雪崩 #### 3. 不用 RabbitMQ 会怎样 --- 你可能会用 Redis 先扛一层,把库存放到 Redis 里,用原子操作扣减: ```php $stock = Redis::decr('goods:1001:stock'); if ($stock < 0) { return '已售罄'; } // 接下来就要创建订单、写数据库 // ... ``` Redis 能扛住高并发,扣库存没问题,但扣完库存之后呢 ? 还是要创建订单、写数据库 如果 1 万个请求同时写数据库,还是会崩,Redis 解决了「读」的压力,但没解决「写」的压力 #### 4. 用 RabbitMQ 怎么解决 --- 思路是:先接住请求,慢慢处理 ```plaintext 1万个请求 → Redis 判断库存 → 有库存的请求进入 RabbitMQ ↓ 消费者按节奏慢慢处理 ↓ 写入数据库 ``` 数据库永远只看到稳定的流量,不会被瞬间峰值打崩 #### 5. 代码实现 --- 第一步:Redis 快速判断 用户看到的是「排队中」,不是转圈圈,也就是还有库存,已经进入队列等待消费 这里只是演示削峰思路,实际秒杀系统通常还会增加一人一单限制、防重复提交、风控校验、热点缓存等环节 ```php public function seckill($userId, $goodsId) { // Redis 原子扣减库存 $stock = Redis::decr("goods:{$goodsId}:stock"); if ($stock < 0) { // 库存不足,直接返回 Redis::incr("goods:{$goodsId}:stock"); // 恢复 return ['message' => '已售罄']; } // 有库存,把请求丢进队列 $message = new AMQPMessage(json_encode([ 'user_id' => $userId, 'goods_id' => $goodsId, 'timestamp' => time(), ])); $channel->basic_publish($message, '', 'seckill_queue'); return ['message' => '排队中,请稍候']; } ``` 第二步:消费者慢慢处理 消费者可以控制并发数,比如只启动 10 个消费者,每秒处理 100 个订单,数据库稳稳的 ```php $callback = function ($msg) { $data = json_decode($msg->body, true); // 创建订单、写数据库 $order = createOrder($data['user_id'], $data['goods_id']); // 通知用户(通过 WebSocket 或轮询) notifyUser($data['user_id'], $order->id); $msg->ack(); }; $channel->basic_consume('seckill_queue', '', false, false, false, false, $callback); ``` 第三步:前端轮询结果 用户提交秒杀请求后,前端轮询查询结果: ```php // 用户查询秒杀结果 public function seckillResult($userId, $goodsId) { $order = DB::table('orders') ->where('user_id', $userId) ->where('goods_id', $goodsId) ->where('created_at', '>', now()->subMinutes(5)) ->first(); if ($order) { return ['message' => '抢购成功', 'order_id' => $order->id]; } return ['message' => '排队中']; } ``` #### 6. 整个流程 --- 用户体验:点击 → 排队中 → 抢购成功/已售罄,整个过程数据库压力很小,不会崩。 ```plaintext 用户点击抢购 ↓ Redis 判断库存(毫秒级响应) ↓ 有库存 → 请求进入 RabbitMQ → 用户看到「排队中」 没库存 → 直接返回「已售罄」 ↓ 消费者按节奏处理(创建订单、写数据库) ↓ 用户轮询查看结果 ``` #### 7. 本文小结 --- 秒杀的核心思路:把瞬间流量变成平稳流量 | 层 | 作用 | | ------------ | ------------ | | Redis | 快速判断库存,过滤无效请求 | | RabbitMQ | 接住有效请求,削峰填谷 | | 数据库 | 慢慢处理,不会被打崩 | RabbitMQ 在秒杀系统中的角色是缓冲层 RabbitMQ 解决的是流量削峰问题,库存准确性主要依赖 Redis 和业务逻辑控制 它不负责判断库存(那是 Redis 的事),它只负责把请求排好队,让消费者按节奏处理 下一讲,我们来做日志收集系统