51-限流算法详解

0 阅读12分钟

限流算法详解

本章导读

限流是保护系统稳定性的第一道防线。本章将深入剖析固定窗口、滑动窗口、令牌桶、漏桶四种主流限流算法的原理与实现,并结合分布式场景讲解 Redis + Lua 的生产级方案。

学习目标

  • 目标1:理解四种限流算法的工作原理,能够分析它们的优缺点和适用场景
  • 目标2:掌握基于 Redis + Lua 的分布式限流实现,理解原子性保证的重要性
  • 目标3:能够设计多级限流方案,实现本地限流与分布式限流的组合策略

前置知识:已完成 Redis 相关章节的学习,理解分布式系统基本概念

阅读时长:约 35 分钟

一、知识概述

限流是保护系统的重要手段,通过控制请求速率,防止系统因过载而崩溃。本文将深入讲解主流的限流算法及其实现:

  • 固定窗口算法
  • 滑动窗口算法
  • 令牌桶算法
  • 漏桶算法
  • 分布式限流
  • 限流框架实践

二、为什么需要限流

2.1 系统面临的问题

正常流量 ──────→ [服务] ──────→ 正常响应
                    ↓
突发流量 ──────→ [服务] ──────→ 系统崩溃/响应超时

常见场景:

  • 秒杀活动瞬间流量激增
  • 恶意攻击或爬虫
  • 下游服务响应慢导致的请求堆积
  • 上游服务异常重试风暴

2.2 限流的目标

  1. 保护系统:防止系统因过载而崩溃
  2. 公平分配资源:确保关键业务优先
  3. 降级保护:在系统压力过大时优雅降级
  4. 成本控制:控制资源消耗

三、固定窗口算法

3.1 原理

将时间划分为固定大小的窗口,在每个窗口内统计请求数量,超过阈值则拒绝。

时间线(窗口大小=1秒,阈值=5):
|--窗口1--|--窗口2--|--窗口3--|
   352次
          允许       允许

3.2 实现

/**
 * 固定窗口限流器
 */
public class FixedWindowRateLimiter {
    
    private final long windowSize;      // 窗口大小(毫秒)
    private final int limit;            // 窗口内最大请求数
    private final AtomicInteger counter; // 当前窗口计数
    private final AtomicLong windowStart; // 当前窗口开始时间
    
    public FixedWindowRateLimiter(long windowSizeMs, int limit) {
        this.windowSize = windowSizeMs;
        this.limit = limit;
        this.counter = new AtomicInteger(0);
        this.windowStart = new AtomicLong(System.currentTimeMillis());
    }
    
    /**
     * 尝试获取许可
     */
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        long currentWindowStart = windowStart.get();
        
        // 检查是否需要切换窗口
        if (now - currentWindowStart >= windowSize) {
            // 新窗口,重置计数
            windowStart.set(now);
            counter.set(0);
        }
        
        // 检查是否超过限制
        if (counter.get() >= limit) {
            return false;
        }
        
        counter.incrementAndGet();
        return true;
    }
    
    /**
     * 获取当前窗口状态
     */
    public WindowStatus getStatus() {
        long now = System.currentTimeMillis();
        long currentWindowStart = windowStart.get();
        long remainingTime = windowSize - (now - currentWindowStart);
        
        return new WindowStatus(
            counter.get(),
            limit,
            Math.max(0, remainingTime)
        );
    }
    
    @Data
    @AllArgsConstructor
    public static class WindowStatus {
        private int currentCount;
        private int limit;
        private long remainingTimeMs;
    }
}

3.3 问题:边界突发

窗口大小=1秒,阈值=5

|--窗口1--|--窗口2--|
   ↑         ↑
  0.91.15次请求   5次请求

在2秒内实际处理了10次请求,在窗口边界存在突发流量问题。

四、滑动窗口算法

4.1 原理

将窗口划分为多个小格子,滑动统计最近N个格子的请求数。

