Spring cloud 限流的多種方式
在頻繁的網(wǎng)絡(luò)請求時,服務(wù)有時候也會受到很大的壓力,尤其是那種網(wǎng)絡(luò)攻擊,非法的。這樣的情形有時候需要作一些限制。例如:限制對方的請求,這種限制可以有幾個依據(jù):請求IP、用戶唯一標識、請求的接口地址等等。
當前限流的方式也很多:Spring cloud 中在網(wǎng)關(guān)本身自帶限流的一些功能,基于 redis 來做的。同時,阿里也開源了一款:限流神器 Sentinel。今天我們主要圍繞這兩塊來實戰(zhàn)微服務(wù)的限流機制。
首先講 Spring cloud 原生的限流功能,因為限流可以是對每個服務(wù)進行限流,也可以對于網(wǎng)關(guān)統(tǒng)一作限流處理。
一、實戰(zhàn)基于 Spring cloud Gateway 的限流
pom.xml引入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
其基礎(chǔ)是基于redis,所以:
spring:
application:
name: gateway-service
redis: #redis相關(guān)配置
database: 8
host: 10.12.15.5
port: 6379
password: 123456 #有密碼時設(shè)置
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相關(guān)配置
database: 8
host: 10.12.15.5
port: 6379
password: 123456 #有密碼時設(shè)置
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 10000ms
后面是限流的主要配置:
spring
cloud:
gateway:
routes: #路由配置:參數(shù)為一個List
- id: cas-server #唯一標識
uri: lb://cas-server-service #轉(zhuǎn)發(fā)的地址,寫服務(wù)名稱
order: -1
predicates:
- Path=/cas-server/** #判斷匹配條件,即地址帶有/ribbon/**的請求,會轉(zhuǎn)發(fā)至lb:cas-server-service
filters:
- StripPrefix=1 #去掉Path前綴,參數(shù)為1代表去掉/ribbon
- name: RequestRateLimiter #基于redis的Gateway的自身限流
args:
redis-rate-limiter.replenishRate: 1 # 允許用戶每秒處理多少個請求
redis-rate-limiter.burstCapacity: 3 # 令牌桶的容量,允許在一秒鐘內(nèi)完成的最大請求數(shù)
key-resolver: "#{@ipKeyResolver}" #SPEL表達式取的對應(yīng)的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 # 令牌桶的容量,允許在一秒鐘內(nèi)完成的最大請求數(shù)
key-resolver: "#{@ipKeyResolver}" #SPEL表達式取的對應(yīng)的bean
這里是在原有的路由基礎(chǔ)上加入 RequestRateLimiter限流過濾器,包括三個參數(shù):
- name: RequestRateLimiter #基于redis的Gateway的自身限流
args:
redis-rate-limiter.replenishRate: 3 #允許用戶每秒處理多少個請求
redis-rate-limiter.burstCapacity: 5 #令牌桶的容量,允許在一秒鐘內(nèi)完成的最大請求數(shù)
key-resolver: "#{@ipKeyResolver}" #SPEL表達式取的對應(yīng)的bean
- 其中 replenishRate,其含義表示允許每秒處理請求數(shù);
- burstCapacity 表示允許在一秒內(nèi)處理的最大請求數(shù);
- key-resolver 這里采用請求 IP 限流,利用SPEL 表達式取對應(yīng)的 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個服務(wù)進行壓測,打印結(jié)果:
{"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在同一秒內(nèi)多次請求后:
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
從上面可以看到,執(zhí)行后,會出現(xiàn)調(diào)用失敗的情況,狀態(tài)變?yōu)?29 (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應(yīng)用端和控制臺通信端口
heartbeat-interval-ms: 500 #心跳時間
scg:
fallback: #scg.fallback為sentinel限流后的響應(yīng)配置
mode: response
response-status: 455
response-body: 已被限流
其中,這里面配置了一個服務(wù):spring.cloud.sentinel.transport.dashboard,配置的是 sentinel 的 Dashboard 地址。同時 spring.cloud.sentinel.transport.port 這個端口配置會在應(yīng)用對應(yīng)的機器上啟動一個Http Server,該 Server 會與 Sentinel 控制臺做交互。
Sentinel 默認為所有的 HTTP 服務(wù)提供限流埋點,上面配置完成后自動完成所有埋點,只需要控制配置限流規(guī)則即可。
這里我們講下通過注解來給指定接口函數(shù)加上限流埋點,寫一個RestController,在接口函數(shù)上加上注解
@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 看到相應(yīng)數(shù)據(jù),這里不展示了。接下來可以設(shè)置接口的限流功能,在 “+流控” 按鈕點擊打開設(shè)置界面,設(shè)置閾值類型為 qps,單機閾值為5。
瀏覽器重復請求 http://10.10.15.5:5556/admin-web/api/user/getToken 如果超過閥值就會出現(xiàn)如下界面信息:
Blocked by Sentinel (flow limiting)
此時,就看到Sentinel 限流起作用了,可以加上 spring.cloud.sentinel.scg.fallback 為sentinel 限流后的響應(yīng)配置,亦可自定義限流異常信息:
@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中對應(yīng)的異常處理方法名,參數(shù)類型和返回值必須和原方法一致;
- blockHandlerClass:自定義限流邏輯處理類
Sentinel 限流邏輯處理完畢了,但每次服務(wù)重啟后,之前配置的限流規(guī)則就會被清空。因為是內(nèi)存形式的規(guī)則對象。所以下面就講下用 Sentinel 的一個特性 ReadableDataSource 獲取文件、數(shù)據(jù)庫或者配置中心設(shè)置限流規(guī)則,目前支持 Apollo、Nacos、ZK 配置來管理。
首先回憶一下,一條限流規(guī)則主要由下面幾個因素組成:
- resource:資源名,即限流規(guī)則的作用對象,即為注解 @SentinelResource 的value;
- count:限流閾值;grade:限流閾值類型(QPS 或并發(fā)線程數(shù));
- limitApp:流控針對的調(diào)用來源,若為 default 則不區(qū)分調(diào)用來源;
- strategy:基于調(diào)用關(guān)系的限流策略;
- controlBehavior:流量控制效果(直接拒絕、排隊等待、勻速器模式)
理解了意思,接下來通過文件來配置:
#通過文件讀取限流規(guī)則 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 添加限流規(guī)則:
[
{
"resource": "getToken",
"count": 1,
"controlBehavior": 0,
"grade": 1,
"limitApp": "default",
"strategy": 0
},
{
"resource": "resource",
"count": 1,
"controlBehavior": 0,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
重新啟動項目,出現(xiàn)如下日志說明成功:
DataSource ds1-sentinel-file-datasource start to loadConfig DataSource ds1-sentinel-file-datasource load 2 FlowRule
如果采用 Nacos 作為配置獲取限流規(guī)則,可在文件中加如下配置:
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
到此這篇關(guān)于Spring cloud 限流的多種方式的文章就介紹到這了,更多相關(guān)Spring cloud 限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解lombok @Getter @Setter 使用注意事項
這篇文章主要介紹了詳解lombok @Getter @Setter 使用注意事項,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
java連接zookeeper實現(xiàn)zookeeper教程
這篇文章主要介紹了java連接zookeeper實現(xiàn)zookeeper教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java利用反射動態(tài)設(shè)置對象字段值的實現(xiàn)
橋梁信息維護需要做到字段級別的權(quán)限控制,本文主要介紹了Java利用反射動態(tài)設(shè)置對象字段值的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-01-01
Java?String源碼contains題解重復疊加字符串匹配
這篇文章主要為大家介紹了Java?String源碼contains題解重復疊加字符串匹配示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11

