3.3 KiB
3.3 KiB
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成功后则通过发消息通知消费者操作数据库。
为了防止重复创建订单,还需要进行一次幂等校验,即根据订单号的唯一性判断是否有重复的。
三、压测
采用ApiPost工具进行压测。