前言
- 采用redis令牌桶算法进行限流。
- 限流插件是网关对流量管控、限制核心的实现。
- 流量配置可以到接口级别、参数级别。
使用rateLimiter
-
启动soul-admin,在
soul-admin
–> 插件管理–>rate_limiter
将其设置为开启,redis部署模式默认为单机,如果是哨兵(sentinel),集群(cluster)等多节点的,在URL中的配置,请对每个实列使用;
分割. 如 192.168.1.1:6379;192.168.1.2:6379。。
-
在boostrap的pom.xml引入ratelimiter依赖
<!-- soul ratelimiter plugin start--> <dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-plugin-ratelimiter</artifactId> <version>${project.version}</version> </dependency> <!-- soul ratelimiter plugin end-->
-
启动soul-examples-http 服务
-
在 admin 后台选择 rateLimiter 插件,添加选择器
- 添加规则,设置例子服务的url地址、桶容量为10和速率为5。
-
容量是令牌数,用户在一秒内执行的最大请求数。速率是令牌桶的填充速率,允许用户每秒执行多少请求,而丢弃任何请求。
-
测试接口,比如用postman测试 ,设置并发数50,延迟为0,确认调第11次开始,接口返回异常。
源码分析
-
类
RateLimiterPlugin
#doExecute
,判断请求是否允许通过,允许就执行下一个。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); }); }
-
redisRateLimiter
#redisScript
获取lua脚本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; }
-
用 lua 脚本来保证操作的原子性的,看脚本大概是最后返回一个allowed_num,1表示通过,0不通过。
-
会通过 lua 脚本在 redis 创建两个 key,通过1个请求减少一个容量,然后按照上面设置的rate填充速率(如:令牌桶10,速率是5),请求太快,就不允许通过了。