soul 网关(十六):RateLimiter 插件解析

前言

上文粗略的讲了限流和熔断的能解决什么问题,使用场景是什么。本文主要讲述 RateLimiter 插件的配置使用和解析,以单体 redis 为例进行讲解。 根据官网文档来看, RateLimiter 是基于令牌桶实现的。

操作流程

配置

1、 进入 admin 控制台,在菜单 "System Manage => Plugin" 中配置 rate_limiter。操作如下图所示:

操作步骤

这里 redis 的配置是需要注意的,当这里配置错误时,或者 redis 没有启动时,虽然会匹配到 RateLimiter 插件,但实际是不起作用的。

  1. 增加 Seletor 和 Rule, 步骤如下图所示:
    增加 Seletor:


    增加seletor

增加 Rule


增加Rule

Handler 中有两个参数:

  • capacity: 令牌桶的容量。
  • rate: 令牌的刷新速率,即每秒产生多少个令牌。

验证配置

  1. 启动 soul-examples 中的 soul-examples-http
  2. 启动 postman, 利用 runner 查看请求情况。
    增加测试


    增加测试

启动 runner


输入连接
  1. 设置并发数 (Iterations)为 50,


    测试结果

源码解析

admin 数据发生变更

  1. 当插件信息发生变更时,BootStrap 中接口 PluginDataSubscriber 有订阅到了信息的变更,它的实现 CommonPluginDataSubscriber, 由该类中 subscribeDataHandler 的方法,进入到 RateLimiterPluginDataHandler 逻辑中。
 private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
        Optional.ofNullable(classData).ifPresent(data -> {
            if (data instanceof PluginData) {
                PluginData pluginData = (PluginData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cachePluginData(pluginData);
                // pluginData.getName()  == rate_limite
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
                }
                ... 
            }
            ...
        });
    }
  1. 进入 RateLimiterPluginDataHandler 的 handlerPlugin 中,看到 createLettuceConnectionFactory 方法,进入该方法,发现这里是根据之前 admin 的配置生成相应的 LettuceConnectionFactory 的连接池,由于之前是 单机模式, 则这里就采用了 redisStandaloneConfiguration
 public void handlerPlugin(final PluginData pluginData) {
        if (Objects.nonNull(pluginData) && pluginData.getEnabled()) {
             ...
            //spring data redisTemplate
            if (Objects.isNull(Singleton.INST.get(ReactiveRedisTemplate.class))
                    || Objects.isNull(Singleton.INST.get(RateLimiterConfig.class))
                    || !rateLimiterConfig.equals(Singleton.INST.get(RateLimiterConfig.class))) {
                LettuceConnectionFactory lettuceConnectionFactory = createLettuceConnectionFactory(rateLimiterConfig);
...
                //  创建 reactiveRedisTemplate, 模板中加载了 redis 的 lua 脚本
                ReactiveRedisTemplate<String, String> reactiveRedisTemplate = new SoulReactiveRedisTemplate<>(lettuceConnectionFactory, serializationContext);
                Singleton.INST.single(ReactiveRedisTemplate.class, reactiveRedisTemplate);
                Singleton.INST.single(RateLimiterConfig.class, rateLimiterConfig);
            }
        }
    }

访问相应链接

  1. 当访问链接 localhost:9195/http/test/findByUserId?userId=1 时,从终端的日志看到
2021-02-04 17:26:23.341  INFO 12552 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin  : rate_limiter selector success match , selector name :测试1
2021-02-04 17:26:23.343  INFO 12552 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin  : rate_limiter rule success match , rule name :test22222

由此我们可知进入到了 RateLimiterPlugin 中。 查看 doExecute 方法中有 isAllowed 的判断,而 isAllowed 中利用 ReactiveRedisTemplate 执行 lua 脚本实现的令牌桶,来判断。

总结

  1. RateLimiter 利用了 Redis 单线程的处理线程,实现了令牌桶算法,保证数据的原子性。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容