窗口大小=1秒,格子数=10,每个格子=100ms

格子: [0][1][2][3][4][5][6][7][8][9]
       ←─────── 滑动窗口 ───────→
       
时间流逝,窗口向前滑动,丢弃最旧的格子,添加新格子。

4.2 实现

/**
 * 滑动窗口限流器
 */
public class SlidingWindowRateLimiter {
    
    private final long windowSize;      // 窗口大小(毫秒)
    private final int limit;            // 窗口内最大请求数
    private final int subWindowCount;   // 子窗口数量
    private final long subWindowSize;   // 子窗口大小
    
    private final int[] counters;       // 每个子窗口的计数
    private final AtomicLong currentWindowStart; // 当前子窗口开始时间
    
    public SlidingWindowRateLimiter(long windowSizeMs, int limit, int subWindowCount) {
        this.windowSize = windowSizeMs;
        this.limit = limit;
        this.subWindowCount = subWindowCount;
        this.subWindowSize = windowSizeMs / subWindowCount;
        this.counters = new int[subWindowCount];
        this.currentWindowStart = new AtomicLong(System.currentTimeMillis());
    }
    
    public SlidingWindowRateLimiter(long windowSizeMs, int limit) {
        this(windowSizeMs, limit, 10); // 默认10个子窗口
    }
    
    /**
     * 尝试获取许可
     */
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        
        // 计算当前子窗口索引
        int currentIndex = (int) ((now / subWindowSize) % subWindowCount);
        long currentWindowStartTime = now - now % subWindowSize;
        
        // 检查是否需要重置子窗口
        if (currentWindowStart.get() != currentWindowStartTime) {
            // 清理过期的子窗口
            cleanExpiredWindows(now);
            currentWindowStart.set(currentWindowStartTime);
        }
        
        // 计算当前窗口内的总请求数
        int totalCount = calculateTotalCount(currentIndex);
        
        // 检查是否超过限制
        if (totalCount >= limit) {
            return false;
        }
        
        counters[currentIndex]++;
        return true;
    }
    
    /**
     * 清理过期的子窗口
     */
    private void cleanExpiredWindows(long now) {
        for (int i = 0; i < subWindowCount; i++) {
            // 计算该子窗口的时间范围
            long windowStartTime = now - now % subWindowSize - 
                (subWindowCount - i) * subWindowSize;
            long windowEndTime = windowStartTime + subWindowSize;
            
            // 如果子窗口已经过期,重置计数
            if (windowEndTime <= now - windowSize) {
                counters[i] = 0;
            }
        }
    }
    
    /**
     * 计算当前窗口内的总请求数
     */
    private int calculateTotalCount(int currentIndex) {
        int total = 0;
        for (int i = 0; i < subWindowCount; i++) {
            total += counters[i];
        }
        return total;
    }
}

4.3 基于Redis的分布式滑动窗口

/**
 * Redis滑动窗口限流器
 */
@Service
public class RedisSlidingWindowLimiter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String SCRIPT = """
        local key = KEYS[1]
        local limit = tonumber(ARGV[1])
        local window = tonumber(ARGV[2])
        local now = tonumber(ARGV[3])
        
        -- 删除过期记录
        redis.call('zremrangebyscore', key, 0, now - window * 1000)
        
        -- 统计当前窗口请求数
        local count = redis.call('zcard', key)
        
        if count < limit then
            -- 添加当前请求记录
            redis.call('zadd', key, now, now .. ':' .. math.random())
            redis.call('expire', key, window)
            return 1
        else
            return 0
        end
        """;
    
    public boolean tryAcquire(String key, int limit, long windowSeconds) {
        String redisKey = "rate_limit:" + key;
        long now = System.currentTimeMillis();
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(SCRIPT, Long.class);
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(redisKey),
            String.valueOf(limit),
            String.valueOf(windowSeconds),
            String.valueOf(now)
        );
        
        return result != null && result == 1L;
    }
    
    /**
     * 获取当前窗口请求数
     */
    public long getCurrentCount(String key, long windowSeconds) {
        String redisKey = "rate_limit:" + key;
        long now = System.currentTimeMillis();
        
        redisTemplate.opsForZSet().removeRangeByScore(redisKey, 0, now - windowSeconds * 1000);
        Long count = redisTemplate.opsForZSet().size(redisKey);
        return count != null ? count : 0;
    }
}

