Sentinel實(shí)現(xiàn)服務(wù)限流、熔斷、降級(jí)

限流的基本認(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ā)送。

image.png

滑動(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)

  1. 漏水的速率是固定的
  2. 即使存在突然注水量變大的情況,漏水的速率也是固定的


    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è)令牌。

image.png

有以下三種情形可能發(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

image.png

添加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

演示流程

  1. 修改provider中限流規(guī)則:flowRule.setLimitApp("springboot-app1");
  2. 在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)行推送。如下圖所示:


image.png

搭建token-server

image.png

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)之后,在user.home/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)之后,在user.home/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)呢?

  1. 平均響應(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
  2. 異常比例,當(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%
  3. 異常數(shù),當(dāng)資源在1分鐘的異常數(shù)據(jù)超過閾值后會(huì)進(jìn)行熔斷針對(duì)這些規(guī)則,Sentinel中給出了響應(yīng)的字段來設(shè)置
image.png
  • 平均響應(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é)院

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,025評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,970評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,157評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,410評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,896評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,157評(píng)論 2 375