Files
blog-press/docs/Web/Others/Seckill.md
2026-06-22 15:51:10 +08:00

3.8 KiB
Raw Blame History

title, date
title date
秒杀系统设计与实战 2026-06-10

一、简介

  一个典型的秒杀系统通常采用 限流 + 缓存 + 异步 的架构:

客户端
   ↓
Nginx / 网关(限流)
   ↓
秒杀接口校验、Redis 预扣库存)
   ↓
RocketMQ下单消息
   ↓
订单服务(创建订单、真正扣库存)
   ↓
MySQL

限流挡人、缓存决策、异步解耦

二、实战设计

2.1 限流

2.1.1 Nginx限流

# 定义规则
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线程池被打爆。

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限流

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里面判断逻辑,然后再执行数据库。

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 异步

rocketMQTemplate.convertAndSend("seckill-order", Map.of("userId", userId, "goodsId", 1));
return ResponseEntity.status(200).body("秒杀成功");

Redis成功后则通过发消息通知消费者操作数据库。
  为了防止重复创建订单,还需要进行一次幂等校验,即根据订单号的唯一性判断是否有重复的。

三、压测

采用JMeter工具进行压测。

3.1 创建测试数据

新建users.csv输入以下内容

1
2
3
...

  然后在线程组->添加->配置元件->CSV Data Set Config设置变量名称为userId。

::: tip 不需要输入表头。
:::

3.2 HTTP请求

  设置请求路径为/seckill/${userId}设置线程组参数例如线程数1000Ramp-Up时间5秒循环次数为1000创建聚合报告执行线程查看结果。

JMeter