网站首页 文章专栏 详解rateLimiter插件,以及如何限流
详解rateLimiter插件,以及如何限流

一. rateLimiter插件是什么

很明显。该插件是限流用的,在网关上对流量进行管控限制,防止大量请求击垮业务,在soul中可以到接口级别,也可以到参数级别,具体需要在控制台进行配置。


二. rateLimiter体验

1). 使用该插件,首先需要在 soul-admin控制台 –> 插件管理–> rate_limiter 将其设置为开启。

image.png

该插件依赖redis实现限流,所以需要配置redis相关信息,目前支持redis的单机,哨兵,以及集群模式。

    1>. 如果是哨兵,集群等多节点的,在URL中的配置,请对每个实列使用 ; 分割. 如 192.168.1.1:6379;192.168.1.2:6379。

    2>. 如果用户无需使用,在admin后台把插件禁用。


2). 然后在网关的pom中加上依赖

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


3). 进入admin控制台的rateLimiter插件信息,配置限流的规则

image.png

具体的方式如同其他插件一样,这里匹配uri 以 /http/开头的所有请求。

image.png

image.png

然后指定规则,可以具体到某个接口

    速率:是你允许用户每秒执行多少请求,而丢弃任何请求。这是令牌桶的填充速率。

    容量:是允许用户在一秒钟内执行的最大请求数。这是令牌桶可以保存的令牌数。

这里我都填1,容易模拟并发场景,实际根据你的业务并发量来


看下效果实际:

业务请求为 “http://localhost:9195/http/order/findById?id=yangxing”,符合我们设置的限流路径,正常请求结果为

{
    "id": "yangxing",
    "name": "hello world findById"
}

当我们快速点击请求,模拟并发场景时会发现:

{
    "code": 429,
    "message": "You have been restricted, please try again later!",
    "data": null
}

此时,就已经被限流了。


四. rateLimiter怎么起作用的

1). 首先 RateLimiterPlugin 和其他插件一样,都继承与AbstractSoulPlugin,在插件链调用时,执行自己的doExecute方法,且RateLimiterPlugin 的排序比较靠前,能够起到及时的防护作用。

GLOBAL(1, 0, "global"),

SIGN(2, 0, "sign"),

WAF(10, 0, "waf"),

RATE_LIMITER(20, 0, "rate_limiter"),

CONTEXTPATH_MAPPING(25, 0, "context_path"),

REWRITE(30, 0, "rewrite"),

REDIRECT(40, 0, "redirect"),

HYSTRIX(45, 0, "hystrix"),

SENTINEL(45, 0, "sentinel"),

RESILIENCE4J(45, 0, "resilience4j"),

DIVIDE(50, 0, "divide"),

SPRING_CLOUD(50, 0, "springCloud"),

WEB_SOCKET(55, 0, "webSocket"),

...

2). 其在初始化是会创建一个 RedisRateLimiter,该类是redis限流的调用实现类

public RedisRateLimiter() {
    this.script = redisScript();
    initialized.compareAndSet(false, true);
}

private RedisScript<List<Long>> redisScript() {
    DefaultRedisScript redisScript = new DefaultRedisScript<>();
    redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/META-INF/scripts/request_rate_limiter.lua")));
    redisScript.setResultType(List.class);
    return redisScript;
}

初始化一个 RedisScript ,并从文件夹中,加载写好的lua脚本,通过lua脚本实现限流


3). 执行到RateLimiterPlugin 插件的doExecute方法时,会去获取限流的规则,并判断是否被限流,以及可用的请求数

@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
    final String handle = rule.getHandle();
    final RateLimiterHandle limiterHandle = GsonUtils.getInstance().fromJson(handle, RateLimiterHandle.class);
    return redisRateLimiter.isAllowed(rule.getId(), limiterHandle.getReplenishRate(), limiterHandle.getBurstCapacity())
            .flatMap(response -> {
                if (!response.isAllowed()) {
                    exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                    Object error = SoulResultWrap.error(SoulResultEnum.TOO_MANY_REQUESTS.getCode(), SoulResultEnum.TOO_MANY_REQUESTS.getMsg(), null);
                    return WebFluxResultUtils.result(exchange, error);
                }
                return chain.execute(exchange);
            });
}

如果允许,就放行,执行后续插件,如果不允许,则直接返回相应,提示被限流了。


4). 上面通过redisRateLimiter.isAllowed方判读是否被限流,那么具体怎么实现的呢

public Mono<RateLimiterResponse> isAllowed(final String id, final double replenishRate, final double burstCapacity) {
    if (!this.initialized.get()) {
        throw new IllegalStateException("RedisRateLimiter is not initialized");
    }
    List<String> keys = getKeys(id);
    List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", Instant.now().getEpochSecond() + "", "1");
    Flux<List<Long>> resultFlux = Singleton.INST.get(ReactiveRedisTemplate.class).execute(this.script, keys, scriptArgs);
    return resultFlux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
            .reduce(new ArrayList<Long>(), (longs, l) -> {
                longs.addAll(l);
                return longs;
            }).map(results -> {
                boolean allowed = results.get(0) == 1L;
                Long tokensLeft = results.get(1);
                RateLimiterResponse rateLimiterResponse = new RateLimiterResponse(allowed, tokensLeft);
                log.info("RateLimiter response:{}", rateLimiterResponse.toString());
                return rateLimiterResponse;
            }).doOnError(throwable -> log.error("Error determining if user allowed from redis:{}", throwable.getMessage()));
}

其实就是调用lua脚本,根据返回值判断的,重点就是lua脚本了,由于博主菜鸡对lua不太了解,这里就不分析了。。。

放张整体流程图吧

limiting.png



整体分析下来发现流程很简单,就是开启插件,配置规则,根据规则去redis判断是否限流,然后判断直接返回还是放行到下一插件。



版权声明:本文由星尘阁原创出品,转载请注明出处!

本文链接:http://www.52xingchen.cn/detail/70




赞助本站,网站的发展离不开你们的支持!
来说两句吧
大侠留个名吧,或者可以使用QQ登录。
: 您已登陆!可以继续留言。
最新评论