五、令牌桶算法

5.1 原理

以固定速率生成令牌放入桶中,请求到达时从桶中取令牌,有令牌则通过,无令牌则拒绝。

                ┌─────────────┐
    令牌生成 ──→│  令牌桶     │
    (r/秒)      │  容量=b     │──→ 请求取令牌 ──→ 通过/拒绝
                └─────────────┘
                
- 桶容量:最多存放b个令牌
- 生成速率:每秒生成r个令牌
- 桶满后新令牌丢弃

5.2 特点

  • 允许突发:桶中有令牌时可以快速处理一批请求
  • 平滑限流:长期来看,速率被限制在r/秒
  • 灵活:可以配置不同的令牌消耗量

5.3 实现

/**
 * 令牌桶限流器
 */
public class TokenBucketRateLimiter {
    
    private final long capacity;        // 桶容量
    private final double rate;          // 令牌生成速率(个/毫秒)
    private final AtomicLong tokens;    // 当前令牌数
    private final AtomicLong lastRefillTime; // 上次填充时间
    
    public TokenBucketRateLimiter(long capacity, double ratePerSecond) {
        this.capacity = capacity;
        this.rate = ratePerSecond / 1000; // 转换为每毫秒
        this.tokens = new AtomicLong(capacity);
        this.lastRefillTime = new AtomicLong(System.currentTimeMillis());
    }
    
    /**
     * 尝试获取令牌
     */
    public boolean tryAcquire() {
        return tryAcquire(1);
    }
    
    /**
     * 尝试获取指定数量令牌
     */
    public synchronized boolean tryAcquire(long tokensRequested) {
        refill();
        
        long currentTokens = tokens.get();
        if (currentTokens >= tokensRequested) {
            tokens.addAndGet(-tokensRequested);
            return true;
        }
        
        return false;
    }
    
    /**
     * 填充令牌
     */
    private void refill() {
        long now = System.currentTimeMillis();
        long lastRefill = lastRefillTime.get();
        
        if (now > lastRefill) {
            // 计算应该生成的令牌数
            long elapsed = now - lastRefill;
            long tokensToAdd = (long) (elapsed * rate);
            
            if (tokensToAdd > 0) {
                // 更新令牌数(不超过容量)
                long newTokens = Math.min(capacity, tokens.get() + tokensToAdd);
                tokens.set(newTokens);
                lastRefillTime.set(now);
            }
        }
    }
    
    /**
     * 获取当前状态
     */
    public BucketStatus getStatus() {
        refill();
        return new BucketStatus(tokens.get(), capacity, rate * 1000);
    }
    
    @Data
    @AllArgsConstructor
    public static class BucketStatus {
        private long currentTokens;
        private long capacity;
        private double ratePerSecond;
    }
}

5.4 Guava RateLimiter

import com.google.common.util.concurrent.RateLimiter;

@Service
public class GuavaRateLimiterService {
    
    // 创建令牌桶限流器,每秒生成10个令牌
    private final RateLimiter rateLimiter = RateLimiter.create(10.0);
    
    /**
     * 尝试获取许可(非阻塞)
     */
    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
    
    /**
     * 尝试获取许可(带超时)
     */
    public boolean tryAcquire(long timeout, TimeUnit unit) {
        return rateLimiter.tryAcquire(timeout, unit);
    }
    
    /**
     * 获取许可(阻塞等待)
     */
    public void acquire() {
        rateLimiter.acquire();
    }
    
    /**
     * 获取指定数量许可
     */
    public void acquire(int permits) {
        rateLimiter.acquire(permits);
    }
    
