网站首页 文章专栏 详解rateLimiter插件,以及如何限流
很明显。该插件是限流用的,在网关上对流量进行管控限制,防止大量请求击垮业务,在soul中可以到接口级别,也可以到参数级别,具体需要在控制台进行配置。
1). 使用该插件,首先需要在 soul-admin控制台 –> 插件管理–> rate_limiter 将其设置为开启。
该插件依赖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插件信息,配置限流的规则
具体的方式如同其他插件一样,这里匹配uri 以 /http/开头的所有请求。
然后指定规则,可以具体到某个接口
速率:是你允许用户每秒执行多少请求,而丢弃任何请求。这是令牌桶的填充速率。
容量:是允许用户在一秒钟内执行的最大请求数。这是令牌桶可以保存的令牌数。
这里我都填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 }
此时,就已经被限流了。
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不太了解,这里就不分析了。。。
放张整体流程图吧
整体分析下来发现流程很简单,就是开启插件,配置规则,根据规则去redis判断是否限流,然后判断直接返回还是放行到下一插件。
版权声明:本文由星尘阁原创出品,转载请注明出处!