feat:增加秒杀系统设计文档

This commit is contained in:
2026-06-10 18:16:29 +08:00
parent b40d3f14a8
commit b839e361ac
4 changed files with 242 additions and 1 deletions

111
docs/Web/Others/Seckill.md Normal file
View File

@@ -0,0 +1,111 @@
---
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("已售罄");
}
```
&emsp;&emsp;要先减再判断如果小于0则表示已售罄再回滚数据。
## 2.3 异步
```java
rocketMQTemplate.convertAndSend("seckill-order", Map.of("userId", userId, "goodsId", 1));
return ResponseEntity.status(200).body("秒杀成功");
```
&emsp;&emsp;Redis成功后则通过发消息通知消费者操作数据库。
&emsp;&emsp;为了防止重复创建订单,还需要进行一次幂等校验,即根据订单号的唯一性判断是否有重复的。
# 三、压测
&emsp;&emsp;采用ApiPost工具进行压测。