    /**
     * 预热版令牌桶(适用于需要预热期的场景)
     */
    public RateLimiter createWarmup(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
        // 在预热期内,令牌生成速率逐渐增加
        return RateLimiter.create(permitsPerSecond, warmupPeriod, unit);
    }
}

六、漏桶算法

6.1 原理

请求像水一样流入桶中,桶以固定速率漏水(处理请求),桶满则溢出(拒绝请求)。

                    ┌─────────────┐
    请求流入 ──────→│   漏桶      │
                   │  容量=b     │──→ 固定速率流出 ──→ 处理
                   │  漏出=r/秒  │
                   └─────────────┘
                        ↓
                    桶满溢出 ──→ 拒绝

6.2 特点

  • 强制恒定速率:流出速率固定,不能突发
  • 平滑流量:将不规则流量整形为恒定流量
  • 保护下游:防止突发流量冲击下游系统

6.3 实现

/**
 * 漏桶限流器
 */
public class LeakyBucketRateLimiter {
    
    private final long capacity;        // 桶容量
    private final double rate;          // 漏水速率(个/毫秒)
    private final AtomicLong water;     // 当前水量
    private final AtomicLong lastLeakTime; // 上次漏水时间
    
    public LeakyBucketRateLimiter(long capacity, double ratePerSecond) {
        this.capacity = capacity;
        this.rate = ratePerSecond / 1000;
        this.water = new AtomicLong(0);
        this.lastLeakTime = new AtomicLong(System.currentTimeMillis());
    }
    
    /**
     * 尝试添加水滴(请求)
     */
    public synchronized boolean tryAcquire() {
        leak();
        
        if (water.get() < capacity) {
            water.incrementAndGet();
            return true;
        }
        
        return false;
    }
    
    /**
     * 漏水
     */
    private void leak() {
        long now = System.currentTimeMillis();
        long lastLeak = lastLeakTime.get();
        
        if (now > lastLeak) {
            // 计算应该漏出的水量
            long elapsed = now - lastLeak;
            long leaked = (long) (elapsed * rate);
            
            if (leaked > 0) {
                // 更新水量(不低于0)
                long newWater = Math.max(0, water.get() - leaked);
                water.set(newWater);
                lastLeakTime.set(now);
            }
        }
    }
    
    /**
     * 获取当前状态
     */
    public BucketStatus getStatus() {
        leak();
        return new BucketStatus(water.get(), capacity, rate * 1000);
    }
    
    @Data
    @AllArgsConstructor
    public static class BucketStatus {
        private long currentWater;
        private long capacity;
        private double ratePerSecond;
    }
}

七、算法对比

7.1 对比表格

算法允许突发流量整形实现复杂度空间复杂度适用场景
固定窗口是(边界)O(1)简单限流
滑动窗口O(n)精确限流
令牌桶O(1)API限流
漏桶O(1)流量整形

7.2 选择建议

                    ┌─────────────────────┐
                    │ 是否需要允许突发流量?│
                    └──────────┬──────────┘
                              │
              ┌───────────────┴───────────────┐
              │ 是                            │ 否
              ↓                               ↓
    ┌─────────────────┐              ┌─────────────────┐
    │ 令牌桶算法       │              │ 是否需要精确限流?│
    └─────────────────┘              └────────┬────────┘
                                              │
                              ┌───────────────┴───────────────┐
                              │ 是                            │ 否
                              ↓                               ↓
                    ┌─────────────────┐              ┌─────────────────┐
                    │ 滑动窗口算法     │              │ 漏桶算法         │
                    └─────────────────┘              └─────────────────┘

八、分布式限流

8.1 Redis + Lua实现

/**
 * 分布式令牌桶限流器
 */
