Skip to main content

限流系统设计

限流系统设计是保护系统免受流量冲击、确保系统稳定运行的重要技术。通过合理的限流算法和策略,可以有效控制请求流量,防止系统过载。

核心价值

限流 = 流量控制 + 系统保护 + 资源管理 + 用户体验 + 成本控制

  • 🚦 流量控制:调节请求流量,避免系统过载
  • 🛡️ 系统保护:防止系统崩溃,保证系统稳定性
  • 📊 资源管理:合理分配系统资源,提高资源利用率
  • 👤 用户体验:保证核心功能可用,提升用户满意度
  • 💰 成本控制:降低资源成本,提高系统经济性

1. 限流基础概念

1.1 限流目的

限流系统的主要目的:

目的说明实现方式
保护系统防止系统过载崩溃限制请求数量
资源管理合理分配系统资源按优先级分配
成本控制控制API调用成本限制调用频率
用户体验保证核心功能可用降级非核心功能
安全防护防止恶意攻击异常流量检测
java
1@Configuration
2public class RateLimitConfig {
3
4 @Value("${rate.limit.enabled:true}")
5 private boolean rateLimitEnabled;
6
7 @Value("${rate.limit.default.qps:100}")
8 private int defaultQps;
9
10 @Value("${rate.limit.default.burst:200}")
11 private int defaultBurst;
12
13 @Bean
14 public RateLimiter rateLimiter() {
15 return RateLimiter.create(defaultQps);
16 }
17
18 @Bean
19 public RateLimitProperties rateLimitProperties() {
20 RateLimitProperties properties = new RateLimitProperties();
21 properties.setEnabled(rateLimitEnabled);
22 properties.setDefaultQps(defaultQps);
23 properties.setDefaultBurst(defaultBurst);
24 return properties;
25 }
26}

1.2 限流维度

限流可以从不同维度进行:

  1. 全局限流:对整个系统进行总体流量控制
  2. 用户限流:对单个用户的请求频率进行限制
  3. IP限流:对来自特定IP地址的请求进行限制
  4. API限流:对特定API接口的调用频率进行限制
  5. 接口限流:对特定接口方法的调用频率进行限制
  6. 资源限流:对特定资源的访问频率进行限制
java
1public enum RateLimitDimension {
2 // 全局限流
3 GLOBAL("全局限流", "对整个系统进行限流"),
4
5 // 用户限流
6 USER("用户限流", "对单个用户进行限流"),
7
8 // IP限流
9 IP("IP限流", "对单个IP地址进行限流"),
10
11 // API限流
12 API("API限流", "对特定API接口进行限流"),
13
14 // 接口限流
15 INTERFACE("接口限流", "对特定接口方法进行限流"),
16
17 // 资源限流
18 RESOURCE("资源限流", "对特定资源进行限流");
19
20 private final String name;
21 private final String description;
22
23 RateLimitDimension(String name, String description) {
24 this.name = name;
25 this.description = description;
26 }
27}

2. 限流算法

info

限流算法是限流系统的核心,不同算法有不同的特点和适用场景。常见的限流算法包括固定窗口、滑动窗口、令牌桶和漏桶算法。

限流算法对比

算法优点缺点适用场景
固定窗口简单易实现、内存消耗少边界突发流量问题简单场景、低精度要求
滑动窗口平滑限流效果、避免边界问题实现复杂、内存消耗大需要精确限流的场景
令牌桶允许突发流量、灵活配置实现较复杂允许突发流量的场景
漏桶固定速率处理、流量整形无法应对突发流量需要固定处理速率的场景

2.1 固定窗口算法

固定窗口算法将时间分为固定长度的窗口,在每个窗口内限制请求数量。

java
1@Component
2public class FixedWindowRateLimiter {
3
4 private final Map<String, WindowInfo> windows = new ConcurrentHashMap<>();
5 private final int limit;
6 private final long windowSize;
7
8 public FixedWindowRateLimiter(int limit, long windowSize) {
9 this.limit = limit;
10 this.windowSize = windowSize;
11 }
12
13 public boolean isAllowed(String key) {
14 long currentTime = System.currentTimeMillis();
15 long windowStart = currentTime - (currentTime % windowSize);
16
17 WindowInfo window = windows.compute(key, (k, v) -> {
18 if (v == null || v.getWindowStart() != windowStart) {
19 return new WindowInfo(windowStart, 1);
20 } else {
21 v.incrementCount();
22 return v;
23 }
24 });
25
26 return window.getCount() <= limit;
27 }
28
29 public void reset(String key) {
30 windows.remove(key);
31 }
32}
固定窗口算法的局限性

