目录
Spring Cloud Gateway 学习专栏
1. Spring Cloud : Gateway 服务网关认识(一)
2. Spring Cloud :整合Gateway 学习 (二)
3. Spring Cloud:Gateway 路由定义定位器 RouteDefinitionLocator (三)
4. Spring Cloud : Gateway 网关过滤器 GatewayFilter(四)
5. Spring Cloud : Gateway 网关限流(五)
6. Spring Cloud:Gateway 集成 Sentinel (六)
7. Spring Cloud : Gateway Redis动态路由 (七)
8. Spring Cloud : Gateway 整合Swagger (八)
如果发现本文有错误的地方,请大家毫不吝啬,多多指教,欢迎大家评论,谢谢!
一、概述
网关限流,顾名思义,限流就是限制流量,就像你手机卡的流量包总共有 20 个G,流量用完了就没有了,通过手机限流,我们可以限制我们超额使用流量,实际在我们项目中通过限流可以很好控制系统的 QPS,从而达到保护系统的目的。
1. 为什么需要限流
比如 Web 服务,对外 Api,这种类型的服务有以下几种可能导致机器被拖垮;
- 用户增长过快 (好事)
- 因为某个热点事件 (微博热搜)
- 竞争对象爬虫
- 恶意请求
这些情况都是无法预知的,不知道什么时候会有 10 倍甚至 20 的流量访问,如果真碰上这种情况,扩容是根本来不及的。
二、限流算法
常见的限流算法有:
- 计数器算法
- 漏桶 (Leaky Bucket)算法
- 令牌桶 (Token Bucket)算法
1. 计数器算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于 A 接口来说,我们 1 分钟的访问次数不能超过 100 个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器 counter ,每当一个请求过来的时候,counter 就加 1,如果 counter 的值大于 100,并且该请求与第一个请求的间隔时间还在 1 分钟之内,触发限流;如果该请求与第一个请求间隔时间大于 1 分钟,重置 counter 重新计数,具体算法的示意图如下:
这个算法虽然简单,但是有一个致命的问题,我们看下图:
从上图中我们可以看到,假设有一个恶意用户,他在 0:59 时,瞬间发送了 100 个请求,并且 1:00 又瞬间发送了 100 个请求,那么其实这个用户在 1 秒里面,瞬间发送 200 个请求。我们刚才规定是 1 分钟最多 100 个请求,也就是每秒钟最多 100 个请求,用户通过在时间窗口的重置节点突发请求,可以瞬间超过我们的速率限制。用户可能通过算法的这个漏洞,瞬间压垮我们服务。
还存在资源浪费的问题,我们的预期是希望 100 个请求可以均匀分散在这一分钟内,假设 30s 以内我们请求上限了,那么剩余的半分钟服务器就会处于闲置状态,比如下图:
2. 漏桶算法
漏桶算法其实也很简单,可以粗略的认为就是注水漏水的过程,往桶中任意速率流入水,以一定速率流出水,当水超过桶流量则丢弃,因为桶的容量是不变的,保证了整体的速率。
其实从字面就很好理解. 类似生活用到的漏斗, 当客户端请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变.
当水流入速度过大时, 漏斗就会溢出, 同样会造成服务拒绝. 相对于计数器
的在恢复期内全部拒绝请求, 因为漏斗桶会以一定的速率消费请求, 这样就能够让后续的请求有机会进入到漏斗桶里面.
漏斗桶的弊端
由于漏斗桶有点类似队列, 先进去才能被消费掉, 如果漏斗桶溢出了, 后续的请求都直接丢弃了, 也就是说漏斗桶是无法短时间应对突发流量的. 对于互联网行业来说, 面对突发流量, 不能一刀切将突发流量全部干掉, 这样会给产品带来口碑上影响. 因此漏斗桶也不是完美的方案.
不过漏斗桶能够限制数据的平均传输速率, 能够满足大部分的使用场景的. 如: 我们可以使用漏斗桶限制论坛发帖频率
3. 令牌桶算法
令牌桶算法是对漏桶算法的一种改进,漏桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,已固定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数量达到上限,就丢弃令牌。
应用场景
桶中一直有大量的可用令牌,这时进来的请求可以直接拿到令牌执行,比如设置 QPS 为 100/s,那么限流器初始化完成一秒后,桶中就已经有 100 个令牌,等服务启动完成对外提供服务时,该限流器可以抵挡瞬时 100 个请求。当桶中没有令牌时,请求会进行等待,最后相当于以一定速率执行。
下面就是一个简单的示意图:
实现关键点:
- 初始化固定数量的令牌放入令牌桶中初始化和开启一个定时的任务;
- 定时往令牌桶添加令牌提供一个获取令牌的方法;
- 获取一个令牌,令牌桶中减一,如果令牌桶中为空,返回失败;
四、Gateway 限流
Spring Cloud Gateway 官方提供了 RequestRateLimiterGatewayFilterFactory
过滤器工厂,使用 Redis
和 Lua
脚本实现了令牌桶的方式。
官网文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-redis-ratelimiter 具体实现逻辑在 RequestRateLimiterGatewayFilterFactory
类中,Lua
脚本在如下图所示的源码文件夹中:
1. 添加依赖
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.1</version>
</dependency>
2. 配置文件
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 断言,路径相匹配的进行路由 (谓词)
- Path=/user/** # 路径匹配
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 以恒定速率存入令牌
redis-rate-limiter.burstCapacity: 2 # 桶的总容量
# key-resolver: "#{@pathKeyResolver}" #这个必须要配置,否则返回403 pathKeyResolver
# key-resolver: "#{@paramsKeyResolver}" #这个必须要配置,否则返回403 paramsKeyReslver
key-resolver: "#{@remoteAddrKeyResolver}" #这个必须要配置,否则返回403 paramsKeyResolver
# 将 /user/user/getUserInfo?userId=1 重写为 /user/getUserInfo?userId=1
- RewritePath=/user/(?<segment>.*),/$\{segment}
3. 限流规则配置类
/**
* 路由限流配置
*/
@Configuration
public class KeyResolverConfig {
@Bean(value = "remoteAddrKeyResolver")
public KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
// @Bean(value = "pathKeyResolver")
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
// @Bean(value = "paramsKeyResolver")
public KeyResolver paramsKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
}
多次访问:http://127.0.0.1:9000/user/user/getUserInfo?userId=1
Redis 结果如下:
源码地址
mall-gateway 这个项目
https://gitee.com/gaibianzlp/zlp-mall-demo.git
参考链接
2. Spring Cloud 系列之 Gateway 服务网关-限流