高并发系统设计实战
高并发系统是互联网架构的核心挑战。秒杀、抢购、热点事件等场景需要应对瞬时大流量冲击。本文详解高并发系统设计的核心技术:限流、降级、缓存、队列、分库分表等,并提供完整的秒杀系统实战方案。
一、高并发挑战
1.1 典型场景
---
config:
theme: forest
---
mindmap
root((高并发场景))
秒杀/抢购
特点:瞬时流量大、库存有限
峰值:10 万 + QPS
难点:超卖、系统崩溃
热点事件
特点:突发流量、不可预测
峰值:百万 + QPS
难点:缓存穿透、数据库压力
大促活动
特点:持续高并发、业务复杂
峰值:数十万 QPS
难点:全链路稳定性
直播互动
特点:高并发读写、实时性要求高
峰值:百万 + 并发连接
难点:消息推送、状态同步
1.2 性能指标
| 指标 | 说明 | 目标值 |
|---|---|---|
| QPS | 每秒查询数 | 10 万 + |
| TPS | 每秒事务数 | 1 万 + |
| 响应时间 | P99 延迟 | < 100ms |
| 可用性 | 系统可用率 | 99.99% |
| 并发用户 | 同时在线用户 | 百万 + |
1.3 设计原则
高并发设计原则
├── 冗余原则
│ └── 机器冗余、数据冗余、机房冗余
│
├── 拆分原则
│ ├── 业务拆分:微服务化
│ ├── 数据拆分:分库分表
│ └── 读写拆分:主从复制
│
├── 缓存原则
│ ├── 多级缓存:CDN → 反向代理 → 应用 → 数据库
│ └── 热点数据:预加载、永不过期
│
├── 异步原则
│ ├── 消息队列:削峰填谷
│ └── 异步处理:提升响应速度
│
└── 限流原则
├── 入口限流:网关层
├── 服务限流:Sentinel、Hystrix
└── 降级熔断:保护核心业务
二、限流技术
2.1 限流算法
计数器
/**
* 固定窗口计数器限流
*/
public class CounterRateLimiter {
private final int limit; // 限流阈值
private final long windowMs; // 窗口大小(毫秒)
private final AtomicInteger count = new AtomicInteger(0);
private volatile long windowStart = System.currentTimeMillis();
public CounterRateLimiter(int limit, long windowMs) {
this.limit = limit;
this.windowMs = windowMs;
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 窗口过期,重置计数器
if (now - windowStart >= windowMs) {
count.set(0);
windowStart = now;
}
// 未超限,允许通过
if (count.get() < limit) {
count.incrementAndGet();
return true;
}
return false;
}
}
// 使用示例
RateLimiter limiter = new CounterRateLimiter(100, 1000); // 1 秒 100 次
if (limiter.tryAcquire()) {
// 处理请求
} else {
// 拒绝请求
}
问题:临界问题(窗口切换时流量翻倍)
滑动窗口
/**
* 滑动窗口限流
*/
public class SlidingWindowRateLimiter {
private final int limit; // 限流阈值
private final int windowSize; // 窗口数量
private final long windowMs; // 窗口总时长
private final AtomicInteger[] windows;
private volatile int currentWindow = 0;
private volatile long windowStart = System.currentTimeMillis();
public SlidingWindowRateLimiter(int limit, int windowSize, long windowMs) {
this.limit = limit;
this.windowSize = windowSize;
this.windowMs = windowMs;
this.windows = new AtomicInteger[windowSize];
for (int i = 0; i < windowSize; i++) {
windows[i] = new AtomicInteger(0);
}
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
long elapsed = now - windowStart;
long slotMs = windowMs / windowSize;
// 移动窗口
int slotsToMove = (int) (elapsed / slotMs);
if (slotsToMove > 0) {
for (int i = 0; i < slotsToMove && i < windowSize; i++) {
int idx = (currentWindow + i) % windowSize;
windows[idx].set(0);
}
currentWindow = (currentWindow + slotsToMove) % windowSize;
windowStart = now;
}
// 统计当前窗口内的请求数
int total = 0;
for (AtomicInteger window : windows) {
total += window.get();
}
if (total < limit) {
windows[currentWindow].incrementAndGet();
return true;
}
return false;
}
}
令牌桶
/**
* 令牌桶限流
*/
public class TokenBucketRateLimiter {
private final int capacity; // 桶容量
private final int refillRate; // 补充速率(个/秒)
private final AtomicInteger tokens;
private volatile long lastRefill = System.currentTimeMillis();
public TokenBucketRateLimiter(int capacity, int refillRate) {
this.capacity = capacity;
this.refillRate = refillRate;
this.tokens = new AtomicInteger(capacity);
}
public boolean tryAcquire() {
refill();
int currentTokens = tokens.get();
if (currentTokens > 0 && tokens.compareAndSet(currentTokens, currentTokens - 1)) {
return true;
}
return false;
}
private void refill() {
long now = System.currentTimeMillis();
long elapsed = now - lastRefill;
int tokensToAdd = (int) (elapsed * refillRate / 1000);
if (tokensToAdd > 0) {
tokens.updateAndGet(current -> Math.min(capacity, current + tokensToAdd));
lastRefill = now;
}
}
}
漏桶
/**
* 漏桶限流
*/
public class LeakyBucketRateLimiter {
private final int capacity; // 桶容量
private final int leakRate; // 漏出速率(个/秒)
private final Queue<Long> queue;
public LeakyBucketRateLimiter(int capacity, int leakRate) {
this.capacity = capacity;
this.leakRate = leakRate;
this.queue = new LinkedList<>();
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 移除已过期的请求
while (!queue.isEmpty() && now - queue.peek() > 1000L * capacity / leakRate) {
queue.poll();
}
if (queue.size() < capacity) {
queue.offer(now);
return true;
}
return false;
}
}
2.2 Redis 分布式限流
/**
* Redis 分布式限流(Lua 脚本保证原子性)
*/
@Component
public class RedisRateLimiter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String SCRIPT =
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local window = tonumber(ARGV[2])\n" +
"local now = tonumber(ARGV[3])\n" +
"\n" +
"redis.call('ZREMRANGEBYSCORE', key, 0, now - window)\n" +
"local count = redis.call('ZCARD', key)\n" +
"\n" +
"if count < limit then\n" +
" redis.call('ZADD', key, now, now)\n" +
" redis.call('EXPIRE', key, window)\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
private final DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(SCRIPT, Long.class);
/**
* 尝试获取限流许可
* @param key 限流 key(如:user:123, api:/order)
* @param limit 限流阈值
* @param windowMs 窗口大小(毫秒)
* @return true: 允许通过,false: 拒绝
*/
public boolean tryAcquire(String key, int limit, long windowMs) {
long now = System.currentTimeMillis();
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(key),
String.valueOf(limit),
String.valueOf(windowMs),
String.valueOf(now)
);
return result != null && result == 1;
}
}
// 使用示例
@RestController
@RequestMapping("/api")
public class OrderController {
@Autowired
private RedisRateLimiter rateLimiter;
@PostMapping("/order")
public Result createOrder(@RequestBody OrderRequest request) {
String userId = UserContext.getUserId();
String limitKey = "order:limit:" + userId;
// 限流:每用户每秒 10 次
if (!rateLimiter.tryAcquire(limitKey, 10, 1000)) {
return Result.error("请求过于频繁,请稍后再试");
}
// 处理订单
orderService.create(request);
return Result.success();
}
}
2.3 Sentinel 限流
/**
* Sentinel 限流配置
*/
@Configuration
public class SentinelConfig {
@PostConstruct
public void initRules() {
// QPS 限流
FlowRule qpsRule = new FlowRule();
qpsRule.setResource("createOrder");
qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
qpsRule.setCount(100); // 每秒 100 次
qpsRule.setLimitApp("default");
// 并发线程数限流
FlowRule threadRule = new FlowRule();
threadRule.setResource("createOrder");
threadRule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
threadRule.setCount(50); // 最多 50 个并发线程
threadRule.setLimitApp("default");
FlowRuleManager.loadRules(Arrays.asList(qpsRule, threadRule));
}
}
// 使用示例
@Service
public class OrderServiceImpl implements OrderService {
@SentinelResource(
value = "createOrder",
blockHandler = "handleBlock", // 限流处理
fallback = "handleFallback", // 降级处理
exceptionsToIgnore = {IllegalArgumentException.class}
)
@Override
public Order create(OrderRequest request) {
// 创建订单逻辑
return orderMapper.insert(request);
}
// 限流处理方法
public Order handleBlock(OrderRequest request, BlockException ex) {
log.warn("限流:{}", request.getUserId());
throw new BusinessException("请求过于频繁");
}
// 降级处理方法
public Order handleFallback(OrderRequest request, Throwable ex) {
log.error("服务降级:{}", request.getUserId(), ex);
// 返回默认值或缓存数据
return null;
}
}
三、缓存策略
3.1 多级缓存架构
多级缓存架构
用户请求
↓
┌─────────────────────────────────────┐
│ CDN 缓存(静态资源) │
│ - HTML/CSS/JS │
│ - 图片/视频 │
│ TTL: 1 天 -1 年 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ Nginx 缓存(反向代理) │
│ - API 响应缓存 │
│ - 页面缓存 │
│ TTL: 1 分钟 -1 小时 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 本地缓存(Caffeine/Guava) │
│ - 热点数据 │
│ - 配置数据 │
│ TTL: 1 秒 -10 分钟 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ Redis 缓存(分布式缓存) │
│ - 业务数据 │
│ - Session 数据 │
│ TTL: 5 分钟 -24 小时 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 数据库(MySQL) │
│ - 持久化数据 │
│ - 复杂查询 │
└─────────────────────────────────────┘
3.2 缓存更新策略
/**
* 缓存策略模式
*/
@Service
public class CacheStrategy {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 策略 1:Cache Aside(旁路缓存)
* 读:先读缓存,没有则读 DB 并写入缓存
* 写:先写 DB,再删除缓存
*/
public User getUserById(Long id) {
String key = "user:" + id;
// 读缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 读数据库
user = userMapper.selectById(id);
if (user != null) {
// 写入缓存(设置过期时间,防止缓存不一致)
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
}
return user;
}
public void updateUser(User user) {
// 先写数据库
userMapper.update(user);
// 再删除缓存(下次读取时更新)
String key = "user:" + user.getId();
redisTemplate.delete(key);
}
/**
* 策略 2:Read/Write Through(读写穿透)
* 缓存代理数据库,应用只操作缓存
*/
/**
* 策略 3:Write Behind(异步回写)
* 写缓存后异步写入数据库
*/
}
3.3 缓存穿透/击穿/雪崩
/**
* 缓存问题解决方案
*/
@Service
public class CacheProblemSolver {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 问题 1:缓存穿透
* 现象:查询不存在的数据,缓存不命中,请求直达数据库
* 解决:布隆过滤器、缓存空值
*/
public User getUserWithBloomFilter(Long id) {
// 布隆过滤器检查
if (!bloomFilter.mightContain(id)) {
return null; // 肯定不存在
}
String key = "user:" + id;
User user = (User) redisTemplate.opsForValue().get(key);
if (user == null) {
// 数据库查询
user = userMapper.selectById(id);
if (user == null) {
// 缓存空值(防止穿透)
redisTemplate.opsForValue().set(key, null, 5, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
bloomFilter.put(id);
}
}
return user;
}
/**
* 问题 2:缓存击穿
* 现象:热点 key 过期,大量请求直达数据库
* 解决:互斥锁、永不过期
*/
public User getUserWithMutex(Long id) {
String key = "user:" + id;
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 互斥锁
String lockKey = "lock:user:" + id;
if (tryLock(lockKey)) {
try {
// 双重检查
user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 查询数据库
user = userMapper.selectById(id);
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
return user;
} finally {
unlock(lockKey);
}
} else {
// 等待重试
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return getUserWithMutex(id);
}
}
/**
* 问题 3:缓存雪崩
* 现象:大量 key 同时过期,请求直达数据库
* 解决:随机过期时间、高可用
*/
public void cacheUser(User user) {
String key = "user:" + user.getId();
// 基础过期时间 + 随机值
long expireTime = 30 + new Random().nextInt(10); // 30-40 分钟
redisTemplate.opsForValue().set(key, user, expireTime, TimeUnit.MINUTES);
}
private boolean tryLock(String key) {
return Boolean.TRUE.equals(redisTemplate.opsForValue()
.setIfAbsent(key, "locked", 10, TimeUnit.SECONDS));
}
private void unlock(String key) {
redisTemplate.delete(key);
}
@Autowired
private BloomFilter<Long> bloomFilter;
}
3.4 热点数据发现
/**
* 热点数据发现与缓存
*/
@Component
public class HotKeyDetector {
private final ConcurrentHashMap<String, LongAdder> keyCounter = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@PostConstruct
public void init() {
// 每分钟统计一次
scheduler.scheduleAtFixedRate(this::detectHotKeys, 1, 1, TimeUnit.MINUTES);
}
/**
* 记录 key 访问
*/
public void recordAccess(String key) {
LongAdder counter = keyCounter.computeIfAbsent(key, k -> new LongAdder());
counter.increment();
}
/**
* 发现热点 key
*/
private void detectHotKeys() {
List<String> hotKeys = keyCounter.entrySet().stream()
.filter(entry -> entry.getValue().sum() > 1000) // 阈值:1000 次/分钟
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 将热点 key 推送到本地缓存
for (String key : hotKeys) {
preloadToCaffeine(key);
}
// 重置计数器
keyCounter.clear();
}
private void preloadToCaffeine(String key) {
// 从 Redis 加载数据到本地缓存
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
}
}
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private Cache<String, Object> localCache;
}
四、消息队列削峰
4.1 队列选型
| 队列 | 吞吐量 | 延迟 | 可靠性 | 适用场景 |
|---|---|---|---|---|
| Kafka | 百万 + | ms 级 | 高 | 日志收集、大数据 |
| RocketMQ | 十万 + | ms 级 | 很高 | 交易、订单 |
| RabbitMQ | 万级 | μs 级 | 高 | 低延迟、复杂路由 |
| Pulsar | 百万 + | ms 级 | 高 | 云原生、多租户 |
4.2 秒杀队列方案
/**
* 秒杀队列方案
*/
@Service
public class SeckillQueueService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 秒杀入口
*/
@PostMapping("/seckill/{itemId}")
public Result seckill(@PathVariable Long itemId) {
Long userId = UserContext.getUserId();
// 1. 库存预扣减(Redis)
String stockKey = "seckill:stock:" + itemId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
// 库存不足
redisTemplate.opsForValue().increment(stockKey); // 回滚
return Result.error("库存不足");
}
// 2. 发送消息到队列(异步下单)
SeckillMessage message = new SeckillMessage();
message.setItemId(itemId);
message.setUserId(userId);
message.setStock(stock);
rocketMQTemplate.send("seckill_topic", message);
// 3. 返回排队中
return Result.success("排队中,请稍后查看结果");
}
/**
* 消费消息(下单)
*/
@RocketMQMessageListener(
topic = "seckill_topic",
consumerGroup = "seckill_consumer_group"
)
public class SeckillConsumer implements RocketMQListener<SeckillMessage> {
@Override
@Transactional(rollbackFor = Exception.class)
public void onMessage(SeckillMessage message) {
try {
// 1. 检查重复购买
String buyKey = "seckill:buy:" + message.getItemId() + ":" + message.getUserId();
if (Boolean.TRUE.equals(redisTemplate.hasKey(buyKey))) {
// 重复购买,回滚库存
redisTemplate.opsForValue().increment("seckill:stock:" + message.getItemId());
return;
}
// 2. 创建订单
Order order = new Order();
order.setItemId(message.getItemId());
order.setUserId(message.getUserId());
order.setStatus(OrderStatus.PENDING);
orderMapper.insert(order);
// 3. 标记已购买
redisTemplate.opsForValue().set(buyKey, "1", 24, TimeUnit.HOURS);
log.info("秒杀成功:userId={}, itemId={}", message.getUserId(), message.getItemId());
} catch (Exception e) {
log.error("秒杀失败", e);
// 回滚库存
redisTemplate.opsForValue().increment("seckill:stock:" + message.getItemId());
throw e; // 重试
}
}
}
}
4.3 队列积压处理
/**
* 队列积压监控与处理
*/
@Component
public class QueueBacklogHandler {
@Scheduled(fixedRate = 60000) // 每分钟检查
public void checkBacklog() {
// 检查队列积压量
long backlog = rocketMQTemplate.consumer().backlog("seckill_consumer_group");
if (backlog > 10000) {
log.warn("队列积压:{}", backlog);
// 告警通知
alertService.send("队列积压超过阈值:" + backlog);
// 自动扩容消费者
if (backlog > 50000) {
scaleUpConsumers();
}
}
}
private void scaleUpConsumers() {
// 增加消费者实例(K8s HPA 或手动扩容)
log.info("扩容消费者");
}
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private AlertService alertService;
}
五、数据库优化
5.1 读写分离
# ShardingSphere 配置
spring:
shardingsphere:
datasource:
names: ds-master,ds-slave0,ds-slave1
ds-master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://master:3306/order_db
username: root
password: root123
ds-slave0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://slave0:3306/order_db
username: root
password: root123
ds-slave1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://slave1:3306/order_db
username: root
password: root123
rules:
readwrite-splitting:
data-groups:
pr_ds:
write-data-source-name: ds-master
read-data-source-names: ds-slave0,ds-slave1
load-balancer-name: random
load-balancers:
random:
type: RANDOM
5.2 分库分表
# 分库分表配置
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order$->{0..3}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_table_algor
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: t_order_db_algor
sharding-algorithms:
t_order_table_algor:
type: CLASS_BASED
props:
strategy: COMPLEX
algorithm-class: com.example.OrderTableShardingAlgorithm
t_order_db_algor:
type: CLASS_BASED
props:
strategy: COMPLEX
algorithm-class: com.example.OrderDbShardingAlgorithm
/**
* 分表算法
*/
public class OrderTableShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
ComplexKeysShardingValue<Long> shardingValue) {
// 根据 order_id 分表
Long orderId = shardingValue.getColumnNameAndValueMap().get("order_id");
int tableIndex = (int) (orderId % 4);
return availableTargetNames.stream()
.filter(name -> name.endsWith(String.valueOf(tableIndex)))
.collect(Collectors.toList());
}
}
5.3 SQL 优化
-- 优化前(全表扫描)
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';
-- 优化后(索引扫描)
SELECT * FROM orders
WHERE create_time >= '2024-01-01 00:00:00'
AND create_time < '2024-01-02 00:00:00';
-- 添加索引
CREATE INDEX idx_create_time ON orders(create_time);
-- 覆盖索引(避免回表)
SELECT order_id, user_id, amount
FROM orders
WHERE create_time >= '2024-01-01';
-- 避免 SELECT *
-- 只查询需要的字段
六、秒杀系统实战
6.1 系统架构
---
config:
theme: neutral
look: classic
---
graph TB
subgraph 用户层
A[用户请求] --> B[CDN 静态资源]
A --> C[页面静态化]
end
subgraph 接入层
B --> D[Nginx 限流]
C --> D
D --> E[WAF 防护]
E --> F[负载均衡]
end
subgraph 服务层
F --> G[网关限流 Sentinel]
G --> H[秒杀服务]
G --> I[订单服务]
H -.异步.-> I
end
subgraph 缓存层
H --> J[Redis 集群]
H --> K[本地缓存 Caffeine]
J --> L[库存缓存]
J --> M[用户资格缓存]
K --> N[热点数据]
end
subgraph 消息层
I --> O[RocketMQ]
O --> P[削峰填谷]
O --> Q[异步下单]
end
subgraph 数据层
O --> R[MySQL 主从]
R --> S[订单库分库分表]
R --> T[商品库]
J --> U[Redis 持久化]
end
6.2 核心代码
/**
* 秒杀服务
*/
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 秒杀活动预热
*/
@PostMapping("/preheat/{itemId}")
public void preheat(@PathVariable Long itemId) {
// 1. 加载库存到 Redis
SeckillItem item = seckillItemMapper.selectById(itemId);
String stockKey = "seckill:stock:" + itemId;
redisTemplate.opsForValue().set(stockKey, item.getStock());
// 2. 预热活动信息
String itemKey = "seckill:item:" + itemId;
redisTemplate.opsForValue().set(itemKey, item, 1, TimeUnit.HOURS);
// 3. 加载秒杀白名单(如有)
loadWhitelist(itemId);
}
/**
* 秒杀执行
*/
@PostMapping("/execute/{itemId}")
public Result execute(@PathVariable Long itemId) {
Long userId = UserContext.getUserId();
// 1. 用户资格检查
if (!checkUserQualification(userId, itemId)) {
return Result.error("无购买资格");
}
// 2. 重复购买检查
String buyKey = "seckill:buy:" + itemId + ":" + userId;
if (Boolean.TRUE.equals(redisTemplate.hasKey(buyKey))) {
return Result.error("已购买过");
}
// 3. 库存预扣减
String stockKey = "seckill:stock:" + itemId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey);
return Result.error("库存不足");
}
// 4. 发送秒杀消息(异步下单)
SeckillMessage message = new SeckillMessage();
message.setItemId(itemId);
message.setUserId(userId);
message.setStock(stock);
rocketMQTemplate.send("seckill_order_topic", message);
// 5. 返回排队中
return Result.success("排队中");
}
/**
* 查询秒杀结果
*/
@GetMapping("/result/{itemId}")
public Result getResult(@PathVariable Long itemId) {
Long userId = UserContext.getUserId();
// 检查是否购买成功
String buyKey = "seckill:buy:" + itemId + ":" + userId;
if (Boolean.TRUE.equals(redisTemplate.hasKey(buyKey))) {
// 查询订单
Order order = orderMapper.selectByUserAndItem(userId, itemId);
return Result.success(order);
}
return Result.success("未中奖");
}
private boolean checkUserQualification(Long userId, Long itemId) {
// 黑名单检查
String blacklistKey = "seckill:blacklist:" + userId;
if (Boolean.TRUE.equals(redisTemplate.hasKey(blacklistKey))) {
return false;
}
// 风控检查(简化)
return true;
}
private void loadWhitelist(Long itemId) {
// 加载白名单用户
List<Long> whitelist = whitelistMapper.selectByItemId(itemId);
String whitelistKey = "seckill:whitelist:" + itemId;
redisTemplate.opsForSet().add(whitelistKey, whitelist.toArray());
}
}
七、总结
7.1 核心要点
- 限流:入口限流、服务限流、降级熔断
- 缓存:多级缓存、更新策略、问题预防
- 队列:削峰填谷、异步处理
- 数据库:读写分离、分库分表、SQL 优化
- 监控:全链路监控、告警、自动扩容
7.2 架构演进
架构演进路线
单机 → 集群 → 微服务 → 云原生
↓ ↓ ↓ ↓
限流 负载均衡 服务治理 弹性伸缩
缓存 读写分离 消息队列 Serverless
高并发系统设计是系统工程,需要从架构、代码、运维多维度持续优化。