固定窗口算法在窗口边界可能导致突发流量问题。例如,在一个窗口的末尾和下一个窗口的开头,可能在短时间内通过两倍于阈值的请求。

2.2 滑动窗口算法

滑动窗口算法通过将时间窗口分为多个小块,并随着时间推移滑动,提供更平滑的限流效果。

滑动窗口工作原理
  1. 窗口分割:将时间窗口分为多个小桶
  2. 请求计数:每个请求落入对应的时间桶
  3. 窗口滑动:随着时间推移,旧桶淘汰,新桶加入
  4. 流量控制:根据所有桶的请求总数决定是否允许请求

优点:

  • 平滑处理请求,避免边界突发问题
  • 提供更精确的流量控制
  • 适应性强,可根据需求调整桶的数量和大小
java
1@Component
2public class SlidingWindowRateLimiter {
3
4 private final Map<String, SlidingWindow> windows = new ConcurrentHashMap<>();
5 private final int limit;
6 private final long windowSize;
7 private final int bucketCount;
8
9 public SlidingWindowRateLimiter(int limit, long windowSize, int bucketCount) {
10 this.limit = limit;
11 this.windowSize = windowSize;
12 this.bucketCount = bucketCount;
13 }
14
15 public boolean isAllowed(String key) {
16 long currentTime = System.currentTimeMillis();
17 SlidingWindow window = windows.computeIfAbsent(key, k ->
18 new SlidingWindow(windowSize, bucketCount));
19
20 return window.isAllowed(currentTime, limit);
21 }
22
23 public void reset(String key) {
24 windows.remove(key);
25 }
26}

2.3 令牌桶算法

令牌桶算法通过以固定速率向桶中添加令牌,请求到达时需要获取令牌才能被处理。

java
1@Component
2public class TokenBucketRateLimiter {
3
4 private final Map<String, TokenBucket> buckets = new ConcurrentHashMap<>();
5 private final double rate;
6 private final int capacity;
7
8 public TokenBucketRateLimiter(double rate, int capacity) {
9 this.rate = rate;
10 this.capacity = capacity;
11 }
12
13 public boolean isAllowed(String key) {
14 TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(rate, capacity));
15 return bucket.tryConsume(1);
16 }
17
18 public boolean isAllowed(String key, int tokens) {
19 TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(rate, capacity));
20 return bucket.tryConsume(tokens);
21 }
22
23 public void reset(String key) {
24 buckets.remove(key);
25 }
26}
令牌桶特点

令牌桶算法的关键特点是能够处理突发流量。通过预先存储的令牌,可以允许短时间内的请求突发,同时通过固定速率的令牌产生来限制长期的请求速率。

2.4 漏桶算法

漏桶算法以固定速率处理请求,多余的请求存储在桶中,如果桶满则拒绝新请求。

漏桶与令牌桶的区别

漏桶算法

  • 请求以固定速率处理
  • 不允许突发流量
  • 起到流量整形(Traffic Shaping)作用
  • 确保系统以稳定的速率处理请求

令牌桶算法

  • 可以处理短时间的突发流量
  • 长期平均速率受令牌产生速率限制
  • 更加灵活,可以通过参数调整应对不同场景
  • 适用于需要处理突发流量的场景
java
1@Component
2public class LeakyBucketRateLimiter {
3
4 private final Map<String, LeakyBucket> buckets = new ConcurrentHashMap<>();
5 private final double rate;
6 private final int capacity;
7
8 public LeakyBucketRateLimiter(double rate, int capacity) {
9 this.rate = rate;
10 this.capacity = capacity;
11 }
12
13 public boolean isAllowed(String key) {
14 LeakyBucket bucket = buckets.computeIfAbsent(key, k -> new LeakyBucket(rate, capacity));
15 return bucket.tryConsume();
16 }
17
18 public void reset(String key) {
19 buckets.remove(key);
20 }
21}

3. 限流策略

3.1 分级限流

