限流的基本認(rèn)識(shí)
場(chǎng)景分析
一個(gè)互聯(lián)網(wǎng)產(chǎn)品,打算搞一次大促來增加銷量以及曝光。公司的架構(gòu)師基于往期的流量情況做了一個(gè)活動(dòng)流量的預(yù)估,然后整個(gè)公司的各個(gè)技術(shù)團(tuán)隊(duì)開始按照這個(gè)目標(biāo)進(jìn)行設(shè)計(jì)和優(yōu)化,最終在大家不懈的努力之下,達(dá)到了鏈路壓測(cè)的目標(biāo)流量峰值。到了活動(dòng)開始那天,大家都在盯著監(jiān)控面板,看著流量像洪水一樣涌進(jìn)來。由于前期的宣傳工作做得很好,使得這個(gè)流量遠(yuǎn)遠(yuǎn)超過預(yù)估的峰值,后端服務(wù)開始不穩(wěn)定,CPU、內(nèi)存各種爆表。部分服務(wù)開始出現(xiàn)無響應(yīng)的情況。最后,整個(gè)系統(tǒng)開始崩潰,用戶無法正常訪問服務(wù)。最后導(dǎo)致公司巨大的損失
引入限流
在10.1黃金周,各大旅游景點(diǎn)都是人滿為患。所有有些景點(diǎn)為了避免出現(xiàn)踩踏事故,會(huì)采取限流措施。那在架構(gòu)場(chǎng)景中,是不是也能這么做呢?針對(duì)這個(gè)場(chǎng)景,能不能夠設(shè)置一個(gè)最大的流量限制,如果超過這個(gè)流量,我們就拒絕提供服務(wù),從而使得我們的服務(wù)不會(huì)掛掉。
當(dāng)然,限流雖然能夠保護(hù)系統(tǒng)不被壓垮,但是對(duì)于被限流的用戶,就會(huì)很不開心。所以限流其實(shí)是一種有損的解決方案。但是相比于全部不可用,有損服務(wù)是最好的一種解決辦法
限流的作用
除了前面說的限流使用場(chǎng)景之外,限流的設(shè)計(jì)還能防止惡意請(qǐng)求流量、惡意攻擊
所以,限流的基本原理是通過對(duì)并發(fā)訪問/請(qǐng)求進(jìn)行限速或者一個(gè)時(shí)間窗口內(nèi)的請(qǐng)求進(jìn)行限速來保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以拒絕服務(wù)(定向到錯(cuò)誤頁(yè)或者告知資源沒有了)、排隊(duì)或等待(秒殺、下單)、降級(jí)(返回兜底數(shù)據(jù)或默認(rèn)數(shù)據(jù)或默認(rèn)數(shù)據(jù),如商品詳情頁(yè)庫(kù)存默認(rèn)有貨)
一般互聯(lián)網(wǎng)企業(yè)常見的限流有:限制總并發(fā)數(shù)(如數(shù)據(jù)庫(kù)連接池、線程池)、限制瞬時(shí)并發(fā)數(shù)(nginx的limit_conn模塊,用來限制瞬時(shí)并發(fā)連接數(shù))、限制時(shí)間窗口內(nèi)的平均速率(如Guava的
RateLimiter、nginx的limit_req模塊,限制每秒的平均速率);其他的還有限制遠(yuǎn)程接口調(diào)用速率、限制MQ的消費(fèi)速率。另外還可以根據(jù)網(wǎng)絡(luò)連接數(shù)、網(wǎng)絡(luò)流量、CPU或內(nèi)存負(fù)載等來限流。
有了限流,就意味著在處理高并發(fā)的時(shí)候多了一種保護(hù)機(jī)制,不用擔(dān)心瞬間流量導(dǎo)致系統(tǒng)掛掉或雪崩,最終做到有損服務(wù)而不是不服務(wù);但是限流需要評(píng)估好,不能亂用,否則一些正常流量出現(xiàn)一些奇怪的問題而導(dǎo)致用戶體驗(yàn)很差造成用戶流失。
常見的限流算法
滑動(dòng)窗口
發(fā)送和接受方都會(huì)維護(hù)一個(gè)數(shù)據(jù)幀的序列,這個(gè)序列被稱作窗口。發(fā)送方的窗口大小由接受方確定,目的在于控制發(fā)送速度,以免接受方的緩存不夠大,而導(dǎo)致溢出,同時(shí)控制流量也可以避免網(wǎng)絡(luò)擁塞。下面圖中的4,5,6號(hào)數(shù)據(jù)幀已經(jīng)被發(fā)送出去,但是未收到關(guān)聯(lián)的ACK,7,8,9幀則是等待發(fā)送。可以看出發(fā)送端的窗口大小為6,這是由接受端告知的。此時(shí)如果發(fā)送端收到4號(hào)ACK,則窗口的左邊緣向右收縮,窗口的右邊緣則向右擴(kuò)展,此時(shí)窗口就向前“滑動(dòng)了”,即數(shù)據(jù)幀10也可以被發(fā)送。
滑動(dòng)窗口演示地址:
https://media.pearsoncmg.com/aw/ecs_kurose_compnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html
漏桶(控制傳輸速率Leaky bucket)
漏桶算法思路是,不斷的往桶里面注水,無論注水的速度是大還是小,水都是按固定的速率往外漏水;
如果桶滿了,水會(huì)溢出;
桶本身具有一個(gè)恒定的速率往下漏水,而上方時(shí)快時(shí)慢的會(huì)有水進(jìn)入桶內(nèi)。當(dāng)桶還未滿時(shí),上方的水可以加入。一旦水滿,上方的水就無法加入。桶滿正是算法中的一個(gè)關(guān)鍵的觸發(fā)條件(即流量異常判斷成立的條件)。而此條件下如何處理上方流下來的水,有兩種方式
在桶滿水之后,常見的兩種處理方式為:
1.暫時(shí)攔截住上方水的向下流動(dòng),等待桶中的一部分水漏走后,再放行上方水。
2.溢出的上方水直接拋棄。
特點(diǎn)
- 漏水的速率是固定的
-
即使存在突然注水量變大的情況,漏水的速率也是固定的
image.png
令牌桶(能夠解決突發(fā)流量)
令牌桶算法是網(wǎng)絡(luò)流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。
典型情況下,令牌桶算法用來控制發(fā)送到網(wǎng)絡(luò)上的數(shù)據(jù)的數(shù)目,并允許突發(fā)數(shù)據(jù)的發(fā)送。
令牌桶是一個(gè)存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌; 令牌桶算法實(shí)際上由三部分組成:兩個(gè)流和一個(gè)桶,分別是令牌流、數(shù)據(jù)流和令牌桶
令牌流與令牌桶
系統(tǒng)會(huì)以一定的速度生成令牌,并將其放置到令牌桶中,可以將令牌桶想象成一個(gè)緩沖區(qū)(可以用隊(duì)列這種數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)),當(dāng)緩沖區(qū)填滿的時(shí)候,新生成的令牌會(huì)被扔掉。這里有兩個(gè)變量很重要:
第一個(gè)是生成令牌的速度,一般稱為 rate 。比如,我們?cè)O(shè)定 rate = 2 ,即每秒鐘生成 2 個(gè)令牌,也就是每 1/2 秒生成一個(gè)令牌;
第二個(gè)是令牌桶的大小,一般稱為 burst 。比如,我們?cè)O(shè)定 burst = 10 ,即令牌桶最大只能容納 10 個(gè)令牌。
有以下三種情形可能發(fā)生:
- 1.數(shù)據(jù)流的速率 等于 令牌流的速率。這種情況下,每個(gè)到來的數(shù)據(jù)包或者請(qǐng)求都能對(duì)應(yīng)一個(gè)令牌,然后無延遲地通過隊(duì)列;
- 2.數(shù)據(jù)流的速率 小于 令牌流的速率。通過隊(duì)列的數(shù)據(jù)包或者請(qǐng)求只消耗了一部分令牌,剩下的令牌會(huì)在令牌桶里積累下來,直到桶被裝滿。剩下的令牌可以在突發(fā)請(qǐng)求的時(shí)候消耗掉。
- 3.數(shù)據(jù)流的速率 大于 令牌流的速率。這意味著桶里的令牌很快就會(huì)被耗盡。導(dǎo)致服務(wù)中斷一段時(shí)
間,如果數(shù)據(jù)包或者請(qǐng)求持續(xù)到來,將發(fā)生丟包或者拒絕響應(yīng)。
Sentinel整合Dubbo
添加jar依賴
<dependency>
<artifactId>sentinel-api</artifactId>
<groupId>com.wei.sentinel</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
SentinelService
public interface SentinelService {
String sayHello(String name);
}
SentinelServiceImpl
@Service
public class SentinelServiceImpl implements SentinelService{
@Override
public String sayHello(String name) {
System.out.println("begin execute sayHello:"+name);
return "Hello World:"+name+"->timer:"+ LocalDateTime.now();
}
}
ProviderConfig
@Configuration
@DubboComponentScan("com.wei.sentinel")
public class ProviderConfig {
@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig config=new ApplicationConfig();
config.setName("sentinel-provider");
config.setOwner("wei");
return config;
}
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig=new RegistryConfig();
registryConfig.setAddress("zookeeper://192.168.1.102:2181");
registryConfig.setCheck(false);
return registryConfig;
}
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig=new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
}
Bootstrap
public class Bootstrap {
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ProviderConfig.class);
((AnnotationConfigApplicationContext) applicationContext).start();
System.in.read();
}
}
創(chuàng)建SpringBoot的Consumer項(xiàng)目
添加jar依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<artifactId>sentinel-api</artifactId>
<groupId>com.wei.sentinel</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
SentinelDubboController
@RestController
public class SentinelDubboController {
@Reference
SentinelService sentinelService;
@GetMapping("/say")
public String sayHello(){
String result=sentinelService.sayHello("wei");
return result;
}
}
application.properties
dubbo.registry.address=zookeeper://192.168.1.102:2181
dubbo.application.name=springboot-sentinel
dubbo.scan.base-packages=com.wei.sentinel
添加sentinel限流支持
添加jar包依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dubbo-adapter</artifactId>
<version>1.6.2</version>
</dependency
設(shè)置限流的基準(zhǔn)
Service Provider 用于向外界提供服務(wù),處理各個(gè)消費(fèi)者的調(diào)用請(qǐng)求。為了保護(hù) Provider 不被激增的流量拖垮影響穩(wěn)定性,可以給 Provider 配置 QPS 模式的限流,這樣當(dāng)每秒的請(qǐng)求量超過設(shè)定的閾值時(shí)會(huì)自動(dòng)拒絕多的請(qǐng)求。限流粒度可以是服務(wù)接口和服務(wù)方法兩種粒度。若希望整個(gè)服務(wù)接口的 QPS 不超過一定數(shù)值,則可以為對(duì)應(yīng)服務(wù)接口資源(resourceName 為接口全限定名)配置 QPS 閾值;若希望服務(wù)的某個(gè)方法的 QPS 不超過一定數(shù)值,則可以為對(duì)應(yīng)服務(wù)方法資源(resourceName 為接口全限定名:方法簽名)配置 QPS 閾值
private static void initFlowRule(){
FlowRule flowRule=new FlowRule();
//針對(duì)具體的方法限流
flowRule.setResource("com.wei.sentinel.SentinelService:sayHello(java.lang.String)");
flowRule.setCount(10);//限流閾值 qps=10
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);//限流閾值類型(QPS 或并發(fā)線程數(shù))
flowRule.setLimitApp("default");//流控針對(duì)的調(diào)用來源,若為 default 則不區(qū)分調(diào)用來源
//流量控制手段(直接拒絕、Warm Up、勻速排隊(duì))
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
FlowRuleManager.loadRules(Collections.singletonList(flowRule));
}
啟動(dòng)時(shí)加入 JVM 參數(shù) -Dcsp.sentinel.dashboard.server=localhost:8080 指定控制臺(tái)地址和端口
自行用使用jemeter進(jìn)行壓測(cè)
參數(shù)解釋:
1.LimitApp
很多場(chǎng)景下,根據(jù)調(diào)用方來限流也是非常重要的。比如有兩個(gè)服務(wù) A 和 B 都向 Service Provider 發(fā)起調(diào)用請(qǐng)求,我們希望只對(duì)來自服務(wù) B 的請(qǐng)求進(jìn)行限流,則可以設(shè)置限流規(guī)則的 limitApp 為服務(wù) B 的名稱。
Sentinel Dubbo Adapter 會(huì)自動(dòng)解析 Dubbo 消費(fèi)者(調(diào)用方)的 application name 作為調(diào)用方
名稱(origin),在進(jìn)行資源保護(hù)的時(shí)候都會(huì)帶上調(diào)用方名稱。若限流規(guī)則未配置調(diào)用方(default),則該限流規(guī)則對(duì)所有調(diào)用方生效。若限流規(guī)則配置了調(diào)用方則限流規(guī)則將僅對(duì)指定調(diào)用方生效。
注:Dubbo 默認(rèn)通信不攜帶對(duì)端 application name 信息,因此需要開發(fā)者在調(diào)用端手動(dòng)將 applicationname 置入 attachment 中,provider 端進(jìn)行相應(yīng)的解析。Sentinel Dubbo Adapter 實(shí)現(xiàn)了一個(gè) Filter 用于自動(dòng)從 consumer 端向 provider 端透?jìng)?application name。若調(diào)用端未引入 Sentinel DubboAdapter,又希望根據(jù)調(diào)用端限流,可以在調(diào)用端手動(dòng)將 application name 置入 attachment 中,key 為dubboApplication
演示流程
- 修改provider中限流規(guī)則:flowRule.setLimitApp("springboot-app1");
- 在consumer工程中,做如下處理。其中一個(gè)通過attachment傳遞了一個(gè)消費(fèi)者的
application.name,另一個(gè)沒有傳,通過jemeter工具進(jìn)行測(cè)試
@GetMapping("/say1")
public String sayHello(){
RpcContext.getContext().setAttachment("dubboApplication","springboot-app1\"");
String result=sentinelService.sayHello("wei");
return result;
}
@GetMapping("/say2")
public String say2Hello(){
String result=sentinelService.sayHello("wei");
return result;
}
2.ControlBehavior
當(dāng) QPS 超過某個(gè)閾值的時(shí)候,則采取措施進(jìn)行流量控制。流量控制的手段包括以下幾種:直接拒絕、Warm Up、勻速排隊(duì)。對(duì)應(yīng) FlowRule 中的 controlBehavior 字段
- 直接拒絕(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默認(rèn)的流量控制方式,當(dāng)QPS超過任意規(guī)則的閾值后,新的請(qǐng)求就會(huì)被立即拒絕,拒絕方式為拋出FlowException。這種方式適用于對(duì)系統(tǒng)處理能力確切已知的情況下,比如通過壓測(cè)確定了系統(tǒng)的準(zhǔn)確水位時(shí)
- Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即預(yù)熱/冷啟動(dòng)方式,當(dāng)系統(tǒng)長(zhǎng)期處于低并發(fā)的情況下,流量突然增加到qps的最高峰值,可能會(huì)造成系統(tǒng)的瞬間流量過大把系統(tǒng)壓垮。所以warmup,相當(dāng)于處理請(qǐng)求的數(shù)量是緩慢增加,經(jīng)過一段時(shí)間以后,到達(dá)系統(tǒng)處理請(qǐng)求個(gè)數(shù)的最大值
- 勻速排隊(duì)(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式會(huì)嚴(yán)格控制請(qǐng)求通過的間隔時(shí)間,也即是讓請(qǐng)求以均勻的速度通過,對(duì)應(yīng)的是漏桶算法
它的原理是,以固定的間隔時(shí)間讓請(qǐng)求通過。當(dāng)請(qǐng)求過來的時(shí)候,如果當(dāng)前請(qǐng)求距離上個(gè)通過的請(qǐng)求通過的時(shí)間間隔不小于預(yù)設(shè)值,則讓當(dāng)前請(qǐng)求通過;否則,計(jì)算當(dāng)前請(qǐng)求的預(yù)期通過時(shí)間,如果該請(qǐng)求的預(yù)期通過時(shí)間小于規(guī)則預(yù)設(shè)的 timeout 時(shí)間,則該請(qǐng)求會(huì)等待直到預(yù)設(shè)時(shí)間到來通過;反之,則馬上拋出阻塞異常。
可以設(shè)置一個(gè)最長(zhǎng)排隊(duì)等待時(shí)間: flowRule.setMaxQueueingTimeMs(5 * 1000); // 最長(zhǎng)排隊(duì)等待時(shí)間:5s
這種方式主要用于處理間隔性突發(fā)的流量,例如消息隊(duì)列。想象一下這樣的場(chǎng)景,在某一秒有大量的請(qǐng)求到來,而接下來的幾秒則處于空閑狀態(tài),我們希望系統(tǒng)能夠在接下來的空閑期間逐漸處理這些請(qǐng)求,而不是在第一秒直接拒絕多余的請(qǐng)求。
實(shí)現(xiàn)分布式限流
要想使用集群流控功能,我們需要在應(yīng)用端配置動(dòng)態(tài)規(guī)則源,并通過 Sentinel 控制臺(tái)實(shí)時(shí)進(jìn)行推送。如下圖所示:
搭建token-server
Jar包依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-server-default</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
ClusterServer
public class ClusterServer {
public static void main(String[] args) throws Exception {
ClusterTokenServer tokenServer=new SentinelDefaultTokenServer();
ClusterServerConfigManager.loadGlobalTransportConfig(
new ServerTransportConfig().setIdleSeconds(600).setPort(9999));
ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("App-wei")); //設(shè)置成動(dòng)態(tài)
tokenServer.start();
}
}
DataSourceInitFunc
public class NacosDataSourceInitFunc implements InitFunc {
private final String remoteAddress="192.168.1.102"; //nacos 配置中心的服務(wù)host
private final String groupId="SENTINEL_GROUP";
private final String FLOW_POSTFIX="-flow-rules"; //dataid(names+postfix)
//意味著當(dāng)前的token-server會(huì)從nacos上獲得限流的規(guī)則
@Override
public void init() throws Exception {
ClusterFlowRuleManager.setPropertySupplier(namespace ->{
ReadableDataSource<String, List<FlowRule>> rds=
new NacosDataSource<List<FlowRule>>(remoteAddress,groupId,namespace+FLOW_POSTFIX,
source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>(){}));
return rds.getProperty();
});
}
}
resource目錄添加擴(kuò)展點(diǎn)
/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc = 自定義擴(kuò)展點(diǎn)
com.wei.sentinel.server.NacosDataSourceInitFunc
啟動(dòng)Sentinel dashboard
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -
Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.1.jar
啟動(dòng)nacos以及增加配置
DataID=App-wei-flow-rules
Group=SENTINEL_GROUP
配置內(nèi)容:
配置之前寫死的相關(guān)Sentinel 配置,如resource,grade,count等等
配置jvm參數(shù)
配置如下jvm啟動(dòng)參數(shù),連接到sentinel dashboard
-Dproject.name=App-wei -Dcsp.sentinel.dashboard.server=192.168.1.102:8080
-Dcsp.sentinel.log.use.pid=true
服務(wù)啟動(dòng)之后,在
/logs/csp/ 可以找到sentinel-record.log.pid*.date文件,如果看到日
志文件中獲取到了遠(yuǎn)程服務(wù)的信息,說明token-server啟動(dòng)成功了
Dubbo接入分布式限流
jar包依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dubbo-adapter</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-client-default</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.6.3</version>
</dependency>
增加擴(kuò)展點(diǎn)
擴(kuò)展點(diǎn)需要在resources/META-INF/services/增加擴(kuò)展的配置
com.alibaba.csp.sentinel.init.InitFunc = 自定義擴(kuò)展點(diǎn)
public class DataSourceInitFunc implements InitFunc {
private static final String CLUSTER_SERVER_HOST = "localhost";//token-server的ip
private static final int CLUSTER_SERVER_PORT = 9999;//token-server 端口
private static final int REQUEST_TIME_OUT = 200000; //請(qǐng)求超時(shí)時(shí)間
private static final String APP_NAME = "App-wei";
private static final String REMOTE_ADDRESS = "192.168.1.102"; //nacos服務(wù)的ip
private static final String GROUP_ID = "SENTINEL_GROUP";//group id
private static final String FLOW_POSTFIX = "-flow-rules";//限流規(guī)則后綴
@Override
public void init() throws Exception {
loadClusterClientConfig();
registerClusterFlowRuleProperty();
}
//通過硬編碼的方式,配置連接到token-server服務(wù)的地址,{這種在實(shí)際使用過程中不建議,后續(xù)可以基于動(dòng)態(tài)配置源改造
public static void loadClusterClientConfig() {
ClusterClientAssignConfig assignConfig = new ClusterClientAssignConfig();
assignConfig.setServerHost(CLUSTER_SERVER_HOST);
assignConfig.setServerPort(CLUSTER_SERVER_PORT);
ClusterClientConfigManager.applyNewAssignConfig(assignConfig);
ClusterClientConfig clientConfig = new ClusterClientConfig();
clientConfig.setRequestTimeout(REQUEST_TIME_OUT); //token-client請(qǐng)求 token - server獲取令牌的超時(shí)時(shí)間
ClusterClientConfigManager.applyNewConfig(clientConfig);
}
/**
* 注冊(cè)動(dòng)態(tài)規(guī)則Property
* 當(dāng)client與Server連接中斷,退化為本地限流時(shí)需要用到的該規(guī)則
* 該配置為必選項(xiàng),客戶端會(huì)從nacos上加載限流規(guī)則,請(qǐng)求tokenserver時(shí),會(huì)戴上要check的規(guī)則id
*/
private static void registerClusterFlowRuleProperty() {
// 使用 Nacos 數(shù)據(jù)源作為配置中心,需要在 REMOTE_ADDRESS 上啟動(dòng)一個(gè) Nacos 的服務(wù)
ReadableDataSource<String, List<FlowRule>> ds = new
NacosDataSource<List<FlowRule>>(REMOTE_ADDRESS, GROUP_ID, APP_NAME + FLOW_POSTFIX,
source -> JSON.parseObject(source, new
TypeReference<List<FlowRule>>() {
}));
// 為集群客戶端注冊(cè)動(dòng)態(tài)規(guī)則源
FlowRuleManager.register2Property(ds.getProperty());
}
}
配置jvm參數(shù)
這里的project-name要包含在token-server中配置的namespace中,
token server 會(huì)根據(jù)客戶端對(duì)應(yīng)的 namespace(默認(rèn)為 project.name 定義的應(yīng)用名)下的連接數(shù)來計(jì)算總的閾值
-Dproject.name=App-wei -Dcsp.sentinel.dashboard.server=192.168.8.106:8080
-Dcsp.sentinel.log.use.pid=true
服務(wù)啟動(dòng)之后,在
/logs/csp/ 可以找到sentinel-record.log.pid*.date文件,如果看到日志文件中獲取到了token-server的信息,說明連接成功了
Sentinel熔斷降級(jí)
Sentinel 熔斷降級(jí)會(huì)在調(diào)用鏈路中某個(gè)資源出現(xiàn)不穩(wěn)定狀態(tài)時(shí)(例如調(diào)用超時(shí)或異常比例升高),對(duì)這個(gè)資源的調(diào)用進(jìn)行限制,讓請(qǐng)求快速失敗,避免影響到其它的資源而導(dǎo)致級(jí)聯(lián)錯(cuò)誤。當(dāng)資源被降級(jí)后,在接下來的降級(jí)時(shí)間窗口之內(nèi),對(duì)該資源的調(diào)用都自動(dòng)熔斷
那么怎么去判斷資源是否處于穩(wěn)定狀態(tài)呢?
- 平均響應(yīng)時(shí)間,比如,在1s內(nèi)連續(xù)處理5個(gè)請(qǐng)求,它的平均響應(yīng)時(shí)間都超過閾值,那么在后續(xù)的時(shí)
間窗口中,對(duì)于這個(gè)方法的調(diào)用都會(huì)自動(dòng)熔斷,sentinel默認(rèn)的平均響應(yīng)時(shí)間是4900ms - 異常比例,當(dāng)指定資源每秒請(qǐng)求量大于等于5,并且每秒的異常總數(shù)占通過量的比值超過閾值之后
(比如每秒處理1000個(gè)請(qǐng)求,那么其中異常請(qǐng)求數(shù)為500,那么當(dāng)前的比值是50%),那么該資源
會(huì)進(jìn)入降級(jí)狀態(tài)。異常的比率范圍是[0.0.1.0]表示0%到100% - 異常數(shù),當(dāng)資源在1分鐘的異常數(shù)據(jù)超過閾值后會(huì)進(jìn)行熔斷針對(duì)這些規(guī)則,Sentinel中給出了響應(yīng)的字段來設(shè)置
平均響應(yīng)時(shí)間 (DEGRADE_GRADE_RT):當(dāng) 1s 內(nèi)持續(xù)進(jìn)入 5 個(gè)請(qǐng)求,對(duì)應(yīng)時(shí)刻的平均響應(yīng)時(shí)間(秒級(jí))均超過閾值(count,以 ms 為單位),那么在接下的時(shí)間窗口(DegradeRule 中的
timeWindow,以 s 為單位)之內(nèi),對(duì)這個(gè)方法的調(diào)用都會(huì)自動(dòng)地熔斷(拋出 DegradeException)。
注意 Sentinel 默認(rèn)統(tǒng)計(jì)的 RT 上限是 4900 ms,超出此閾值的都會(huì)算作 4900 ms,若需要變更此上限可以通過啟動(dòng)配置項(xiàng) -Dcsp.sentinel.statistic.max.rt=xxx 來配置。異常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):當(dāng)資源的每秒請(qǐng)求量 >= 5,并且每秒異常總數(shù)占通過量的比值超過閾值(DegradeRule 中的 count)之后,資源進(jìn)入降級(jí)狀態(tài),即在接下的時(shí)間窗口(DegradeRule 中的 timeWindow,以 s 為單位)之內(nèi),對(duì)這個(gè)方法的調(diào)用都會(huì)自動(dòng)地返回。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。
異常數(shù) (DEGRADE_GRADE_EXCEPTION_COUNT):當(dāng)資源近 1 分鐘的異常數(shù)目超過閾值之后會(huì)進(jìn)行熔斷。注意由于統(tǒng)計(jì)時(shí)間窗口是分鐘級(jí)別的,若 timeWindow 小于 60s,則結(jié)束熔斷狀態(tài)后仍可能再進(jìn)入熔斷狀態(tài)。
熔斷演示
Jar包依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dubbo-adapter</artifactId>
<version>1.6.2</version>
</dependency>
配置規(guī)則
添加一個(gè)DataSourceInitFunc。
然后在resource/METAINF/services/com.alibaba.csp.sentinel.init.InitFunc中配置改類的全路徑,這樣的話sentinel在觸發(fā)限流時(shí)會(huì)去調(diào)用這個(gè)initFunc來解析規(guī)則
public class DataSourceInitFunc implements InitFunc {
@Override
public void init() throws Exception {
List<DegradeRule> rules=new ArrayList<>();
DegradeRule rule=new DegradeRule();
//下面這個(gè)配置的意思是,當(dāng)1s內(nèi)持續(xù)進(jìn)入5個(gè)請(qǐng)求,平均響應(yīng)時(shí)間都超過count(10ms),
// 那么在接下來的timewindow(10s)內(nèi),對(duì)
//這個(gè)方法的調(diào)用都會(huì)自動(dòng)熔斷,拋出異常:degradeException.
//指定被保護(hù)的資源
rule.setResource("com.wei.sentinel.SentinelService");
rule.setCount(10); //閾值
//降級(jí)模式, RT(平均響應(yīng)時(shí)間)、異常比例(DEGRADE_GRADE_EXCEPTION_RATIO)/異常數(shù)量
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);//降級(jí)的時(shí)間單位, 單位為s
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
增加一個(gè)sleep,這個(gè)時(shí)候就會(huì)起到一個(gè)熔斷的效果
@Override
public String sayHello(String name) {
try {
Thread.sleep(1500); //添加這個(gè)和不添加這個(gè)的影響
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("begin execute sayHello:"+name);
return "Hello World:"+name+"->timer:"+ LocalDateTime.now();
}
——學(xué)自咕泡學(xué)院