zuul集成Sentinel,完成對(duì)path映射的限流操作
zuul集成Sentinel完成對(duì)path映射的限流
前面我們講過(guò)了對(duì)單體應(yīng)用的Sentinel限流,以及使用zookeeper對(duì)規(guī)則的持久化。通過(guò)前面的工作,我們可以完成單個(gè)實(shí)例的細(xì)粒度的限流、熔斷操作。
譬如有一個(gè)服務(wù)User,在分布式環(huán)境下,開(kāi)啟了多個(gè)實(shí)例,那么每個(gè)實(shí)例都可以獲得如每秒限制10個(gè)登錄的限流功能。但是有些場(chǎng)景下,我們想要另外一種限流方式,譬如在網(wǎng)關(guān)zuul層,想限制對(duì)User服務(wù)的限流,而不去關(guān)心具體它有多少個(gè)實(shí)例。這時(shí),就需要用到網(wǎng)關(guān)限流了。
Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模塊,包含網(wǎng)關(guān)限流的規(guī)則和自定義 API 的實(shí)體和管理邏輯:
GatewayFlowRule:網(wǎng)關(guān)限流規(guī)則,針對(duì) API Gateway 的場(chǎng)景定制的限流規(guī)則,可以針對(duì)不同 route 或自定義的 API 分組進(jìn)行限流,支持針對(duì)請(qǐng)求中的參數(shù)、Header、來(lái)源 IP 等進(jìn)行定制化的限流。
ApiDefinition:用戶自定義的 API 定義分組,可以看做是一些 URL 匹配的組合。比如我們可以定義一個(gè) API 叫 my_api,請(qǐng)求 path 模式為 /foo/** 和 /baz/** 的都?xì)w到 my_api 這個(gè) API 分組下面。
限流的時(shí)候可以針對(duì)這個(gè)自定義的 API 分組維度進(jìn)行限流。
注意這個(gè)版本,1.6.0以后才有的。

我們直接上代碼,進(jìn)入實(shí)戰(zhàn)。新建一個(gè)SpringCloud項(xiàng)目,選中zuul。并在啟動(dòng)類(lèi)上加上@EnableZuulProxy注解,代表這是一個(gè)zuul網(wǎng)關(guān)項(xiàng)目。
pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>sentinelzuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sentinelzuul</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<sentinel.version>1.6.1</sentinel.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
</dependency>-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-zuul-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
官方文檔上寫(xiě),只需要引入sentinel-zuul-adapter依賴,實(shí)測(cè)后發(fā)現(xiàn),只引入這個(gè)的話,所依賴的Sentinel-core是1.5.2版本,會(huì)導(dǎo)致啟動(dòng)失敗。所以我手工加入了其他幾個(gè)依賴。
yml文件如下:
server:
port: 9999
zuul:
routes:
one:
path: /baoban/**
url: http://localhost:8888/baoban/
spring:
application:
name: sentinelzuul
這里配了一個(gè)簡(jiǎn)單的routes映射。那是另外一個(gè)本地服務(wù)。
使用zuul的限流很簡(jiǎn)單,2個(gè)類(lèi)即可
ZuulConfig.java
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter;
import com.netflix.zuul.ZuulFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author wuweifeng wrote on 2019/7/3.
*/
@Configuration
public class ZuulConfig {
@Bean
public ZuulFilter sentinelZuulPreFilter() {
return new SentinelZuulPreFilter(10000);
}
@Bean
public ZuulFilter sentinelZuulPostFilter() {
return new SentinelZuulPostFilter(1000);
}
@Bean
public ZuulFilter sentinelZuulErrorFilter() {
return new SentinelZuulErrorFilter(-1);
}
}
package com.example.sentinelzuul.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Set;
/**
* @author wuweifeng wrote on 2019/7/3.
*/
@Configuration
public class GatewayRuleConfig {
private static final int URL_MATCH_STRATEGY_EXACT = 0;
private static final int URL_MATCH_STRATEGY_PREFIX = 1;
private static final int URL_MATCH_STRATEGY_REGEX = 2;
@PostConstruct
public void doInit() {
// Prepare some gateway rules and API definitions (only for demo).
// It's recommended to leverage dynamic data source or the Sentinel dashboard to push the rules.
initCustomizedApis();
initGatewayRules();
}
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("baobao_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
//add(new ApiPathPredicateItem().setPattern("/ahas"));
add(new ApiPathPredicateItem().setPattern("/baoban/**")
.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX));
}});
//ApiDefinition api2 = new ApiDefinition("another_customized_api")
// .setPredicateItems(new HashSet<ApiPredicateItem>() {{
// add(new ApiPathPredicateItem().setPattern("/**")
// .setMatchStrategy(URL_MATCH_STRATEGY_PREFIX));
// }});
definitions.add(api1);
//definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("baobao_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(1)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("aliyun-product-route")
.setCount(2)
.setIntervalSec(2)
//應(yīng)對(duì)突發(fā)請(qǐng)求時(shí)額外允許的請(qǐng)求數(shù)目。
.setBurst(2)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
)
);
rules.add(new GatewayFlowRule("another-route-httpbin")
.setCount(10)
//統(tǒng)計(jì)時(shí)間窗口,單位是秒,默認(rèn)是 1 秒。
.setIntervalSec(1)
//流量整形的控制效果,同限流規(guī)則的 controlBehavior 字段,目前支持快速失敗和勻速排隊(duì)兩種模式,默認(rèn)是快速失敗。
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
//勻速排隊(duì)模式下的最長(zhǎng)排隊(duì)時(shí)間,單位是毫秒,僅在勻速排隊(duì)模式下生效。
.setMaxQueueingTimeoutMs(600)
//參數(shù)限流配置
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
.setFieldName("X-Sentinel-Flag")
)
);
rules.add(new GatewayFlowRule("another-route-httpbin")
.setCount(1)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("pa")
)
);
rules.add(new GatewayFlowRule("some_customized_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("pn")
)
);
GatewayRuleManager.loadRules(rules);
//監(jiān)聽(tīng)zookeeper,使用zookeeper的規(guī)則
//ReadableDataSource<String, Set<GatewayFlowRule>> flowRuleDataSource = new ZookeeperDataSource<>(null, null,
// source -> JSON.parseObject(source, new TypeReference<Set<GatewayFlowRule>>() {
// }));
//GatewayRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}
主要做的有2件事
1是配置一下APIDefinition,也就是給自己的映射規(guī)則起個(gè)名字。
2是配置Rule,和之前的配置rule差不多。創(chuàng)建一個(gè)rule的集合,設(shè)置rule規(guī)則,具體規(guī)則各字段在上面截圖中有解釋。

