分布式系统常会提及限流与降级的概念。所谓限流就是限制系统的输入流量,以达到保护系统的目的。
一. 限流算法综述
限流的实现主要依靠限流算法,主要有:
固定时间窗口算法
滑动时间窗口算法
令牌桶算法
漏桶算法
1. 固定时间窗口算法
固定时间窗口算法也叫计数器算法,是限流算法里比较容易实现的算法。该算法指在固定的时间窗口内统计请求量,请求量超过规定的最大次数就拒绝处理或降级处理等。
时间窗口如果为1分钟,计算策略如下:

算法特点:
实现简单
时间窗口固定,每个窗口开始时计数清零,这样后面的请求不受之前的影响,做到了前后请求的隔离;
因为两个窗口之间没有任何联系,所以调用者可以在一个时间窗口的结束到下一个时间窗口开始这个非常短的时间内发起两倍于阈值的请求。所以固定时间窗口算法无法限制窗口间突发流量。
2. 滑动时间窗口算法
滑动时间窗口其实是固定时间窗口算法的优化,主要为解决固定时间窗口算法无法限制窗口间突发流量的缺点。其计算时,将时间分成大小相同的格子,每个格子统计各自的流量。选取固定数量的格子进行统计,并以格子为单位移动窗口,窗口内流量不得大于设定值。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
算法特点
因为窗口顺延,所以可以抵御窗口间突发流量(对比固定时间窗口算法)。
假如限流10万次/小时,如果某个调用者在前10分钟调用了10万次那么他必须再等待1小时才能发起下一次正常请求。所以没有做到前后请求隔离。
3. 漏桶算法(leaky bucket)
漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。这个从桶底流出去的水就是系统正常处理的请求,从旁边流出去的水就是系统拒绝掉的请求。

算法特点
因为流出的速度是一定的,可以抵御突发流量,做到更加平滑的限流。
4. 令牌桶算法(Token Bucket)
令牌桶算法是比较常见的限流算法之一,Google开源项目Guava中的RateLimiter使用的就是令牌桶算法。流程如下:
所有的请求在处理之前都需要拿到一个可用的令牌才会被处理。
根据限流大小,设置按照一定的速率往桶里添加令牌。
桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝。
请求到达后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除。

算法特点:
可以抵御突发流量,因为桶内的令牌数不会超过给定的最大值
可以做到更加平滑的限流,因为令牌是匀速放入的。
令牌桶算法允许流量一定程度的突发。(相比漏桶算法)
在时间点刷新的临界点上,只要剩余token足够,令牌桶算法会允许对应数量的请求通过,而后刷新时间因为token不足,流量也会被限制在外,这样就比较好的控制了瞬时流量。因此,令牌桶算法也被广泛使用。
二. gateway实现限流
1. 增加redis依赖
目前gateway令牌桶算法基于redis实现,故要引入redis依赖
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
4</dependency>
2. 配置限流键
1@Bean
2public KeyResolver ipKeyResolver() {
3 return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
4}
3. 配置令牌桶算法参数
1spring:
2 redis:
3 host: 127.0.0.1
4 port: 6379
5 cloud:
6 gateway:
7 routes:
8 - id: limiter
9 uri: lb://SERVICE-DEMO
10 predicates:
11 - Path=/demo/**
12 filters:
13 - name: RequestRateLimiter
14 args:
15 redis-rate-limiter.replenishRate: 10
16 redis-rate-limiter.burstCapacity: 20
17 key-resolver: "#{@ipKeyResolver}"
18
发送请求之后,redis中会有对应的令牌:

如果请求过多会返回429状态码:

Spring Cloud Gateway目前提供的限流还是相对比较简单的,在实际中我们的限流策略会有很多种情况,比如:
每个接口的限流数量不同,可以通过配置中心动态调整
超过的流量被拒绝后可以返回固定的格式给调用方
对某个服务进行整体限流(这个大家可以思考下用Spring Cloud Gateway如何实现,其实很简单)
......
针对这种情况,我们也可以通过重新RedisRateLimiter来实现自己的限流策略。
参考资料




