soul源码解读(十六)-- rateLimiter插件原理分析

17 篇文章 0 订阅

soul源码解读(十六)

官网的介绍和流程图如下:

  • 限流插件,是网关对流量管控限制核心的实现。
  • 可以到接口级别,也可以到参数级别,具体怎么用,还得看你对流量配置。

在这里插入图片描述
使用 rateLimiter 插件,我们需要安装 redis ,这里可以参考我之前写的文章Windows下部署redis主从、哨兵(sentinel)、集群(cluster)

使用rateLimiter

1.启动 admin,打开 rateLimiter 插件开关
在这里插入图片描述

2.在 bootstrap 项目的 pom 文件引入 rateLimiter 插件的相关依赖,启动 bootstrap

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

3.启动一个 http 服务,这里我们启动 soul-examples-http 服务

4.配置限流参数

在 admin 后台选择 rateLimiter 插件,添加选择器
在这里插入图片描述
添加规则
在这里插入图片描述

  • 容量:是允许用户在一秒钟内执行的最大请求数。这是令牌桶可以保存的令牌数。
  • 速率:是你允许用户每秒执行多少请求,而丢弃任何请求。这是令牌桶的填充速率。

5.测试接口

在 postman 新建测试用例,编写一个响应码=200的断言
在这里插入图片描述
点击测试用例右侧小箭头,点击 run
在这里插入图片描述
设置并发数为100,延迟为0
在这里插入图片描述
点击运行,可以看到在执行成10次请求后,接口返回异常了。
在这里插入图片描述

分析源码

接下来我们分下下 rateLimiter 插件是怎么实现限流的。

刚刚用 postman 测试接口之后,我们可以看到控制台有输出下面的日志

rate_limiter selector success match , selector name :http限流
rate_limiter rule success match , rule name :限流findById
RateLimiter response:Response{allowed=true, tokensRemaining=9}

我们找到日志输出的地方,发现是 AbstractSoulPlugin#execute。

根据前面分析过的源码,我们知道 soul 会遍历所有插件,如果插件不用跳过,就会执行这个函数。

这个函数又会判断插件有没有开启,有没有匹配的选择器和规则。

我们进到 RateLimiterPlugin.java 里去看下这个插件是怎么工作的。

// RateLimiterPlugin.java
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
    ... 
    // 这里会判断是否允许请求通过,允许就执行下一个插件逻辑,不允许就返回 Too Many Requests	
   	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);
            });
}

我们继续看下 isAllowed 函数

// RedisRateLimiter.java
public Mono<RateLimiterResponse> isAllowed(final String id, final double replenishRate, final double burstCapacity) {
	...
    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()));
}

// 组装keys
private static List<String> getKeys(final String id) {
  	String prefix = "request_rate_limiter.{" + id;
    String tokenKey = prefix + "}.tokens";
    String timestampKey = prefix + "}.timestamp";
    return Arrays.asList(tokenKey, timestampKey);
}
// 获取lua脚本
private RedisScript<List<Long>> redisScript() {
  	...
    redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/META-INF/scripts/request_rate_limiter.lua")));
    return redisScript;
}

可以看到,上面是用 lua 脚本来保证操作的原子性的。

具体 lua 脚本在 soul-plugin-ratelimiter 模块 /src/main/resource/META-INF/scripts/request_rate_limiter.lua

lua 脚本最后返回一个 Long 集合,第一个数用来标识是否允许请求通过 1 通过 0 不通过,第二个数表示剩余的容量。

我们执行一次请求,可以看到 redis 里新建了两个 key

127.0.0.1:6379> keys *
1) "request_rate_limiter.{1356226169225809920}.tokens"
2) "request_rate_limiter.{1356226169225809920}.timestamp"

rateLimiter 原理总结:

请求过来之后,会通过 lua 脚本在 redis 创建两个 key,通过一次请求就减少1个容量,同时会按照设定的
速率补充容量,去过请求太快,就返回不允许请求通过。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值