這里我配了一個(gè)簡(jiǎn)單的一秒1個(gè)QPS的規(guī)則。最后用GatewayRuleManager去loadRules即可。
之后測(cè)試一下就發(fā)現(xiàn)規(guī)則已生效。頻繁訪問(wèn)被限流的服務(wù)時(shí),會(huì)報(bào)下面的異常。

如果你想自定義這個(gè)熔斷的返回值的話,可以加個(gè)類(lèi)實(shí)現(xiàn)ZuulBlockFallbackProvider:
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.BlockResponse;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.DefaultBlockFallbackProvider;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackProvider;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author wuweifeng wrote on 2019/7/3.
*/
public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider {
private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class);
// you can define route as service level
@Override
public String getRoute() {
return "baobao_api";
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route));
if (cause instanceof BlockException) {
return new BlockResponse(429, "the route is blocked", route);
} else {
return new BlockResponse(500, "System Error", route);
}
}
}
getRoute方法返回的就是上面定義的resouceName。然后注冊(cè)一下就好了。


當(dāng)然這也是基于內(nèi)存的規(guī)則,不能動(dòng)態(tài)改變,在實(shí)際生產(chǎn)中,如果需要?jiǎng)討B(tài)改變規(guī)則的話,還是需要去用zookeeper之類(lèi)的。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中MapStruct對(duì)象映射的實(shí)現(xiàn)
MapStruct是一種Java實(shí)體類(lèi)映射框架,本文就來(lái)介紹一下Java中MapStruct對(duì)象映射的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12
使用Rhino讓java執(zhí)行javascript的方法實(shí)例
這篇文章主要介紹了java使用Rhino執(zhí)行javascript的方法,Rhino由Mozilla開(kāi)發(fā),是 JavaScript 一種基于Java的實(shí)現(xiàn)2013-12-12
關(guān)于maven使用過(guò)程中無(wú)法導(dǎo)入依賴的一些總結(jié)
這篇文章主要介紹了關(guān)于maven使用過(guò)程中無(wú)法導(dǎo)入依賴的一些總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
java反射機(jī)制及beanUtils的實(shí)現(xiàn)原理分析
本文介紹了Java的反射機(jī)制、VO、DTO、PO的概念以及BeanUtils的實(shí)現(xiàn)原理和簡(jiǎn)單示例,通過(guò)反射可以在運(yùn)行時(shí)動(dòng)態(tài)操作類(lèi)、方法和字段,BeanUtils用于在不同bean之間進(jìn)行屬性復(fù)制2024-12-12
java獲取IP歸屬地全網(wǎng)顯示開(kāi)源庫(kù)使用
這篇文章主要為大家介紹了java獲取IP歸屬地全網(wǎng)顯示的開(kāi)源庫(kù)使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Java獲取Process子進(jìn)程進(jìn)程ID方法詳解
這篇文章主要介紹了Java獲取Process子進(jìn)程進(jìn)程ID方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12
Java實(shí)現(xiàn)表單提交(支持多文件同時(shí)上傳)
本文介紹了Java、Android實(shí)現(xiàn)表單提交(支持多文件同時(shí)上傳)的方法,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01

