--- title: 秒杀系统设计与实战 date: 2026-06-10 --- # 一、简介   一个典型的秒杀系统通常采用 **限流 + 缓存 + 异步** 的架构: ```plain 客户端 ↓ Nginx / 网关(限流) ↓ 秒杀接口(校验、Redis 预扣库存) ↓ RocketMQ(下单消息) ↓ 订单服务(创建订单、真正扣库存) ↓ MySQL ```   **限流挡人、缓存决策、异步解耦**。 # 二、实战设计 ## 2.1 限流 ### 2.1.1 Nginx限流 ```conf # 定义规则 limit_req_zone $binary_remote_addr zone=seckill:10m rate=5r/s; server { listen 80 default_server; listen [::]:80 default_server; location /seckill-api/ { # 启用限流 limit_req zone=seckill burst=10 nodelay; # 返回 429 limit_req_status 429; proxy_pass http://127.0.0.1:8080/; } } ```   `limit_req_zone`用来制定规则,`limit_req`表示启用规则,如果某个`location`没写,则不会启动规则。   `zone=seckill:10m`表示开辟一块`10MB`的内存,取名叫`seckill`,专门存`IP`访问记录,`rate=5r/s`表示每个`IP`每秒最多`5`个请求。   `limit_req zone=seckill`表示启动`seckill`规则,`burst=10`表示突发请求缓冲,`nodelay`表示超出后直接失败。 ::: tip `limit_req_zone`一定要写在`http`模块中,如果是`Linux`部署的,直接写在最外面。 ::: ::: warning Nginx限流只能防止单ip高刷,无法挡住大量不同IP的分布式攻击。 ::: ### 2.1.2 Semaphore限流   `Semaphore`是针对应用层限流,保护`JVM`,防止`Tomcat`线程池被打爆。 ```java private final Semaphore semaphore = new Semaphore(100); if (!semaphore.tryAcquire()) { return ResponseEntity.status(429).body("系统繁忙"); } try { } finally { semaphore.release(); } ```   `new Semaphore(100)`表示最多`100`个线程同时进来,超过则直接返回失败,不会进入排队,也不会卡住线程。 ::: tip Semaphore不是全局限流,不具备分布式能力。 ::: ### 2.1.3 Redis限流 ```java String userKey = "seckill:user:" + userId; Boolean first = redis.opsForValue().setIfAbsent(userKey, "1", 60, TimeUnit.SECONDS); if (Boolean.FALSE.equals(first)) { return ResponseEntity.status(429).body("请勿重复尝试"); } ```   如果是限制每个用户只能限购一次,则可以将过期时间设置为永久。 ## 2.2 缓存   先在`Redis`里面判断逻辑,然后再执行数据库。 ```java String stockKey = "seckill:stock:1"; Long remain = redis.opsForValue().decrement(stockKey); if (remain == null || remain < 0) { redis.opsForValue().increment(stockKey); return ResponseEntity.status(200).body("已售罄"); } ```   要先减再判断,如果小于0,则表示已售罄,再回滚数据。 ## 2.3 异步 ```java rocketMQTemplate.convertAndSend("seckill-order", Map.of("userId", userId, "goodsId", 1)); return ResponseEntity.status(200).body("秒杀成功"); ```   Redis成功后则通过发消息通知消费者操作数据库。   为了防止重复创建订单,还需要进行一次幂等校验,即根据订单号的唯一性判断是否有重复的。 # 三、压测   采用JMeter工具进行压测。 ## 3.1 创建测试数据   新建users.csv,输入以下内容: ```csv 1 2 3 ... ```   然后在`线程组->添加->配置元件->CSV Data Set Config`,设置变量名称为userId。 ::: tip 不需要输入表头。 ::: ## 3.2 HTTP请求   设置请求路径为`/seckill/${userId}`,设置线程组参数,例如线程数1000,Ramp-Up时间5秒,循环次数为1000,创建聚合报告,执行线程,查看结果。 ![JMeter](../images/jmeter.png)