@Service
public class DistributedTokenBucketLimiter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String SCRIPT = """
        local key = KEYS[1]
        local capacity = tonumber(ARGV[1])
        local rate = tonumber(ARGV[2])
        local requested = tonumber(ARGV[3])
        local now = tonumber(ARGV[4])
        
        -- 获取当前桶状态
        local info = redis.call('hmget', key, 'tokens', 'last_time')
        local tokens = tonumber(info[1]) or capacity
        local lastTime = tonumber(info[2]) or now
        
        -- 计算新生成的令牌
        local delta = math.max(0, now - lastTime)
        local newTokens = delta / 1000 * rate
        
        -- 更新令牌数
        tokens = math.min(capacity, tokens + newTokens)
        
        -- 判断是否足够
        if tokens >= requested then
            tokens = tokens - requested
            redis.call('hmset', key, 'tokens', tokens, 'last_time', now)
            redis.call('expire', key, math.ceil(capacity / rate) + 1)
            return {1, tokens}
        else
            redis.call('hmset', key, 'tokens', tokens, 'last_time', now)
            redis.call('expire', key, math.ceil(capacity / rate) + 1)
            return {0, tokens}
        end
        """;
    
    /**
     * 尝试获取令牌
     */
    public boolean tryAcquire(String key, long capacity, double ratePerSecond, long requested) {
        String redisKey = "token_bucket:" + key;
        long now = System.currentTimeMillis();
        
        DefaultRedisScript<List> script = new DefaultRedisScript<>(SCRIPT, List.class);
        List<Long> result = redisTemplate.execute(
            script,
            Collections.singletonList(redisKey),
            String.valueOf(capacity),
            String.valueOf(ratePerSecond),
            String.valueOf(requested),
            String.valueOf(now)
        );
        
        return result != null && !result.isEmpty() && result.get(0) == 1L;
    }
}

8.2 集群限流(Redis Cluster)

/**
 * 集群限流服务
 */
@Service
public class ClusterRateLimiter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 集群总限流 + 本地限流
     * 解决Redis集群限流的性能问题
     */
    public boolean tryAcquire(String key, int globalLimit, int localLimit) {
        // 第一层:本地限流(快速失败)
        if (!localTryAcquire(key, localLimit)) {
            return false;
        }
        
        // 第二层:全局限流(精确控制)
        return globalTryAcquire(key, globalLimit);
    }
    
    // 本地限流器缓存
    private final Cache<String, TokenBucketRateLimiter> localLimiters = 
        Caffeine.newBuilder()
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build();
    
    private boolean localTryAcquire(String key, int limit) {
        TokenBucketRateLimiter limiter = localLimiters.get(key, 
            k -> new TokenBucketRateLimiter(limit, limit));
        return limiter.tryAcquire();
    }
    
    private boolean globalTryAcquire(String key, int limit) {
        // 使用Redis滑动窗口
        return redisTryAcquire(key, limit, 1);
    }
    
    private boolean redisTryAcquire(String key, int limit, int windowSeconds) {
        // ... Redis滑动窗口实现
        return true;
    }
}

九、限流框架实践

9.1 Spring Boot + 自定义注解

// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    String key() default "";
    int limit() default 100;
    int window() default 1; // 秒
    LimitType type() default LimitType.TOKEN_BUCKET;
    String message() default "请求过于频繁,请稍后再试";
}

public enum LimitType {
    FIXED_WINDOW,
    SLIDING_WINDOW,
    TOKEN_BUCKET,
    LEAKY_BUCKET
}

