Spring cloud 限流的多种方式
Posted Java程序员老张
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring cloud 限流的多种方式相关的知识,希望对你有一定的参考价值。
在频繁的网络请求时,服务有时候也会受到很大的压力,尤其是那种网络攻击,非法的。这样的情形有时候需要作一些限制。本文主要介绍了两种限流方法,感兴趣的可以了解一下
目录
在频繁的网络请求时,服务有时候也会受到很大的压力,尤其是那种网络攻击,非法的。这样的情形有时候需要作一些限制。例如:限制对方的请求,这种限制可以有几个依据:请求IP、用户唯一标识、请求的接口地址等等。
当前限流的方式也很多:Spring cloud 中在网关本身自带限流的一些功能,基于 redis 来做的。同时,阿里也开源了一款:限流神器 Sentinel。今天我们主要围绕这两块来实战微服务的限流机制。
首先讲 Spring cloud 原生的限流功能,因为限流可以是对每个服务进行限流,也可以对于网关统一作限流处理。
一、实战基于 Spring cloud Gateway 的限流
pom.xml引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
其基础是基于redis,所以:
spring:
application:
name: gateway-service
redis: #redis相关配置
database: 8
host: 10.12.15.5
port: 6379
password: 123456 #有密码时设置
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 10000ms
接下来需要注入限流策略的 bean:
@Primary
@Bean(value = "ipKeyResolver")
KeyResolver ipKeyResolver()
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
//return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
//return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
@Bean(value = "apiKeyResolver")
KeyResolver apiKeyResolver()
return exchange -> Mono.just(exchange.getRequest().getPath().value());
@Bean(value = "userKeyResolver")
KeyResolver userKeyResolver()
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
这里引入ipKeyResolver、apiKeyResolver、userKeyResolver三种策略,可以利用注解 @Primary 来决定其中一个被使用。
注入bean后,需要在配置中备用:
spring:
application:
name: gateway-service
redis: #redis相关配置
database: 8
host: 10.12.15.5
port: 6379
password: 123456 #有密码时设置
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 10000ms
后面是限流的主要配置:
spring
cloud:
gateway:
routes: #路由配置:参数为一个List
- id: cas-server #唯一标识
uri: lb://cas-server-service #转发的地址,写服务名称
order: -1
predicates:
- Path=/cas-server/** #判断匹配条件,即地址带有/ribbon/**的请求,会转发至lb:cas-server-service
filters:
- StripPrefix=1 #去掉Path前缀,参数为1代表去掉/ribbon
- name: RequestRateLimiter #基于redis的Gateway的自身限流
args:
redis-rate-limiter.replenishRate: 1 # 允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 3 # 令牌桶的容量,允许在一秒钟内完成的最大请求数
key-resolver: "#@ipKeyResolver" #SPEL表达式取的对应的bean
- id: admin-web
uri: lb://admin-web-service
order: -1
predicates:
- Path=/admin-web/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 3 # 令牌桶的容量,允许在一秒钟内完成的最大请求数
key-resolver: "#@ipKeyResolver" #SPEL表达式取的对应的bean
这里是在原有的路由基础上加入 RequestRateLimiter限流过滤器,包括三个参数:
- name: RequestRateLimiter #基于redis的Gateway的自身限流
args:
redis-rate-limiter.replenishRate: 3 #允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 5 #令牌桶的容量,允许在一秒钟内完成的最大请求数
key-resolver: "#@ipKeyResolver" #SPEL表达式取的对应的bean
-
其中 replenishRate,其含义表示允许每秒处理请求数;
-
burstCapacity 表示允许在一秒内处理的最大请求数;
-
key-resolver 这里采用请求 IP 限流,利用SPEL 表达式取对应的 bean
写一个小脚本来压测一下:
for i in $(seq 1 30000); do echo $(expr $i \\\\* 3 + 1);curl -i -H "Accept: application/json" -H "Authorization:bearer b064d95b-af3f-4053-a980-377c63ab3413" -X GET http://10.10.15.5:5556/order-service/api/order/getUserInfo;done
for i in $(seq 1 30000); do echo $(expr $i \\\\* 3 + 1);curl -i -H "Accept: application/json" -H "Authorization:bearer b064d95b-af3f-4053-a980-377c63ab3413" -X GET http://10.10.15.5:5556/admin-web/api/user/getCurrentUser;done
上面两个脚本分别对2个服务进行压测,打印结果:
"message":"status":200,"code":0,"message":"success","data":"\\"message\\":\\"status\\":200,\\"code\\":0,\\"message\\":\\"get user success\\",\\"data\\":\\"id\\":23,\\"isAdmin\\":1,\\"userId\\":\\"fbb18810-e980-428c-932f-848f3b9e7c84\\",\\"userType\\":\\"super_admin\\",\\"username\\":\\"admin\\",\\"realName\\":\\"super_admin\\",\\"password\\":\\"$2a$10$89AqlYKlnsTpNmWcCMvgluRFQ/6MLK1k/nkBpz.Lw6Exh.WMQFH6W\\",\\"phone\\":null,\\"email\\":null,\\"createBy\\":\\"admin\\",\\"createTime\\":1573119753172,\\"updateBy\\":\\"admin\\",\\"updateTime\\":1573119753172,\\"loginTime\\":null,\\"expireTime\\":null,\\"remarks\\":\\"super_admin\\",\\"delFlag\\":0,\\"loginType\\":null"ex
在用测试工具Jmeter在同一秒内多次请求后:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Remaining: 0
X-RateLimit-Burst-Capacity: 3
X-RateLimit-Replenish-Rate: 1
content-length: 0
expr: syntax error
HTTP/1.1 429 Too Many Requests
X-RateLimit-Remaining: 0
X-RateLimit-Burst-Capacity: 3
X-RateLimit-Replenish-Rate: 1
content-length: 0
expr: syntax error
从上面可以看到,执行后,会出现调用失败的情况,状态变为429 (Too Many Requests) 。
二、基于阿里开源限流神器:Sentinel
首先引入依赖:
<!--基于 阿里的sentinel作限流 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
在配置文件 application.yaml 文件中配置,需要新增2个配置:
spring:
application:
name: admin-web
cloud:
kubernetes:
discovery:
all-namespaces: true
sentinel:
eager: true #取消Sentinel控制台的懒加载
transport:
dashboard: 10.12.15.2:8080 #sentinel的Dashboard地址
port: 8719 #是sentinel应用端和控制台通信端口
heartbeat-interval-ms: 500 #心跳时间
scg:
fallback: #scg.fallback为sentinel限流后的响应配置
mode: response
response-status: 455
response-body: 已被限流
其中,这里面配置了一个服务:spring.cloud.sentinel.transport.dashboard,配置的是 sentinel 的 Dashboard 地址。同时 spring.cloud.sentinel.transport.port 这个端口配置会在应用对应的机器上启动一个Http Server,该 Server 会与 Sentinel 控制台做交互。
Sentinel 默认为所有的 HTTP 服务提供限流埋点,上面配置完成后自动完成所有埋点,只需要控制配置限流规则即可。
这里我们讲下通过注解来给指定接口函数加上限流埋点,写一个RestController,在接口函数上加上注解
@SentinelResource:
@GetMapping(value = "/getToken")
@SentinelResource("getToken")
public Response<Object> getToken(Authentication authentication)
//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getCredentials();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
String token = details.getTokenValue();
return Response.ok(200, 0, "get token success", token);
以上代码部分完成了,接下来先安装SentinelDashBoard,Sentinel DashBoard下载地址:github.com/alibaba/Sentinel/releases。
下载完成后,命令启动:
java -jar sentinel-dashboard-1.6.2.jar
默认启动端口为8080,访问 IP:8080,就可以显示 Sentinel 的登录界面,用户名与密码均为sentinel。登录 Dashboard 成功后,多次访问接口"/getToken",可以在 Dashboard 看到相应数据,这里不展示了。接下来可以设置接口的限流功能,在 “+流控” 按钮点击打开设置界面,设置阈值类型为 qps,单机阈值为5。
浏览器重复请求 http://10.10.15.5:5556/admin-web/api/user/getToken 如果超过阀值就会出现如下界面信息:
Blocked by Sentinel (flow limiting)
此时,就看到Sentinel 限流起作用了,可以加上 spring.cloud.sentinel.scg.fallback 为sentinel 限流后的响应配置,亦可自定义限流异常信息:
@GetMapping(value = "/getToken")
@SentinelResource(value = "getToken", blockHandler = "handleSentinelException", blockHandlerClass = MySentinelException.class))
public Response<Object> getToken(Authentication authentication)
//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getCredentials();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
String token = details.getTokenValue();
return Response.ok(200, 0, "get token success", token);
public class MySentinelException
public static Response<Object> handleSentinelException(BlockException e)
Map<String,Object> map=new HashMap<>();
logger.info("Oops: " + ex.getClass().getCanonicalName());
return Response.ok(200, -8, "通过注解 @SentinelResource 配置限流埋点并自定义限流后的处理逻辑", null);
这里讲下注解 @SentinelResource 包含以下属性:
-
value:资源名称,必需项;
-
entryType:入口类型,可选项(默认为 EntryType.OUT);
-
blockHandler:blockHandlerClass中对应的异常处理方法名,参数类型和返回值必须和原方法一致;
-
blockHandlerClass:自定义限流逻辑处理类
Sentinel 限流逻辑处理完毕了,但每次服务重启后,之前配置的限流规则就会被清空。因为是内存形式的规则对象。所以下面就讲下用 Sentinel 的一个特性 ReadableDataSource 获取文件、数据库或者配置中心设置限流规则,目前支持 Apollo、Nacos、ZK 配置来管理。
首先回忆一下,一条限流规则主要由下面几个因素组成:
-
resource:资源名,即限流规则的作用对象,即为注解 @SentinelResource 的value;
-
count:限流阈值;grade:限流阈值类型(QPS 或并发线程数);
-
limitApp:流控针对的调用来源,若为 default 则不区分调用来源;
-
strategy:基于调用关系的限流策略;
-
controlBehavior:流量控制效果(直接拒绝、排队等待、匀速器模式)
理解了意思,接下来通过文件来配置:
#通过文件读取限流规则
spring.cloud.sentinel.datasource.ds1.file.file=classpath:flowrule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
在resources新建一个文件,比如 flowrule.json 添加限流规则:
[
"resource": "getToken",
"count": 1,
"controlBehavior": 0,
"grade": 1,
"limitApp": "default",
"strategy": 0
,
"resource": "resource",
"count": 1,
"controlBehavior": 0,
"grade": 1,
"limitApp": "default",
"strategy": 0
]
重新启动项目,出现如下日志说明成功:
DataSource ds1-sentinel-file-datasource start to loadConfig
DataSource ds1-sentinel-file-datasource load 2 FlowRule
如果采用 Nacos 作为配置获取限流规则,可在文件中加如下配置:
spring:
application:
name: order-service
cloud:
nacos:
config:
server-addr: 10.10.15.5:8848
discovery:
server-addr: 10.10.15.5:8848
sentinel:
eager: true
transport:
dashboard: 10.10.15.5:8080
datasource:
ds1:
nacos:
server-addr: 10.10.15.5:8848
dataId: $spring.application.name-flow-rules
data-type: json
rule-type: flow
以上是关于Spring cloud 限流的多种方式的主要内容,如果未能解决你的问题,请参考以下文章