分级限流实现
java
1@Component
2public class TieredRateLimiter {
3
4 private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
5 private final RateLimitProperties properties;
6
7 public TieredRateLimiter(RateLimitProperties properties) {
8 this.properties = properties;
9 }
10
11 public boolean isAllowed(String key, RateLimitTier tier) {
12 RateLimiter limiter = getOrCreateLimiter(key, tier);
13 return limiter.tryAcquire();
14 }
15
16 private RateLimiter getOrCreateLimiter(String key, RateLimitTier tier) {
17 String limiterKey = key + ":" + tier.name();
18 return limiters.computeIfAbsent(limiterKey, k -> {
19 double rate = getRateForTier(tier);
20 return RateLimiter.create(rate);
21 });
22 }
23
24 private double getRateForTier(RateLimitTier tier) {
25 switch (tier) {
26 case VIP:
27 return 1000.0; // VIP用户:1000 QPS
28 case PREMIUM:
29 return 500.0; // 高级用户:500 QPS
30 case STANDARD:
31 return 100.0; // 普通用户:100 QPS
32 case FREE:
33 return 10.0; // 免费用户:10 QPS
34 default:
35 return 10.0;
36 }
37 }
38}
39
40enum RateLimitTier {
41 VIP, // VIP用户
42 PREMIUM, // 高级用户
43 STANDARD, // 普通用户
44 FREE // 免费用户
45}

3.2 动态限流

动态限流实现
java
1@Component
2public class DynamicRateLimiter {
3
4 private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
5 private final AtomicReference<Double> globalRate = new AtomicReference<>(100.0);
6
7 @Scheduled(fixedRate = 5000)
8 public void updateRateLimits() {
9 // 根据系统负载动态调整限流
10 double cpuUsage = getCpuUsage();
11 double memoryUsage = getMemoryUsage();
12
13 double newRate = calculateNewRate(cpuUsage, memoryUsage);
14 globalRate.set(newRate);
15
16 // 更新所有限流器
17 limiters.values().forEach(limiter -> {
18 // 注意:Guava的RateLimiter不支持动态修改速率
19 // 这里需要重新创建限流器
20 });
21 }
22
23 private double calculateNewRate(double cpuUsage, double memoryUsage) {
24 double baseRate = 100.0;
25
26 // CPU使用率超过80%,降低限流
27 if (cpuUsage > 80) {
28 baseRate *= 0.5;
29 }
30
31 // 内存使用率超过80%,降低限流
32 if (memoryUsage > 80) {
33 baseRate *= 0.5;
34 }
35
36 return Math.max(baseRate, 10.0); // 最低10 QPS
37 }
38
39 private double getCpuUsage() {
40 OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
41 if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
42 return ((com.sun.management.OperatingSystemMXBean) osBean).getCpuLoad() * 100;
43 }
44 return 0.0;
45 }
46
47 private double getMemoryUsage() {
48 Runtime runtime = Runtime.getRuntime();
49 long maxMemory = runtime.maxMemory();
50 long usedMemory = runtime.totalMemory() - runtime.freeMemory();
51 return (double) usedMemory / maxMemory * 100;
52 }
53}

4. 限流实现

4.1 注解式限流

限流注解实现
java
1@Target(ElementType.METHOD)
2@Retention(RetentionPolicy.RUNTIME)
3public @interface RateLimit {
4 String key() default ""; // 限流键
5 double qps() default 100.0; // QPS限制
6 int burst() default 200; // 突发流量限制
7 RateLimitDimension dimension() default RateLimitDimension.API;
8 String fallback() default ""; // 降级方法
9}
10
11@Aspect
12@Component
13public class RateLimitAspect {
14
15 private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
16
17 @Around("@annotation(rateLimit)")
18 public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
19 String key = getRateLimitKey(point, rateLimit);
20 RateLimiter limiter = getOrCreateLimiter(key, rateLimit.qps());
21
22 if (limiter.tryAcquire()) {
23 return point.proceed();
24 } else {
25 // 限流触发,执行降级逻辑
26 return executeFallback(point, rateLimit);
27 }
28 }
29
30 private String getRateLimitKey(ProceedingJoinPoint point, RateLimit rateLimit) {
31 if (!rateLimit.key().isEmpty()) {
32 return rateLimit.key();
33 }
34
35 // 自动生成key
36 String methodName = point.getSignature().getName();
37 String className = point.getTarget().getClass().getSimpleName();
38 return className + ":" + methodName;
39 }
40
41 private RateLimiter getOrCreateLimiter(String key, double qps) {
42 return limiters.computeIfAbsent(key, k -> RateLimiter.create(qps));
43 }
44
45 private Object executeFallback(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
46 if (!rateLimit.fallback().isEmpty()) {
47 // 调用降级方法
48 Method fallbackMethod = findFallbackMethod(point, rateLimit.fallback());
49 if (fallbackMethod != null) {
50 return fallbackMethod.invoke(point.getTarget(), point.getArgs());
51 }
52 }
53
54 // 默认降级:抛出限流异常
55 throw new RateLimitExceededException("请求频率超限");
56 }
57
58 private Method findFallbackMethod(ProceedingJoinPoint point, String fallbackMethodName) {
59 try {
60 return point.getTarget().getClass().getMethod(fallbackMethodName);
61 } catch (NoSuchMethodException e) {
62 return null;
63 }
64 }
65}
66
67// 使用示例
68@RestController
69public class UserController {
70
71 @RateLimit(qps = 100, dimension = RateLimitDimension.USER)
72 @GetMapping("/users/{id}")
73 public User getUser(@PathVariable Long id) {
74 return userService.getUserById(id);
75 }
76
77 @RateLimit(qps = 10, fallback = "getUserFallback")
78 @GetMapping("/users/{id}/detail")
79 public UserDetail getUserDetail(@PathVariable Long id) {
80 return userService.getUserDetail(id);
81 }
82
83 public UserDetail getUserFallback(Long id) {
84 // 降级逻辑:返回基本信息
85 return new UserDetail(id, "用户" + id, "基本信息");
86 }
87}