// 切面实现
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
    
    private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
    
    @Autowired
    private RedisSlidingWindowLimiter redisLimiter;
    
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
        String key = generateKey(point, rateLimit);
        boolean allowed = false;
        
        switch (rateLimit.type()) {
            case TOKEN_BUCKET:
                allowed = getTokenBucketLimiter(key, rateLimit).tryAcquire();
                break;
            case SLIDING_WINDOW:
                allowed = redisLimiter.tryAcquire(key, rateLimit.limit(), rateLimit.window());
                break;
            case FIXED_WINDOW:
                allowed = fixedWindowTryAcquire(key, rateLimit.limit(), rateLimit.window());
                break;
            default:
                allowed = true;
        }
        
        if (!allowed) {
            throw new RateLimitException(rateLimit.message());
        }
        
        return point.proceed();
    }
    
    private RateLimiter getTokenBucketLimiter(String key, RateLimit rateLimit) {
        return limiters.computeIfAbsent(key, 
            k -> RateLimiter.create(rateLimit.limit()));
    }
    
    private String generateKey(ProceedingJoinPoint point, RateLimit rateLimit) {
        if (!rateLimit.key().isEmpty()) {
            return rateLimit.key();
        }
        
        MethodSignature signature = (MethodSignature) point.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        
        return className + ":" + methodName;
    }
}

// 异常处理
@RestControllerAdvice
public class RateLimitExceptionHandler {
    
    @ExceptionHandler(RateLimitException.class)
    public ResponseEntity<ErrorResponse> handleRateLimitException(RateLimitException e) {
        return ResponseEntity
            .status(HttpStatus.TOO_MANY_REQUESTS)
            .body(new ErrorResponse(429, e.getMessage()));
    }
}

@Data
@AllArgsConstructor
class ErrorResponse {
    private int code;
    private String message;
}

class RateLimitException extends RuntimeException {
    public RateLimitException(String message) {
        super(message);
    }
}

9.2 使用示例

@RestController
@RequestMapping("/api")
public class ApiController {
    
    /**
     * 令牌桶限流:每秒最多100次
     */
    @GetMapping("/data")
    @RateLimit(limit = 100, message = "API调用过于频繁")
    public String getData() {
        return "data";
    }
    
    /**
     * 滑动窗口限流:每秒最多10次
     */
    @PostMapping("/submit")
    @RateLimit(limit = 10, type = LimitType.SLIDING_WINDOW, message = "提交太频繁了")
    public String submit(@RequestBody String data) {
        return "submitted";
    }
    
    /**
     * IP级别限流
     */
    @GetMapping("/search")
    @RateLimit(key = "'search:' + #ip", limit = 50, type = LimitType.SLIDING_WINDOW)
    public String search(@RequestHeader("X-Real-IP") String ip, 
                         @RequestParam String keyword) {
        return "search result";
    }
}

9.3 Sentinel限流

/**
 * Sentinel限流集成
 */
@Configuration
public class SentinelConfig {
    
    @PostConstruct
    public void init() {
        // 初始化规则
        List<FlowRule> rules = new ArrayList<>();
        
        // API限流规则
        FlowRule apiRule = new FlowRule();
        apiRule.setResource("api:getData");
        apiRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        apiRule.setCount(100); // QPS限制100
        rules.add(apiRule);
        
        FlowRuleManager.loadRules(rules);
    }
}

@RestController
public class SentinelController {
    
    @GetMapping("/api/data")
    public String getData() {
        try (Entry entry = SphU.entry("api:getData")) {
            // 业务逻辑
            return "data";
        } catch (BlockException e) {
            // 被限流
            throw new RateLimitException("系统繁忙,请稍后再试");
        }
    }
    
    /**
     * 使用注解方式
     */
    @SentinelResource(value = "api:submit", blockHandler = "handleBlock")
    @PostMapping("/api/submit")
    public String submit(@RequestBody String data) {
        return "submitted";
    }
    
    public String handleBlock(String data, BlockException e) {
        return "系统繁忙,请稍后再试";
    }
}

十、高级应用

10.1 自适应限流

/**
 * 自适应限流器
 * 根据系统负载动态调整限流阈值
 */
@Service
public class AdaptiveRateLimiter {
    
    private final AtomicReference<Double> currentLimit;
    private final double minLimit;
    private final double maxLimit;
    
    public AdaptiveRateLimiter(double minLimit, double maxLimit) {
        this.minLimit = minLimit;
        this.maxLimit = maxLimit;
        this.currentLimit = new AtomicReference<>(maxLimit);
    }
    
