基于Redission高级应用20-自适应限流器

721 阅读7分钟

写作原由:

juejin.cn/post/737210… 文中介绍到实现限流算法有自适应限流,故此想通过Redission 实现一版该算法。:

代码实现:

import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.Queue;

/**
 * @Author derek_smart
 * @Date 202/5/27 12:18
 * @Description AdaptiveRateLimiter 自适应限流工具类 多策略实现
 */
public class AdaptiveRateLimiter {
    private final RedissonClient redissonClient;
    private final RRateLimiter rateLimiter;
    private final String rateConfigKey;
    private final Queue<Double> loadHistory = new LinkedList<>();
    private final RateAdaptationStrategy strategy;

    public enum StrategyType {
        LOAD_PROPORTION, // 根据系统负载比例调整
        HISTORY_BASED    // 根据系统当前负载和历史负载数据调整
    }

    public AdaptiveRateLimiter(RedissonClient redissonClient, String limiterName, String rateConfigKey, StrategyType strategyType) {
        this.redissonClient = redissonClient;
        this.rateLimiter = redissonClient.getRateLimiter(limiterName);
        this.rateConfigKey = rateConfigKey;
        switch (strategyType) {
            case LOAD_PROPORTION:
                this.strategy = new LoadProportionStrategy();
                break;
            case HISTORY_BASED:
                this.strategy = new HistoryBasedStrategy();
                break;
            default:
                throw new IllegalArgumentException("Unknown strategy type");
        }
        restoreRateConfiguration();
    }

    private void restoreRateConfiguration() {
        // 尝试从Redis恢复速率配置
        RateConfig rateConfig = (RateConfig) redissonClient.getMap(rateConfigKey).get("config");
        if (rateConfig != null) {
            rateLimiter.setRate(RateType.OVERALL, rateConfig.rate, rateConfig.rateInterval, rateConfig.unit);
        } else {
            // 如果Redis中没有配置,则设置默认速率
            setRate(new RateConfig(10, 1, RateIntervalUnit.SECONDS));
        }
    }

    public synchronized void setRate(RateConfig newRateConfig) {
        // 更新限流器配置
        rateLimiter.setRate(RateType.OVERALL, newRateConfig.rate, newRateConfig.rateInterval, newRateConfig.unit);
        // 将新配置持久化到Redis
        redissonClient.getMap(rateConfigKey).put("config", newRateConfig);
    }

    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }

    // 自适应限流策略接口
    public interface RateAdaptationStrategy {
        int calculateNewRate(int currentRate, Queue<Double> loadHistory, double currentLoad);
    }

    // 基于系统负载比例的自适应限流策略
    public static class LoadProportionStrategy implements RateAdaptationStrategy {
        @Override
        public int calculateNewRate(int currentRate, Queue<Double> loadHistory, double currentLoad) {
            if (currentLoad > 0.8) {
                return Math.max(currentRate / 2, 1);
            } else if (currentLoad < 0.3) {
                return currentRate * 2;
            }
            return currentRate;
        }
    }

    // 基于历史系统负载数据的自适应限流策略
    public static class HistoryBasedStrategy implements RateAdaptationStrategy {
        @Override
        public int calculateNewRate(int currentRate, Queue<Double> loadHistory, double currentLoad) {
            loadHistory.add(currentLoad);
            if (loadHistory.size() > 10) {
                loadHistory.poll(); // 保持历史数据队列大小
            }
            double averageLoad = loadHistory.stream().mapToDouble(l -> l).average().orElse(0.0);
            if (averageLoad > 0.8) {
                return Math.max(currentRate / 2, 1);
            } else if (averageLoad < 0.3) {
                return currentRate * 2;
            }
            return currentRate;
        }
    }

    public boolean acquirePermission(double currentLoad) {
        // 根据当前系统负载自适应地调整限流速率
        adaptRate(currentLoad);
        return tryAcquire();
    }

    private void adaptRate(double currentLoad) {
        // 使用策略计算新的速率
        RateConfig currentRateConfig = (RateConfig) redissonClient.getMap(rateConfigKey).get("config");
        if (currentRateConfig != null) {
            int newRate = strategy.calculateNewRate(currentRateConfig.rate, loadHistory, currentLoad);
            if (newRate != currentRateConfig.rate) {
                setRate(new RateConfig(newRate, currentRateConfig.rateInterval, currentRateConfig.unit));
            }
        }
    }

    // 辅助类,用于存储速率配置
    public static class RateConfig implements Serializable {
        private final int rate;
        private final long rateInterval;
        private final RateIntervalUnit unit;

        public RateConfig(int rate, long rateInterval, RateIntervalUnit unit) {
            this.rate = rate;
            this.rateInterval = rateInterval;
            this.unit = unit;
        }
    }
}

企业微信截图_17167885078049.png

流程图:

