Soul网关源码分析-限流插件Resilience4j

269 阅读3分钟

简介

前面的文章对请求转发做过分析,也对数据同步做了一些分析,今天就来看看限流插件:resilience4j

示例运行

环境配置
启动下MySQL和redis

D:\Software\mysql-5.7.31-winx64\bin>mysqld --console
D:\geek\redis\Redis-x64-3.2.100>redis-server.exe redis.windows.conf

Soul-Admin启动及相关配置

  • 运行Soul-admin,进入管理界面:系统管理 --> 插件管理 --> resilience4j ,点击编辑,把它开启;
  • 进入管理界面的插件列表:resilience4j 添加选择器和规则,这里安装divide插件的匹配方式配的,让divide的/http前缀的接口都走限流(因为使用测试时官方本身自带的HTTP测试)
  • 规则配置中:token filling number 要设置大于0,不然会报错
  • circuit enable 要设置为0,判断的时候走限流的逻辑
  • 其他的:fallback uri 随便填个路径,其他的参数都可填1

Soul-Bootstrap配置启动

在Soul-Bootstrap中进入相关的依赖,大致如下:

<!-- soul resilience4j plugin start-->
  <dependency>
      <groupId>org.dromara</groupId>
      <artifactId>soul-spring-boot-starter-plugin-resilience4j</artifactId>
       <version>${last.version}</version>
  </dependency>
  <!-- soul resilience4j plugin end-->

启动Soul-Bootstrap

HTTP示例启动

  • 启动:soul-examples --> soul-examples-http --> SoulTestHttpApplication
  • 进入管理界面的:插件列表 --> divide 能看到相关的注册接口信息
  • 访问: http://127.0.0.1:9195/http/order/findById?id=1111
  • 成功运行,下面开始源码debug

源码分析

根据前面的文章,对处理流程基本上有个清晰的认识了,我们通过前面的调试,知道 RateLimiterPlugin 是继承 AbstractSoulPlugin ,那它就会走和路由匹配相关的逻辑,匹配成功后才走 doExcute 限流逻辑,如下代码所示:

    # AbstractSoulPlugin
    // 首先进行路由匹配
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        String pluginName = named();
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        if (pluginData != null && pluginData.getEnabled()) {
            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
            if (CollectionUtils.isEmpty(selectors)) {
                return CheckUtils.checkSelector(pluginName, exchange, chain);
            }
            final SelectorData selectorData = matchSelector(exchange, selectors);
            if (Objects.isNull(selectorData)) {
                if (PluginEnum.WAF.getName().equals(pluginName)) {
                    return doExecute(exchange, chain, null, null);
                }
                return CheckUtils.checkSelector(pluginName, exchange, chain);
            }
            if (selectorData.getLoged()) {
                log.info("{} selector success match , selector name :{}", pluginName, selectorData.getName());
            }
            final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            if (CollectionUtils.isEmpty(rules)) {
                if (PluginEnum.WAF.getName().equals(pluginName)) {
                    return doExecute(exchange, chain, null, null);
                }
                return CheckUtils.checkRule(pluginName, exchange, chain);
            }
            RuleData rule;
            if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
                //get last
                rule = rules.get(rules.size() - 1);
            } else {
                rule = matchRule(exchange, rules);
            }
            if (Objects.isNull(rule)) {
                return CheckUtils.checkRule(pluginName, exchange, chain);
            }
            if (rule.getLoged()) {
                log.info("{} rule success match ,rule name :{}", pluginName, rule.getName());
            }
            return doExecute(exchange, chain, selectorData, rule);
        }
        return chain.execute(exchange);
    }

    # RateLimiterPlugin
    // 匹配完成后走限流的逻辑
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        // 这里更加字符串转成对象,所有规则哪里不能乱填
        Resilience4JHandle resilience4JHandle = GsonUtils.getGson().fromJson(rule.getHandle(), Resilience4JHandle.class);
        // 这里判断 Circle enable 是否为1 走 combined的逻辑,但我们这次想走 limit 的逻辑,所以要填0
        if (resilience4JHandle.getCircuitEnable() == 1) {
            return combined(exchange, chain, rule);
        }
        return rateLimiter(exchange, chain, rule);
    }

    private Mono<Void> rateLimiter(final ServerWebExchange exchange, final SoulPluginChain chain, final RuleData rule) {
        return ratelimiterExecutor.run(
                chain.execute(exchange), fallback(ratelimiterExecutor, exchange, null), Resilience4JBuilder.build(rule))
                .onErrorResume(throwable -> ratelimiterExecutor.withoutFallback(exchange, throwable));
    }

继续跟到RateLimiterExecutor这个类,如下:

public class RateLimiterExecutor implements Executor {

    @Override
    public <T> Mono<T> run(final Mono<T> toRun, final Function<Throwable, Mono<T>> fallback, final Resilience4JConf conf) {
        // 生成限流器
        RateLimiter rateLimiter = Resilience4JRegistryFactory.rateLimiter(conf.getId(), conf.getRateLimiterConfig());
        // 应该是在这触发的限流逻辑
        Mono<T> to = toRun.transformDeferred(RateLimiterOperator.of(rateLimiter));
        if (fallback != null) {
            return to.onErrorResume(fallback);
        }
        return to;
    }
}

我们看到了明显的生成限流器的逻辑,我们查看它的具体实现,发现是一个接口,我们看看它有哪些实现,发现有两个,分别是: SemaphoreBasedRateLimiter 和 AtomicRateLimiter,因为不知道用的哪个,我们在这两个类中可能会执行的函数都给打上断点,重启发送请求,不断的跳断点,终于进入了一个限流器的类: AtomicRateLimiter ,大致如下:

    # AtomicRateLimiter
    public long reservePermission(final int permits) {
        long timeoutInNanos = ((AtomicRateLimiter.State)this.state.get()).config.getTimeoutDuration().toNanos();
        AtomicRateLimiter.State modifiedState = this.updateStateWithBackOff(permits, timeoutInNanos);
        boolean canAcquireImmediately = modifiedState.nanosToWait <= 0L;
        if (canAcquireImmediately) {
            this.publishRateLimiterEvent(true, permits);
            return 0L;
        } else {
            boolean canAcquireInTime = timeoutInNanos >= modifiedState.nanosToWait;
            if (canAcquireInTime) {
                this.publishRateLimiterEvent(true, permits);
                return modifiedState.nanosToWait;
            } else {
                this.publishRateLimiterEvent(false, permits);
                return -1L;
            }
        }
    }

在SoulWebHandler 打上断点,看看限流器的执行顺序是什么样的,通过debug,我们发现顺序和我们预期的基本一致:在进入 RateLimiterPlugin 插件执行的时候,执行的断点也到了限流器(AtomicRateLimiter),等限流器逻辑执行完毕,divide等插件才开始执行。

总结

本次文章大致探索了限流插件:resilience4j的使用配置。调试验证它的限流逻辑执行在plugin链中执行顺序,发现基本符合我们的猜想,限流逻辑的执行和plugin顺序一致。