4.2 过滤器限流

过滤器限流实现
java
1@Component
2public class RateLimitFilter implements Filter {
3
4 private final RateLimiter rateLimiter;
5 private final ObjectMapper objectMapper;
6
7 public RateLimitFilter() {
8 this.rateLimiter = RateLimiter.create(100.0); // 100 QPS
9 this.objectMapper = new ObjectMapper();
10 }
11
12 @Override
13 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
14 throws IOException, ServletException {
15
16 HttpServletRequest httpRequest = (HttpServletRequest) request;
17 HttpServletResponse httpResponse = (HttpServletResponse) response;
18
19 // 获取限流键
20 String key = getRateLimitKey(httpRequest);
21
22 if (rateLimiter.tryAcquire()) {
23 chain.doFilter(request, response);
24 } else {
25 // 限流响应
26 sendRateLimitResponse(httpResponse);
27 }
28 }
29
30 private String getRateLimitKey(HttpServletRequest request) {
31 // 基于IP的限流
32 String ip = getClientIp(request);
33
34 // 基于用户的限流
35 String userId = getUserId(request);
36
37 // 基于API的限流
38 String api = request.getRequestURI();
39
40 return String.format("%s:%s:%s", ip, userId, api);
41 }
42
43 private String getClientIp(HttpServletRequest request) {
44 String xForwardedFor = request.getHeader("X-Forwarded-For");
45 if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
46 return xForwardedFor.split(",")[0].trim();
47 }
48 return request.getRemoteAddr();
49 }
50
51 private String getUserId(HttpServletRequest request) {
52 // 从请求头或参数中获取用户ID
53 String userId = request.getHeader("X-User-ID");
54 if (userId == null) {
55 userId = request.getParameter("userId");
56 }
57 return userId != null ? userId : "anonymous";
58 }
59
60 private void sendRateLimitResponse(HttpServletResponse response) throws IOException {
61 response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
62 response.setContentType("application/json");
63
64 Map<String, Object> result = new HashMap<>();
65 result.put("code", 429);
66 result.put("message", "请求频率超限,请稍后重试");
67 result.put("timestamp", System.currentTimeMillis());
68
69 response.getWriter().write(objectMapper.writeValueAsString(result));
70 }
71}

5. 限流监控

5.1 限流统计

限流统计实现
java
1@Component
2public class RateLimitStatistics {
3
4 private final Map<String, AtomicLong> requestCounts = new ConcurrentHashMap<>();
5 private final Map<String, AtomicLong> limitCounts = new ConcurrentHashMap<>();
6 private final Map<String, AtomicLong> totalCounts = new ConcurrentHashMap<>();
7
8 public void recordRequest(String key, boolean limited) {
9 requestCounts.computeIfAbsent(key, k -> new AtomicLong()).incrementAndGet();
10 totalCounts.computeIfAbsent(key, k -> new AtomicLong()).incrementAndGet();
11
12 if (limited) {
13 limitCounts.computeIfAbsent(key, k -> new AtomicLong()).incrementAndGet();
14 }
15 }
16
17 public RateLimitStats getStats(String key) {
18 long requests = requestCounts.getOrDefault(key, new AtomicLong()).get();
19 long limits = limitCounts.getOrDefault(key, new AtomicLong()).get();
20 long total = totalCounts.getOrDefault(key, new AtomicLong()).get();
21
22 double limitRate = total > 0 ? (double) limits / total * 100 : 0;
23
24 return new RateLimitStats(key, requests, limits, total, limitRate);
25 }
26
27 public Map<String, RateLimitStats> getAllStats() {
28 Map<String, RateLimitStats> stats = new HashMap<>();
29
30 for (String key : totalCounts.keySet()) {
31 stats.put(key, getStats(key));
32 }
33
34 return stats;
35 }
36
37 @Scheduled(fixedRate = 60000) // 每分钟重置计数器
38 public void resetCounters() {
39 requestCounts.clear();
40 limitCounts.clear();
41 }
42}
43
44class RateLimitStats {
45 private final String key;
46 private final long requests;
47 private final long limits;
48 private final long total;
49 private final double limitRate;
50
51 // 构造函数和getter方法
52}