flowchart TB  
    A[Start] --> B{Redis Config Exists?}  
    B -- Yes --> C[Restore Rate Configuration]  
    B -- No --> D[Set Default Rate Configuration]  
    C --> E[Create RateLimiter with Strategy]  
    D --> E  
    E --> F[Acquire Permission]  
    F --> G{Permission Granted?}  
    G -- Yes --> H[Perform Action]  
    G -- No --> I[Reject or Queue Action]  
    H --> J[End]  
    I --> J  

在这个流程图中:

  • 开始时,我们检查 Redis 中是否存在限流配置。
  • 如果配置存在,我们从 Redis 恢复限流配置
  • 如果配置不存在,我们设置默认的限流配置。
  • 使用所选策略创建 RRateLimiter 实例。
  • 尝试获取权限(许可)。
  • 根据权限是否被授予,执行相应的操作或拒绝/排队操作。
  • 流程结束。

时序图:

sequenceDiagram
    participant Client
    participant AdaptiveRateLimiter
    participant RedissonClient
    participant Redis
    participant Strategy

    Client->>AdaptiveRateLimiter: create(limiterName, strategyType)
    AdaptiveRateLimiter->>RedissonClient: getRateLimiter(limiterName)
    RedissonClient->>Redis: checkConfig(rateConfigKey)
    Redis-->>RedissonClient: returnConfig
    RedissonClient-->>AdaptiveRateLimiter: setRate
    AdaptiveRateLimiter->>Strategy: select(strategyType)
    AdaptiveRateLimiter-->>Client: RateLimiter created

    loop Acquire Permission
        Client->>AdaptiveRateLimiter: acquirePermission(currentLoad)
        AdaptiveRateLimiter->>Strategy: calculateNewRate(currentLoad, loadHistory)
        Strategy-->>AdaptiveRateLimiter: newRate
        AdaptiveRateLimiter->>RedissonClient: setRate(newRate)
        RedissonClient->>Redis: updateConfig(rateConfigKey)
        Redis-->>RedissonClient: confirmation
        RedissonClient-->>AdaptiveRateLimiter: Rate updated
        AdaptiveRateLimiter->>RedissonClient: tryAcquire()
        RedissonClient->>Redis: checkPermission
        Redis-->>RedissonClient: permissionGranted
        RedissonClient-->>AdaptiveRateLimiter: returnResult
        AdaptiveRateLimiter-->>Client: permissionResult
    end

时序图中:

  • 客户端 (Client) 请求创建 AdaptiveRateLimiter 实例,传入限流器名称 (limiterName) 和策略类型 (strategyType)。
  • AdaptiveRateLimiter 通过 RedissonClient 获取 RRateLimiter 实例。
  • RedissonClient 查询 Redis 是否有现有的限流配置 (rateConfigKey)。
  • 如果 Redis 返回限流配置,RedissonClient 会根据这些配置设置限流器的速率。
  • AdaptiveRateLimiter 根据传入的 strategyType 选择对应的限流策略 (Strategy)。
  • 创建过程完成后,客户端得到创建好的 AdaptiveRateLimiter 实例。

循环过程:

  • 客户端尝试获取权限,调用 acquirePermission 方法,并传入当前系统负载 (currentLoad)。
  • AdaptiveRateLimiter 使用选择的策略来计算新的速率。
  • 策略 (Strategy) 根据当前负载和历史负载数据返回新的速率 (newRate)。
  • AdaptiveRateLimiter 使用 RedissonClient 设置新的速率。
  • RedissonClient 更新 Redis 中的配置。
  • RedissonClient 尝试从 Redis 获取权限。
  • Redis 返回权限获取结果。
  • AdaptiveRateLimiter 将权限获取结果返回给客户端。

实现概述:

实现基于 Redisson 的 RRateLimiter,它是一个分布式的限流器,可以在多个服务实例之间共享限流状态。 该实现通过自适应策略来动态调整限流速率,以适应系统负载变化。以下是实现原理及其优缺点: 实现原理:

  1. 初始化: AdaptiveRateLimiter 类在初始化时创建一个 RRateLimiter 实例,并根据提供的 StrategyType 枚举选择一个自适应限流策略。

  2. 配置恢复: 从 Redis 中恢复限流配置,如果没有现有配置,则设置默认限流速率。

  3. 自适应策略: 实现了两种策略:基于当前系统负载比例 (LoadProportionStrategy) 和基于历史负载数据 (HistoryBasedStrategy)。每种策略都有 calculateNewRate 方法,用于根据系统负载计算新的速率。

  4. 权限获取: acquirePermission 方法被用来尝试获取一个许可。在尝试获取许可之前,它会根据当前系统负载调用 adaptRate 方法,该方法使用当前策略来计算并设置新的速率。

  5. 速率调整: adaptRate 方法通过比较当前和历史负载数据来决定是否需要调整速率。如果需要,它会更新 RRateLimiter 的配置,并将新配置持久化到 Redis。

优点:

  • 自适应性: 能够根据系统负载动态调整限流速率,更好地适应负载变化。
  • 分布式支持: 基于 Redisson 的实现允许在多个服务实例间共享限流状态,适用于分布式系统。
  • 灵活性: 通过定义策略接口和枚举,可以方便地添加或更换不同的自适应策略。
  • 无阻塞: tryAcquire 方法提供了非阻塞的权限获取,适合需要快速响应的场景。

