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)境下,開啟了多個(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)類上加上@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>
官方文檔上寫,只需要引入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è)類即可
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)聽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è)類實(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之類的。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot斷點(diǎn)上傳、續(xù)傳、秒傳實(shí)現(xiàn)方式
這篇文章主要介紹了springboot斷點(diǎn)上傳、續(xù)傳、秒傳實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07java中Hibernate面試知識(shí)點(diǎn)整理
在本篇文章里小編給大家整理的是一篇關(guān)于java中Hibernate面試知識(shí)點(diǎn)整理內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2021-01-01Springboot項(xiàng)目全局異常統(tǒng)一處理案例代碼
最近在做項(xiàng)目時(shí)需要對(duì)異常進(jìn)行全局統(tǒng)一處理,主要是一些分類入庫(kù)以及記錄日志等,因?yàn)轫?xiàng)目是基于Springboot的,所以去網(wǎng)絡(luò)上找了一些博客文檔,然后再結(jié)合項(xiàng)目本身的一些特殊需求做了些許改造,現(xiàn)在記錄下來(lái)便于以后查看2023-01-01springboot 自定義權(quán)限標(biāo)簽(tld),在freemarker引用操作
這篇文章主要介紹了springboot 自定義權(quán)限標(biāo)簽(tld),在freemarker引用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Java 注冊(cè)時(shí)發(fā)送激活郵件和激活的實(shí)現(xiàn)示例
這篇文章主要介紹了Java 注冊(cè)時(shí)發(fā)送激活郵件和激活的實(shí)現(xiàn)示例的相關(guān)資料,需要的朋友可以參考下2017-07-07Java使用openssl檢測(cè)網(wǎng)站是否支持ocsp
OCSP在線證書狀態(tài)協(xié)議是為了替換CRL而提出來(lái)的。對(duì)于現(xiàn)代web服務(wù)器來(lái)說(shuō)一般都是支持OCSP的,OCSP也是現(xiàn)代web服務(wù)器的標(biāo)配,這篇文章主要介紹了Java使用openssl檢測(cè)網(wǎng)站是否支持ocsp,需要的朋友可以參考下2022-07-07玩轉(zhuǎn)spring boot 結(jié)合AngularJs和JDBC(4)
玩轉(zhuǎn)spring boot,這篇文章主要介紹了結(jié)合AngularJs和JDBC,玩轉(zhuǎn)spring boot,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Java學(xué)生信息管理系統(tǒng)設(shè)計(jì)(數(shù)據(jù)庫(kù)版)
這篇文章主要為大家詳細(xì)介紹了數(shù)據(jù)庫(kù)版的Java學(xué)生信息管理系統(tǒng)設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11