第五部分:流量控制与限流:保护系统不被冲垮
5.1 为什么需要限流?
秒杀瞬间,请求量可能是系统容量的100倍。如果不加控制,系统会被瞬间冲垮。限流的目的是在系统容量范围内处理请求,超出容量的请求直接拒绝。
5.2 限流算法详解
令牌桶算法是最常用的限流算法之一。它维护一个固定容量的桶,以固定速率向桶中添加令牌。请求到来时,需要从桶中获取一个令牌,有令牌才能通过。
优点:
支持突发流量(桶中有累积的令牌)
平滑限流,不会一刀切
@Aspect
@Component
public class RateLimiterAspect {
@Around("@annotation(rateLimiter)")
public Object around(ProceedingJoinPoint point, RateLimiter rateLimiter) {
// 创建令牌桶
RRateLimiter rRateLimiter = redissonClient.getRateLimiter(key);
rRateLimiter.trySetRate(
RateType.OVERALL,
(long) rateLimiter.permitsPerSecond(), // 每秒令牌数
1, RateIntervalUnit.SECONDS
);
// 尝试获取令牌
if (!rRateLimiter.tryAcquire()) {
throw new BusinessException(429, "请求过于频繁");
}
return point.proceed();
}
}
5.3 多维度限流
在实际应用中,我们需要从多个维度进行限流:
@RateLimiter(key = "seckill:global", permitsPerSecond = 1000) // 全局限流:每秒1000
@RateLimiter(key = "seckill:ip", limitType = LimitType.IP, permitsPerSecond = 5) // IP限流:每秒5
@RateLimiter(key = "seckill:user", limitType = LimitType.USER, permitsPerSecond = 1) // 用户限流:每秒1
public Result doSeckill(Long goodsId) {
// 秒杀逻辑
}
为什么需要多维度限流?
全局限流:保护系统整体容量
IP限流:防止单个IP恶意刷接口
用户限流:防止单个用户频繁请求
第六部分:压测与性能优化:用数据说话
6.1 为什么需要压力测试?
优化不能凭感觉,必须用数据说话。压力测试帮助我们:
了解系统的真实处理能力(QPS上限)
发现性能瓶颈(数据库、缓存、代码)
验证优化效果
6.2 压测环境准备
压测工具选择:
JMeter:开源,功能强大,适合复杂场景
wrk:轻量级,适合快速压测
阿里巴巴PTS:云服务,支持分布式压测
压测执行命令:
# 使用wrk进行压测
wrk -t4 -c1000 -d30s --script=seckill.lua http://localhost:8080/api/seckill/doSeckill
# 参数说明:
# -t4:使用4个线程
# -c1000:保持1000个并发连接
# -d30s:持续压测30秒
6.3 压测结果分析
关注的核心指标:
6.4 性能优化实战
优化一:数据库连接池调优
压测发现,QPS到2000时数据库连接池满,请求开始排队。
原因分析: 最大连接数20,每个请求处理时间50ms,理论最大QPS = 20 / 0.05 = 400,远低于目标。
解决方案:
# 增加最大连接数
maximum-pool-size: 50 # 20 → 50
# 同时需要调整MySQL的max_connections
# SET GLOBAL max_connections = 200;
优化二:SQL优化
压测发现,某个查询执行时间超过100ms。
原因分析: 使用EXPLAIN分析发现没有走索引。
-- 优化前:全表扫描
SELECT * FROM seckill_order WHERE user_id = 123;
-- 优化后:使用索引
ALTER TABLE seckill_order ADD INDEX idx_user_id (user_id);
优化三:JVM调优
压测发现,频繁发生Full GC,导致响应时间抖动。
原因分析: 堆内存不足,对象频繁晋升到老年代。
解决方案:
# 增加堆内存,使用G1垃圾回收器
-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=100