官网地址:https://resilience4j.readme.io/docs/ratelimiter
中文文档:https://resilience4j.readme.io/docs/ratelimiter
【1】概述
Resilience4j提供了一个限流器,它将从epoch开始的所有纳秒划分为多个周期。每个周期的持续时间RateLimiterConfig.limitRefreshPeriod。在每个周期开始时,限流器将活动权限数设置为RateLimiterConfig.limitForPeriod。期间, 对于限流器的调用者,它看起来确实是这样的,但是对于AtomicRateLimiter实现,如果RateLimiter未被经常使用,则会在后台进行一些优化,这些优化将跳过此刷新。
限流器的默认实现是AtomicRateLimiter,它通过原子引用管理其状态。这个AtomicRateLimiter状态完全不可变,并且具有以下字段:
- activeCycle -上次调用的周期号
- activePermissions -在上次调用结束后,可用的活跃权限数。如果保留了某些权限,则可以为负。
- nanosToWait - 最后一次调用要等待的纳秒数
还有一个使用信号量的SemaphoreBasedRateLimiter和一个调度程序,它将在每个RateLimiterConfig#limitRefreshPeriod之后刷新活动权限数。
【2】常见限流算法
① 漏斗算法(Leaky Bucket)
一个固定容量的漏桶,按照设定常量固定速率流出水滴。如果流入水滴超出了桶的容量,则流入的水滴将会溢出了(被丢弃),而漏桶容量是不变的。
② 令牌桶算法(Token Bucket)
令牌桶算法(Token Bucket Algorithm)是一种常用的流量控制算法,主要用于平滑网络数据传输或限制应用程序的请求速率。它的工作原理基于两个核心概念:令牌和桶。
工作原理:
-
令牌生成:
- 令牌以恒定的速率被生成并存入桶中。
- 桶的容量是有限的,一旦达到上限,多余的令牌会被丢弃。
-
请求处理:
- 当一个请求到来时,它需要从桶中消耗一个令牌才能通过。
- 如果桶中没有足够的令牌,那么请求可能被延迟、排队或直接拒绝。
-
突发处理:
- 由于桶可以存储一定量的令牌,因此可以允许一定程度的突发流量。
- 这意味着在短时间内,可以处理超过平均速率的请求,直到桶中的令牌耗尽。
关键参数:
- 填充速率 ®:单位时间内生成的令牌数,决定了数据流的长期平均速率。
- 桶的容量 (B):桶能够持有的令牌的最大数量,影响了可以处理的突发流量的大小。
应用场景:
- 网络流量控制:在网络设备上,如路由器,使用令牌桶算法来平滑网络流量,防止拥塞。
- API限流:在Web服务中,用于限制客户端对API的访问速率,防止过载或滥用。
与漏桶算法的区别:
- 漏桶算法:请求进入一个固定容量的桶,然后以恒定的速率流出,溢出的请求被丢弃。这更侧重于平滑突发流量,但不支持突发处理。
- 令牌桶算法:请求必须等待直到有足够的令牌可用,支持突发流量处理,但需要维护一个额外的数据结构(桶)。
令牌桶算法因其灵活性和效率,在许多领域得到了广泛应用。
③ 滚动时间窗(tumbling time window)
允许固定数量的请求进入超过数量就拒绝或者排队,等下一个时间段进入。
由于是在一个时间间隔内进行限制,如果用户在上个时间间隔结束前请求(但没有超过限制),同时在当前时间间隔刚开始请求(同样没超过限制),在各自的时间间隔内,这些请求都是正常的。
缺点:间隔临界的一段时间内的请求就会超过系统限制,可能导致系统被压垮
假如设定1分钟最多可以请求100次某个接口,如12:00:00-12:00:59时间段内没有数据请求但12:00:59-12:01:00时间段内突然并发100次请求,紧接着瞬间跨入下一个计数周期计数器清零;在12:01:00-12:01:01内又有100次请求。那么也就是说在时间临界点左右可能同时有2倍的峰值进行请求,从而造成后台处理请求加倍过载的bug,导致系统运营能力不足,甚至导致系统崩溃,/(ㄒoㄒ)/~~
④ 滑动时间窗口(sliding time window)
顾名思义,该时间窗口是滑动的。所以,从概念上讲,这里有两个方面的概念需要理解:
-
窗口:需要定义窗口的大小
-
滑动:需要定义在窗口中滑动的大小,但理论上讲滑动的大小不能超过窗口大小
滑动窗口算法是把固定时间片进行划分并且随着时间移动,移动方式为开始时间点变为时间列表中的第2个时间点,结束时间点增加一个时间点,
不断重复,通过这种方式可以巧妙的避开计数器的临界点的问题。下图统计了5次
【3】RateLimiter实践
① pom依赖
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
<!--resilience4j-circuitbreaker-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
② yml文件
如下实例为每秒2次请求配置:
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
timeout-duration: 1 # 线程等待权限的默认等待时间
instances:
cloud-payment-service:
baseConfig: default
③ controller
消费侧实例测试代码如下:使用@RateLimiter注解。
@GetMapping(value = "/feign/pay/ratelimit/{id}")
@RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")
public String myBulkhead(@PathVariable("id") Integer id)
{
return payFeignApi.myRatelimit(id);
}
public String myRatelimitFallback(Integer id,Throwable t)
{
return "你被限流了,禁止访问/(ㄒoㄒ)/~~";
}
这样,当并发>2QPS时,将会提示你被限流了,禁止访问
。