    /**
     * 定期更新限流阈值
     */
    @Scheduled(fixedRate = 1000)
    public void updateLimit() {
        // 获取系统指标
        double cpuUsage = getCpuUsage();
        double memoryUsage = getMemoryUsage();
        double avgResponseTime = getAvgResponseTime();
        
        // 计算新的限流阈值
        double factor = calculateFactor(cpuUsage, memoryUsage, avgResponseTime);
        double newLimit = maxLimit * factor;
        newLimit = Math.max(minLimit, Math.min(maxLimit, newLimit));
        
        currentLimit.set(newLimit);
    }
    
    private double calculateFactor(double cpuUsage, double memoryUsage, double avgResponseTime) {
        // CPU权重
        double cpuFactor = 1.0 - (cpuUsage / 100.0) * 0.5;
        
        // 内存权重
        double memoryFactor = 1.0 - (memoryUsage / 100.0) * 0.3;
        
        // 响应时间权重(响应时间>500ms认为压力大)
        double rtFactor = avgResponseTime < 500 ? 1.0 : 0.5;
        
        return cpuFactor * memoryFactor * rtFactor;
    }
    
    public double getCurrentLimit() {
        return currentLimit.get();
    }
    
    // ... 省略获取系统指标的方法
}

10.2 多级限流

/**
 * 多级限流策略
 */
@Service
public class MultiLevelRateLimiter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // L1: 本地限流器(快速失败)
    private final Cache<String, RateLimiter> localLimiters = 
        Caffeine.newBuilder().build();
    
    /**
     * 三级限流:本地 -> 分布式 -> 降级
     */
    public <T> T execute(String resource, MultiLevelLimitConfig config, 
                         Supplier<T> supplier, Supplier<T> fallback) {
        // L1: 本地限流(快速判断)
        RateLimiter localLimiter = localLimiters.get(resource, 
            k -> RateLimiter.create(config.getLocalLimit()));
        if (!localLimiter.tryAcquire()) {
            return fallback.get();
        }
        
        // L2: 分布式限流(精确控制)
        if (!distributedTryAcquire(resource, config.getDistributedLimit())) {
            return fallback.get();
        }
        
        // L3: 执行业务逻辑
        try {
            return supplier.get();
        } catch (Exception e) {
            log.error("业务执行异常", e);
            return fallback.get();
        }
    }
    
    private boolean distributedTryAcquire(String resource, int limit) {
        // Redis滑动窗口实现
        // ...
        return true;
    }
}

@Data
class MultiLevelLimitConfig {
    private double localLimit;        // 本地限流QPS
    private int distributedLimit;     // 分布式限流QPS
}

十一、总结

11.1 核心要点

  1. 选择合适的算法:根据业务特点选择限流算法
  2. 分布式场景:使用Redis + Lua保证原子性
  3. 多级防护:本地限流 + 分布式限流
  4. 监控告警:实时监控限流触发情况
  5. 优雅降级:限流触发时返回友好提示

11.2 最佳实践

// 推荐的限流配置
@Configuration
public class RateLimitConfig {
    
    // API接口限流
    public static final int API_QPS_LIMIT = 1000;
    
    // 用户级限流
    public static final int USER_QPS_LIMIT = 10;
    
    // IP级限流
    public static final int IP_QPS_LIMIT = 100;
    
    // 关键接口限流
    public static final int CRITICAL_API_LIMIT = 50;
}

限流是系统保护的重要手段,合理配置限流策略可以有效防止系统过载,保障服务的可用性。在实际应用中,需要根据业务场景和系统特点选择合适的限流算法和阈值。 断降级详解》- 学习服务熔断与降级策略

  • 扩展阅读:《亿级流量网站架构核心技术》限流章节

📝 下一章预告

下一章将学习熔断降级机制,探讨如何在系统故障时快速失败、优雅降级,防止故障扩散导致雪崩效应。


本章完