缺点:

  • 复杂性: 相对于静态限流规则,自适应限流器的逻辑更复杂,需要更多的测试来确保稳定性。
  • 调优难度: 需要根据实际的系统负载和业务需求来调整策略参数,这可能需要大量的监控数据和性能分析。
  • 延迟响应: 如果系统负载突然变化,自适应限流器可能会有一定的延迟才能调整到合适的速率。
  • Redis依赖: 依赖于 Redis 作为中心化的状态存储,如果 Redis 出现故障,限流器可能无法正常工作。

测试类:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

/**
 * @Author derek_smart
 * @Date 202/5/27 13:10
 * @Description AdaptiveRateLimiter 测试
 */
public class AdaptiveRateLimiterTest {

    public static void main(String[] args) {
        // 初始化 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redissonClient = Redisson.create(config);

        // 创建 AdaptiveRateLimiter 实例
        String limiterName = "myRateLimiter";
        String rateConfigKey = "rateLimiterConfig";
        AdaptiveRateLimiter rateLimiter;

        // 场景1: 系统负载稳定低于30%,预期速率应该增加
        System.out.println("场景1: 系统负载稳定低于30%");
        rateLimiter = new AdaptiveRateLimiter(redissonClient, limiterName, rateConfigKey, AdaptiveRateLimiter.StrategyType.LOAD_PROPORTION);
        simulateLoadAndTest(rateLimiter, 0.2);

        // 场景2: 系统负载稳定超过80%,预期速率应该降低
        System.out.println("场景2: 系统负载稳定超过80%");
        rateLimiter = new AdaptiveRateLimiter(redissonClient, limiterName, rateConfigKey, AdaptiveRateLimiter.StrategyType.LOAD_PROPORTION);
        simulateLoadAndTest(rateLimiter, 0.9);

        // 场景3: 系统负载波动,使用基于历史负载的策略
        System.out.println("场景3: 系统负载波动,使用基于历史负载的策略");
        rateLimiter = new AdaptiveRateLimiter(redissonClient, limiterName, rateConfigKey, AdaptiveRateLimiter.StrategyType.HISTORY_BASED);
        simulateFluctuatingLoadAndTest(rateLimiter);

        // 关闭 Redisson 客户端
        redissonClient.shutdown();
    }

    /**
     * 模拟了一个稳定的系统负载,并多次尝试获取权限,以查看限流器的行为是否符合预期。
     * @param rateLimiter
     * @param load
     */
    private static void simulateLoadAndTest(AdaptiveRateLimiter rateLimiter, double load) {
        for (int i = 0; i < 5; i++) {
            boolean acquired = rateLimiter.acquirePermission(load);
            System.out.println("尝试获取权限: " + (acquired ? "成功" : "失败"));
            // 模拟一段时间后再次尝试
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 模拟了一个波动的系统负载,同样多次尝试获取权限
     * @param rateLimiter
     */
    private static void simulateFluctuatingLoadAndTest(AdaptiveRateLimiter rateLimiter) {
        double[] loads = {0.5, 0.8, 0.3, 0.7, 0.2};
        for (double load : loads) {
            boolean acquired = rateLimiter.acquirePermission(load);
            System.out.println("当前负载: " + load + ",尝试获取权限: " + (acquired ? "成功" : "失败"));
            // 模拟一段时间后再次尝试
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

企业微信截图_17167885451939.png

使用概述:

测试类中,首先设置了 Redisson 客户端并创建了 AdaptiveRateLimiter 的实例。然后,我们为不同的场景定义了测试用例:

- 场景1: 测试系统负载稳定低于30%时的行为。
- 场景2: 测试系统负载稳定超过80%时的行为。
- 场景3: 测试基于历史负载的策略在系统负载波动时的行为。

simulateLoadAndTest 方法模拟了一个稳定的系统负载,并多次尝试获取权限,以查看限流器的行为是否符合预期。simulateFluctuatingLoadAndTest 方法模拟了一个波动的系统负载,同样多次尝试获取权限。

这些测试用例仅供示例,实际上,你可能需要更详细的单元测试和集成测试来覆盖更多的边界情况和异常情况。此外,对于生产环境,你可能还需要模拟更复杂的系统负载模式,并确保 AdaptiveRateLimiter能够在高并发条件下正常工作。

总结:

在实际使用中,AdaptiveRateLimiter 应该根据具体的业务场景和性能指标进行定制和优化。需要注意的是,任何限流策略都应该平衡好服务的可用性和系统的稳定性,避免因为过度限流而影响用户体验,或因为限流不足而导致系统过载。

基于 Redisson 的自适应限流器提供了一种灵活、可扩展的限流机制,特别适合于需要高度可靠性和跨多个服务实例共享限流状态的分布式系统。然而,它也带来了一定的复杂性和依赖性,需要仔细的设计和维护