5.2 限流告警

限流告警实现
java
1@Component
2public class RateLimitAlert {
3
4 @Autowired
5 private RateLimitStatistics statistics;
6
7 @Autowired
8 private EmailService emailService;
9
10 @Value("${rate.limit.alert.threshold:80}")
11 private double alertThreshold;
12
13 @Scheduled(fixedRate = 30000) // 每30秒检查一次
14 public void checkRateLimitAlert() {
15 Map<String, RateLimitStats> stats = statistics.getAllStats();
16
17 for (RateLimitStats stat : stats.values()) {
18 if (stat.getLimitRate() > alertThreshold) {
19 sendAlert(stat);
20 }
21 }
22 }
23
24 private void sendAlert(RateLimitStats stat) {
25 String subject = "限流告警";
26 String message = String.format(
27 "限流键: %s\n" +
28 "请求总数: %d\n" +
29 "限流次数: %d\n" +
30 "限流率: %.2f%%\n" +
31 "时间: %s",
32 stat.getKey(),
33 stat.getTotal(),
34 stat.getLimits(),
35 stat.getLimitRate(),
36 new Date()
37 );
38
39 try {
40 emailService.sendAlertEmail(subject, message);
41 } catch (Exception e) {
42 log.error("发送限流告警失败", e);
43 }
44 }
45}

6. 面试题精选

6.1 基础概念题

Q: 什么是限流?为什么需要限流?

A: 限流是指控制系统访问频率的技术。需要限流的原因:

  • 保护系统:防止系统过载崩溃
  • 资源管理:合理分配系统资源
  • 成本控制:控制API调用成本
  • 用户体验:保证核心功能可用
  • 安全防护:防止恶意攻击

Q: 常见的限流算法有哪些?各有什么特点?

A: 常见的限流算法:

  • 固定窗口:简单易实现,但存在临界问题
  • 滑动窗口:解决临界问题,但实现复杂
  • 令牌桶:支持突发流量,适合流量整形
  • 漏桶:平滑流量,但不支持突发流量

6.2 算法实现题

Q: 如何实现令牌桶算法?

A: 令牌桶算法实现步骤:

  • 初始化:设置令牌生成速率和桶容量
  • 令牌生成:按速率向桶中添加令牌
  • 请求处理:从桶中取出令牌处理请求
  • 限流判断:桶中令牌不足时拒绝请求

Q: 如何实现滑动窗口算法?

A: 滑动窗口算法实现步骤:

  • 分桶:将时间窗口分为多个小桶
  • 计数:在每个桶中记录请求数量
  • 滑动:根据当前时间更新窗口
  • 统计:统计窗口内所有桶的请求总数

6.3 实际应用题

Q: 如何设计一个分布式限流系统?

A: 分布式限流系统设计:

  • 集中式存储:使用Redis存储限流状态
  • 分布式协调:使用Zookeeper或Consul
  • 一致性保证:使用分布式锁或原子操作
  • 故障处理:降级到本地限流
  • 监控告警:实时监控限流状态

Q: 如何实现动态限流?

A: 动态限流实现方法:

  • 系统监控:监控CPU、内存、网络等指标
  • 动态调整:根据系统负载调整限流参数
  • 配置中心:使用配置中心动态修改限流规则
  • 机器学习:使用ML算法预测和调整限流
限流学习要点
  1. 理解限流概念:掌握限流的目的、维度和策略
  2. 掌握限流算法:学会固定窗口、滑动窗口、令牌桶、漏桶算法
  3. 熟悉实现方式:了解注解式、过滤器式、中间件式限流
  4. 学会监控告警:掌握限流统计、监控、告警机制
  5. 了解最佳实践:学习业界成熟的限流方案

通过本章的学习,你应该已经掌握了限流系统设计的核心概念、算法实现和最佳实践。限流系统设计是保护系统稳定运行的重要技术,掌握这些技术可以帮助你构建出健壮的系统。在实际项目中,合理运用限流技术可以有效防止系统过载,保证服务质量。

参与讨论