微服務(wù)限流容錯降級Sentinel實戰(zhàn)

點(diǎn)贊再看,養(yǎng)成習(xí)慣凡壤,搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章蛋辈。本文 GitHub org_hejianhui/JavaStudy 已收錄目养,有我的系列文章挂滓。

一馒索、什么是雪崩效應(yīng)并淋?


業(yè)務(wù)場景寓搬,高并發(fā)調(diào)用

  1. 正常情況下,微服務(wù)A B C D 都是正常的县耽。
  2. 隨著時間推移句喷,在某一個時間點(diǎn) 微服務(wù)A突然掛了,此時的微服務(wù)B 還在瘋狂的調(diào)用微服務(wù)A兔毙,由于A已經(jīng)掛了唾琼,所以B調(diào)用A必須等待服務(wù)調(diào)用超時。而我們知道每次B -> A 的適合B都會去創(chuàng)建線程(而線程由計算機(jī)的資源澎剥,比如cpu锡溯、內(nèi)存等)。由于是高并發(fā)場景,B 就會阻塞大量的線程祭饭。那邊B所在的機(jī)器就會去創(chuàng)建線程芜茵,但是計算機(jī)資源是有限的,最后B的服務(wù)器就會宕機(jī)倡蝙。(說白了微服務(wù)B 活生生的被豬隊友微服務(wù)A給拖死了
  3. 由于微服務(wù)A這個豬隊友活生生的把微服務(wù)B給拖死了九串,導(dǎo)致微服務(wù)B也宕機(jī)了,然后也會導(dǎo)致微服務(wù) C D 出現(xiàn)類似的情況寺鸥,最終我們的豬隊友A成功的把微服務(wù) B C D 都拖死了猪钮。這種情況也叫做服務(wù)雪崩。也有一個專業(yè)術(shù)語(cascading failures)級聯(lián)故障胆建。

二烤低、容錯三板斧

2.1 超時

簡單來說就是超時機(jī)制,配置以下超時時間笆载,假如1秒——每次請求在1秒內(nèi)必須返回扑馁,否則到點(diǎn)就把線程掐死,釋放資源凉驻!

思路:一旦超時檐蚜,就釋放資源。由于釋放資源速度較快沿侈,應(yīng)用就不會那么容易被拖死。

代碼演示:(針對調(diào)用方處理)

// 第一步:設(shè)置RestTemplate的超時時間
@Configuration
public class WebConfig {

    @Bean
    public RestTemplate restTemplate() {
        //設(shè)置restTemplate的超時時間
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setReadTimeout(1000);
        requestFactory.setConnectTimeout(1000);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        return restTemplate;
    }
}

// 第二步:進(jìn)行超時異常處理
try{
    ResponseEntity<ProductInfo> responseEntity= restTemplate.getForEntity(uri+orderInfo.getProductNo(), ProductInfo.class);
    productInfo = responseEntity.getBody();
}catch (Exception e) {
    log.info("調(diào)用超時");
    throw new RuntimeException("調(diào)用超時");
}


// 設(shè)置全局異常處理
@ControllerAdvice
public class NiuhExceptionHandler {

    @ExceptionHandler(value = {RuntimeException.class})
    @ResponseBody
    public Object dealBizException() {
        OrderVo orderVo = new OrderVo();
        orderVo.setOrderNo("-1");
        orderVo.setUserName("容錯用戶");
        return orderVo;
    }
}

2.2 艙壁隔離模式

有興趣可以先了解一下船艙構(gòu)造——一般來說市栗,現(xiàn)代的輪船都會分很多艙室缀拭,艙室直接用鋼板焊死,彼此隔離填帽。這樣即使有某個/某些船艙進(jìn)水蛛淋,也不會營銷其它艙室,浮力夠篡腌,船不會沉褐荷。

代碼中的艙壁隔離(線程池隔離模式)

M類使用線程池1,N類使用線程池2嘹悼,彼此的線程池不同叛甫,并且為每個類分配的線程池大小,例如 coreSIze=10杨伙。

舉例子:M類調(diào)用B服務(wù)其监,N類調(diào)用C服務(wù),如果M類和N類使用相同的線程池限匣,那么如果B服務(wù)掛了抖苦,N類調(diào)用B服務(wù)的接口并發(fā)又很高,你又沒有任何保護(hù)措施,你的服務(wù)就很可能被M類拖死锌历。而如果M類有自己的線程池贮庞,N類也有自己的線程池,如果B服務(wù)掛了究西,M類頂多是將自己的線程池占滿窗慎,不會影響N類的線程池——于是N類依然能正常工作。

思路:不把雞蛋放在一個籃子里怔揩,你有你的線程池捉邢,我有我的線程池,你的線程池滿類和我也沒關(guān)系商膊,你掛了也和我也沒關(guān)系伏伐。

2.3 斷路器模式

現(xiàn)實世界的斷路器大家肯定都很了解,每個人家里都會有斷路器晕拆。斷路器實時監(jiān)控電路的情況藐翎,如果發(fā)現(xiàn)電路電流異常,就會跳閘实幕,從而防止電路被燒毀吝镣。

軟件世界的斷路器可以這樣理解:實時監(jiān)測應(yīng)用,如果發(fā)現(xiàn)在一定時間內(nèi)失敗次數(shù)/失敗率達(dá)到一定閥值昆庇,就“跳閘”末贾,斷路器打開——次數(shù),請求直接返回整吆,而不去調(diào)用原本調(diào)用的邏輯拱撵。

跳閘一段時間后(例如15秒),斷路器會進(jìn)入半開狀態(tài)表蝙,這是一個瞬間態(tài)拴测,此時允許一個請求調(diào)用該調(diào)的邏輯,如果成功府蛇,則斷路器關(guān)閉集索,應(yīng)用正常調(diào)用;如果調(diào)用依然不成功汇跨,斷路器繼續(xù)回到打開狀態(tài)务荆,過段時間再進(jìn)入半開狀態(tài)嘗試——通過“跳閘”,應(yīng)用可以保護(hù)自己穷遂,而且避免資源浪費(fèi)蛹含;而通過半開的設(shè)計,可以實現(xiàn)應(yīng)用的“自我修復(fù)”

三塞颁、Sentinel 流量控制浦箱、容錯吸耿、降級

3.1 什么是Sentinel?

A lightweight powerful flow control component enabling reliability and monitoring for microservices.(輕量級的流量控制酷窥、熔斷降級 Java 庫)
github官網(wǎng)地址:https://github.com/alibaba/Sentinel
wiki:https://github.com/alibaba/Sentinel/wiki/

Hystrix 在 Sentinel 面前就是弟弟

Sentinel的初體驗

niuh04-ms-alibaba-sentinel-helloworld

V1版本

  • 第一步:添加依賴包
<!--導(dǎo)入Sentinel的相關(guān)jar包-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.7.1</version>
</dependency>
  • 第二步:controller
@RestController
@Slf4j
public class HelloWorldSentinelController {

    @Autowired
    private BusiServiceImpl busiService;

    /**
     * 初始化流控規(guī)則
     */
    @PostConstruct
    public void init() {

        List<FlowRule> flowRules = new ArrayList<>();

        /**
         * 定義 helloSentinelV1 受保護(hù)的資源的規(guī)則
         */
        //創(chuàng)建流控規(guī)則對象
        FlowRule flowRule = new FlowRule();
        //設(shè)置流控規(guī)則 QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //設(shè)置受保護(hù)的資源
        flowRule.setResource("helloSentinelV1");
        //設(shè)置受保護(hù)的資源的閾值
        flowRule.setCount(1);

        flowRules.add(flowRule);

        //加載配置好的規(guī)則
        FlowRuleManager.loadRules(flowRules);
    }


    /**
     * 頻繁請求接口 http://localhost:8080/helloSentinelV1
     * 這種做法的缺點(diǎn):
     * 1)業(yè)務(wù)侵入性很大,需要在你的controoler中寫入 非業(yè)務(wù)代碼..
     * 2)配置不靈活 若需要添加新的受保護(hù)資源 需要手動添加 init方法來添加流控規(guī)則
     * @return
     */
    @RequestMapping("/helloSentinelV1")
    public String testHelloSentinelV1() {

        Entry entity =null;
        //關(guān)聯(lián)受保護(hù)的資源
        try {
            entity = SphU.entry("helloSentinelV1");
            //開始執(zhí)行 自己的業(yè)務(wù)方法
            busiService.doBusi();
            //結(jié)束執(zhí)行自己的業(yè)務(wù)方法
        } catch (BlockException e) {
            log.info("testHelloSentinelV1方法被流控了");
            return "testHelloSentinelV1方法被流控了";
        }finally {
            if(entity!=null) {
                entity.exit();
            }
        }
        return "OK";
    }
}

測試效果:http://localhost:8080/helloSentinelV1


V1版本的缺陷如下:

  • 業(yè)務(wù)侵入性很大,需要在你的controoler中寫入 非業(yè)務(wù)代碼.
  • 配置不靈活 若需要添加新的受保護(hù)資源 需要手動添加 init方法來添加流控規(guī)則

V2版本:基于V1版本咽安,再添加一個依賴

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.7.1</version>
</dependency>
  • 編寫controller
// 配置一個切面
@Configuration
public class SentinelConfig {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }

}

/**
 * 初始化流控規(guī)則
 */
@PostConstruct
public void init() {

    List<FlowRule> flowRules = new ArrayList<>();

    /**
     * 定義 helloSentinelV2 受保護(hù)的資源的規(guī)則
     */
    //創(chuàng)建流控規(guī)則對象
    FlowRule flowRule2 = new FlowRule();
    //設(shè)置流控規(guī)則 QPS
    flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //設(shè)置受保護(hù)的資源
    flowRule2.setResource("helloSentinelV2");
    //設(shè)置受保護(hù)的資源的閾值
    flowRule2.setCount(1);

    flowRules.add(flowRule2);
}

/**
 * 頻繁請求接口 http://localhost:8080/helloSentinelV2
 * 優(yōu)點(diǎn): 需要配置aspectj的切面SentinelResourceAspect ,添加注解@SentinelResource
 *     解決了v1版本中 sentinel的業(yè)務(wù)侵入代碼問題,通過blockHandler指定被流控后調(diào)用的方法.
 * 缺點(diǎn): 若我們的controller中的方法逐步變多,那么受保護(hù)的方法也越來越多,會導(dǎo)致一個問題
 * blockHandler的方法也會越來越多   引起方法急劇膨脹 怎么解決
 *
 * 注意點(diǎn):
 *   blockHandler 對應(yīng)處理 BlockException 的函數(shù)名稱蓬推,
 *   可選項妆棒。blockHandler 函數(shù)訪問范圍需要是 public,返回類型需要與原方法相匹配沸伏,
 *   參數(shù)類型需要和原方法相匹配并且最后加一個額外的參數(shù)糕珊,
 *   類型為 BlockException。blockHandler 函數(shù)默認(rèn)需要和原方法在同一個類中
 * @return
 */
@RequestMapping("/helloSentinelV2")
@SentinelResource(value = "helloSentinelV2",blockHandler ="testHelloSentinelV2BlockMethod")
public String testHelloSentinelV2() {
    busiService.doBusi();
    return "OK";
}

public String testHelloSentinelV2BlockMethod(BlockException e) {
    log.info("testRt流控");
    return "testRt降級 流控...."+e;
}

測試效果:http://localhost:8080/helloSentinelV2

V3版本 基于V2缺點(diǎn)改進(jìn)

/**
 * 初始化流控規(guī)則
 */
@PostConstruct
public void init() {

    List<FlowRule> flowRules = new ArrayList<>();

    /**
     * 定義 helloSentinelV3 受保護(hù)的資源的規(guī)則
     */
    //創(chuàng)建流控規(guī)則對象
    FlowRule flowRule3 = new FlowRule();
    //設(shè)置流控規(guī)則 QPS
    flowRule3.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //設(shè)置受保護(hù)的資源
    flowRule3.setResource("helloSentinelV3");
    //設(shè)置受保護(hù)的資源的閾值
    flowRule3.setCount(1);



    flowRules.add(flowRule3);
}

/**
 * 我們看到了v2中的缺點(diǎn),我們通過blockHandlerClass 來指定處理被流控的類
 * 通過testHelloSentinelV3BlockMethod 來指定blockHandlerClass 中的方法名稱
 * ***這種方式 處理異常流控的方法必須要是static的
 * 頻繁請求接口 http://localhost:8080/helloSentinelV3
 * @return
 */
@RequestMapping("/helloSentinelV3")
@SentinelResource(value = "helloSentinelV3",blockHandler = "testHelloSentinelV3BlockMethod",blockHandlerClass = BlockUtils.class)
public String testHelloSentinelV3() {
    busiService.doBusi();
    return "OK";
}

// 異常處理類
@Slf4j
public class BlockUtils {


    public static String testHelloSentinelV3BlockMethod(BlockException e){
        log.info("testHelloSentinelV3方法被流控了");
        return "testHelloSentinelV3方法被流控了";
    }
}

測試效果:http://localhost:8080/helloSentinelV3


缺點(diǎn):不能動態(tài)的添加規(guī)則毅糟。如何解決問題红选?

3.2 如何在工程中快速整合Sentinel

<!--加入sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>


<!--加入actuator-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

添加Sentinel后,會暴露/actuator/sentinel 端點(diǎn)http://localhost:8080/actuator/sentinel

而Springboot默認(rèn)是沒有暴露該端點(diǎn)的姆另,所以我們需要自己配置

server:
  port: 8080
management:
  endpoints:
    web:
      exposure:
        include: '*'

3.3 我們需要整合Sentinel-dashboard(哨兵流量衛(wèi)兵)

下載地址:https://github.com/alibaba/Sentinel/releases (我這里版本是:1.6.3)

  • 第一步:執(zhí)行 java -jar sentinel-dashboard-1.6.3.jar 啟動(就是一個SpringBoot工程)
  • 第二步:訪問我們的sentinel控制臺(1.6版本加入登陸頁面)http://localhost:8080/ 喇肋,默認(rèn)賬戶密碼:sentinel/sentinel
  • 第三步:我們的微服務(wù) niuh04-ms-alibaba-sentinel-order 整合 sentinel,我們也搭建好了Sentinel控制臺迹辐,為微服務(wù)添加sentinel的控制臺地址
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:9999

四蝶防、Sentinel監(jiān)控性能指標(biāo)詳解

4.1 實時監(jiān)控面板

在這個面板中我們監(jiān)控我們接口的 通過的QPS拒絕的QPS,在沒有設(shè)置流控規(guī)則明吩,我們是看不到拒絕的QPS间学。

4.2 簇點(diǎn)鏈路

用來線上微服務(wù)的所監(jiān)控的API


4.3 流控設(shè)置

簇點(diǎn)鏈路 選擇具體的訪問的API,然后點(diǎn)擊“流控按鈕


含義

  • 資源名:為我們接口的API /selectOrderInfoById/1
  • 針對來源:這里是默認(rèn)的 default(標(biāo)識不針對來源)印荔,還有一種情況就是假設(shè)微服務(wù)A需要調(diào)用這個資源菱鸥,微服務(wù)B也需要調(diào)用這個資源,那么我們就可以單獨(dú)的為微服務(wù)A和微服務(wù)B進(jìn)行設(shè)置閥值躏鱼。
  • 閥值類型:分為QPS和線程數(shù),假設(shè)閥值為2
    • QPS類型:指的是每秒鐘訪問接口的次數(shù) > 2 就進(jìn)行限流
    • 線程數(shù):為接受請求該資源殷绍,分配的線程數(shù) > 2 就進(jìn)行限流

流控模式

  1. 直接:這種很好理解染苛,就是達(dá)到設(shè)置的閥值后直接被流控拋出異常

瘋狂的請求這個路徑


  1. 關(guān)聯(lián)

業(yè)務(wù)場景:我們現(xiàn)在有兩個API,第一個是保存訂單主到,一個是查詢訂單茶行,假設(shè)我們希望有限操作“保存訂單


測試寫兩個讀寫測試接口

/**
 * 方法實現(xiàn)說明:模仿  流控模式【關(guān)聯(lián)】  讀接口
 * @author:hejianhui
 * @param orderNo
 * @return:
 * @exception:
 * @date:2019/11/24 22:06
 */
@RequestMapping("/findById/{orderNo}")
public Object findById(@PathVariable("orderNo") String orderNo) {
    log.info("orderNo:{}","執(zhí)行查詢操作"+System.currentTimeMillis());
    return orderInfoMapper.selectOrderInfoById(orderNo);
}


/**
 * 方法實現(xiàn)說明:模仿流控模式【關(guān)聯(lián)】   寫接口(優(yōu)先)
 * @author:hejianhui
 * @return:
 * @exception:
 * @date:2019/11/24 22:07
 */
@RequestMapping("/saveOrder")
public String saveOrder() throws InterruptedException {
    //Thread.sleep(500);
    log.info("執(zhí)行保存操作,模仿返回訂單ID");
    return UUID.randomUUID().toString();
}

測試代碼:寫一個for循環(huán)一直調(diào)用我們的寫接口,讓寫接口QPS達(dá)到閥值

public class TestSentinelRule {

    public static void main(String[] args) throws InterruptedException {
        RestTemplate restTemplate = new RestTemplate();
        for(int i=0;i<1000;i++) {
            restTemplate.postForObject("http://localhost:8080/saveOrder",null,String.class);
            Thread.sleep(10);
        }
    }
}

此時訪問我們的讀接口:此時被限流了登钥。


  1. 鏈路

用法說明畔师,本地實驗沒成功,用alibaba 未畢業(yè)版本0.9.0可以測試出效果牧牢,API級別的限制流量


代碼:

@RequestMapping("/findAll")
public String findAll() throws InterruptedException {
    orderServiceImpl.common();
    return "findAll";
}

@RequestMapping("/findAllByCondtion")
public String findAllByCondtion() {
    orderServiceImpl.common();
    return "findAllByCondition";
}

@Service
public class OrderServiceImpl {

    @SentinelResource("common")
    public String common() {
        return "common";
    }
}

根據(jù)流控規(guī)則來說: 只會限制/findAll的請求看锉,不會限制/findAllByCondtion規(guī)則

流控效果

  1. 快速失敗(直接拋出異常)每秒的QPS 操作過1 就直接拋出異常

源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController


  1. 預(yù)熱(warmUp)
    源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController>

當(dāng)流量突然增大的時候姿锭,我們常常會希望系統(tǒng)從空閑狀態(tài)到繁忙狀態(tài)的切換的時間長一些。即如果系統(tǒng)在此之前長期處于空閑的狀態(tài)伯铣,我們希望處理請求的數(shù)量是緩步增加呻此,經(jīng)過預(yù)期的時間后,到達(dá)系統(tǒng)處理請求個數(shù)的最大值腔寡。Warm Up (冷啟動焚鲜,預(yù)熱)模式就是為了實現(xiàn)這個目的。

冷加載因子:codeFacotr 默認(rèn)是3

  • 默認(rèn) coldFactor 為3放前,即請求 QPS 從 threshold / 3 開始忿磅,經(jīng)預(yù)熱時長逐漸升至設(shè)定的 QPS 閥值。


上圖設(shè)置:就是QPS從100/3=33開始算凭语, 經(jīng)過10秒鐘葱她,達(dá)到一百的QPS 才進(jìn)行限制流量。

詳情文檔:https://github.com/alibaba/Sentinel/wiki/限流---冷啟動

  1. 排隊等待
    源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

這種方式適合用于請求以突刺狀來到叽粹,這個時候我們不希望一下子把所有的請求都通過览效,這樣可能會把系統(tǒng)壓垮;同時我們也期待系統(tǒng)以穩(wěn)定的速度虫几,逐步處理這些請求锤灿,以起到“削峰填谷”的效果,而不是拒絕所有請求辆脸。

選擇排隊等待的閥值類型必須是****QPS


上圖設(shè)置:單機(jī)閥值為10但校,表示每秒通過的請求個數(shù)是10,也就是每個請求平均間隔恒定為 1000 / 10 = 100 ms啡氢,每一個請求的最長等待時間(maxQueueingTimeMs)為 20 * 1000ms = 20s状囱。,超過20s就丟棄請求倘是。

詳情文檔:https://github.com/alibaba/Sentinel/wiki/流量控制-勻速排隊模式

4.4 降級規(guī)則

rt(平均響應(yīng)時間)


平均響應(yīng)時間(DEGRADE_GRADE_RT):當(dāng) 1s 內(nèi)持續(xù)進(jìn)入5個請求亭枷,對應(yīng)時刻的平均響應(yīng)時間(秒級)均超過閥值(count,以 ms 為單位)搀崭,那么在接下來的時間窗口(DegradeRule 中的 timeWindow叨粘,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地熔斷(拋出 DegradeException)瘤睹。

注意:Sentinel 默認(rèn)同級的 RT 上限是4900ms升敲,超出此閥值都會算做4900ms,若需要變更此上限可以通過啟動配置項:-Dcsp.sentinel.statistic.max.rt=xxx 來配置

異常比例(DEGRADE_GRADE_EXCEPTION_RATIO)

當(dāng)資源的每秒請求量 >= 5轰传,并且每秒異陈康常總數(shù)占通過量的比值超過閥值(DegradeRule 中的 count)之后,資源進(jìn)入降級狀態(tài)获茬,即在接下的時間窗口(DegradeRule 中的 timeWindow港庄,以 s 為單位)之內(nèi)倔既,對這個方法的調(diào)用都會自動地返回。異常比例的閥值范圍是 [0.0, 1.0]攘轩,代表 0% ~ 100% 叉存。

異常數(shù)(DEGRADE_GRADE_EXCEPTION_COUNT)

當(dāng)資源近千分之的異常數(shù)目超過閥值之后會進(jìn)行熔斷。注意由于統(tǒng)計時間窗口是分鐘級別的度帮,若 timeWindow 小于 60s歼捏,則結(jié)束熔斷狀態(tài)后仍可能再進(jìn)入熔斷狀態(tài)。


4.5 熱點(diǎn)參數(shù)

業(yè)務(wù)場景:秒殺業(yè)務(wù)笨篷,比如商場做促銷秒殺瞳秽,針對蘋果11(商品id=1)進(jìn)行9.9秒殺活動,那么這個時候率翅,我們?nèi)フ埱笥唵谓涌冢ㄉ唐穒d=1)的請求流量十分大练俐,我們就可以通過熱點(diǎn)參數(shù)規(guī)則來控制 商品id=1 的請求的并發(fā)量。而其他正常商品的請求不會受到限制冕臭。那么這種熱點(diǎn)參數(shù)規(guī)則使用腺晾。


五、Sentinel-dashboard 控制臺 和 我們的微服務(wù)通信原理

5.1 控制臺如何獲取到微服務(wù)的監(jiān)控信息辜贵?

5.2 在控制臺配置規(guī)則悯蝉,如何把規(guī)則推送給微服務(wù)的?


我們通過觀察到sentinel-dashboard的機(jī)器列表上觀察注冊服務(wù)微服務(wù)信息托慨。我們的 控制臺就可以通過這些微服務(wù)的注冊信息跟我們的具體的微服務(wù)進(jìn)行通信.


5.3 微服務(wù)整合sentinel時候的提供的一些接口API地址: http://localhost:8720/api

5.4 我們可以通過代碼設(shè)置規(guī)則(我們這里用流控規(guī)則為例)

@RestController
public class AddFlowLimitController {

    @RequestMapping("/addFlowLimit")
    public String addFlowLimit() {
        List<FlowRule> flowRuleList = new ArrayList<>();

        FlowRule flowRule = new FlowRule("/testAddFlowLimitRule");

        //設(shè)置QPS閾值
        flowRule.setCount(1);

        //設(shè)置流控模型為QPS模型
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);

        flowRuleList.add(flowRule);

        FlowRuleManager.loadRules(flowRuleList);

        return "success";

    }

    @RequestMapping("/testAddFlowLimitRule")
    public String testAddFlowLimitRule() {
        return "testAddFlowLimitRule";
    }
}

添加效果截圖: 執(zhí)行:http://localhost:8080/addFlowLimit


Sentinel具體配置項:https://github.com/alibaba/Sentinel/wiki/啟動配置項

5.5 對SpringMVC端點(diǎn)保護(hù)關(guān)閉(一般應(yīng)用場景是做壓測需要關(guān)閉)

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:9999
      filter:
        enabled: true  #關(guān)閉Spring mvc的端點(diǎn)保護(hù)

那么我們的這種類型的接口 不會被sentinel保護(hù)


只有加了 @SentinelResource 的注解的資源才會被保護(hù)

六鼻由、Ribbon整合Sentinel

6.1 第一步:加配置

<!--加入ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

<!--加入sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>


<!--加入actuator-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

6.2 第二步:加注解

在我們的RestTemplate組件上添加@SentinelRestTemplate注解。并且我們可以通過在@SentinelRestTemplate 同樣的可以指定我們的 blockHandlerClass厚棵、fallbackClass蕉世、blockHandler、fallback 這四個屬性

@Configuration
public class WebConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(
            blockHandler = "handleException",blockHandlerClass = GlobalExceptionHandler.class,
            fallback = "fallback",fallbackClass = GlobalExceptionHandler.class

    )
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

*****************全局異常處理類*****************
@Slf4j
public class GlobalExceptionHandler {


    /**
     * 限流后處理方法
     * @param request
     * @param body
     * @param execution
     * @param ex
     * @return
     */
    public static SentinelClientHttpResponse handleException(HttpRequest request,
                                                             byte[] body, ClientHttpRequestExecution execution, BlockException ex)  {

        ProductInfo productInfo = new ProductInfo();
        productInfo.setProductName("被限制流量拉");
        productInfo.setProductNo("-1");
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            return new SentinelClientHttpResponse(objectMapper.writeValueAsString(productInfo));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 熔斷后處理的方法
     * @param request
     * @param body
     * @param execution
     * @param ex
     * @return
     */
    public static SentinelClientHttpResponse fallback(HttpRequest request,
                                                      byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setProductName("被降級拉");
        productInfo.setProductNo("-1");
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            return new SentinelClientHttpResponse(objectMapper.writeValueAsString(productInfo));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

6.3 第三步:添加配置

什么時候關(guān)閉:一般在我們的自己測試業(yè)務(wù)功能是否正常的情況婆硬,關(guān)閉該配置

#是否開啟@SentinelRestTemplate注解
resttemplate:
  sentinel:
    enabled: true

七狠轻、OpenFeign整合我們的Sentinel

7.1 第一步:加配置

在niuh05-ms-alibaba-feignwithsentinel-order上 pom.xml中添加配置

<!--加入sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>


<!--加入actuator-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>com.niuh</groupId>
    <artifactId>niuh03-ms-alibaba-feign-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

7.2 第二步:在Feign的聲明式接口上添加fallback屬性或者 fallbackFactory屬性

  • 為我們添加fallback屬性的api
@FeignClient(name = "product-center",fallback = ProductCenterFeignApiWithSentinelFallback.class)
public interface ProductCenterFeignApiWithSentinel {

    /**
     * 聲明式接口,遠(yuǎn)程調(diào)用http://product-center/selectProductInfoById/{productNo}
     * @param productNo
     * @return
     */
    @RequestMapping("/selectProductInfoById/{productNo}")
    ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo) throws InterruptedException;
}

我們feign的限流降級接口(通過fallback沒有辦法獲取到異常的)

@Component
public class ProductCenterFeignApiWithSentinelFallback implements ProductCenterFeignApiWithSentinel {
    @Override
    public ProductInfo selectProductInfoById(String productNo) {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setProductName("默認(rèn)商品");
        return productInfo;
    }
}
  • 為我們添加fallbackFactory屬性的api
package com.niuh.feignapi.sentinel;

import com.niuh.entity.ProductInfo;
import com.niuh.handler.ProductCenterFeignApiWithSentielFallbackFactoryasdasf;
import com.niuh.handler.ProductCenterFeignApiWithSentinelFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Created by hejianhui on 2019/11/22.
 */

@FeignClient(name = "product-center",fallbackFactory = ProductCenterFeignApiWithSentielFallbackFactoryasdasf.class)
public interface ProductCenterFeignApiWithSentinel {

    /**
     * 聲明式接口,遠(yuǎn)程調(diào)用http://product-center/selectProductInfoById/{productNo}
     * @param productNo
     * @return
     */
    @RequestMapping("/selectProductInfoById/{productNo}")
    ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo) throws InterruptedException;
}

通過FallbackFactory屬性可以處理我們的異常

@Component
@Slf4j
public class ProductCenterFeignApiWithSentielFallbackFactoryasdasf implements FallbackFactory<ProductCenterFeignApiWithSentinel> {
    @Override
    public ProductCenterFeignApiWithSentinel create(Throwable throwable) {
        return new ProductCenterFeignApiWithSentinel(){

            @Override
            public ProductInfo selectProductInfoById(String productNo) {
                ProductInfo productInfo = new ProductInfo();
                if (throwable instanceof FlowException) {
                    log.error("流控了....{}",throwable.getMessage());
                    productInfo.setProductName("我是被流控的默認(rèn)商品");
                }else {
                    log.error("降級了....{}",throwable.getMessage());
                    productInfo.setProductName("我是被降級的默認(rèn)商品");
                }

                return productInfo;
            }
        };
    }
}

八、Sentinel 規(guī)則持久化

Sentinel-dashboard 配置的規(guī)則彬犯,在我們的微服務(wù)以及控制臺重啟的時候就清空了向楼,因為它是基于內(nèi)存的。


8.1 原生模式

Dashboard 的推送規(guī)則方式是通過 API 將規(guī)則推送至客戶端并直接更新到內(nèi)存躏嚎。


優(yōu)缺點(diǎn):這種做法的好處是簡單,無依賴菩貌;壞處是應(yīng)用重啟規(guī)則就會消失卢佣,僅用于簡單測試,不能用于生產(chǎn)環(huán)境箭阶。

8.2 Pull拉模式


首先 Sentinel 控制臺通過 API 將規(guī)則推送至客戶端并更新到內(nèi)存中虚茶,接著注冊的寫數(shù)據(jù)源會將新的規(guī)則保存到本地的文件中戈鲁。使用 pull 模式的數(shù)據(jù)源時一般不需要對 Sentinel 控制臺進(jìn)行改造。

這種實現(xiàn)方法好處是簡單嘹叫,不引入新的依賴婆殿,壞處是無法保證監(jiān)控數(shù)據(jù)的一致性

客戶端Sentinel的改造(拉模式)

通過SPI擴(kuò)展機(jī)制進(jìn)行擴(kuò)展,我們寫一個拉模式的實現(xiàn)類 com.niuh.persistence.PullModeByFileDataSource 罩扇,然后在工廠目錄下創(chuàng)建 META-INF/services/com.alibaba.csp.sentinel.init.InitFun文件婆芦。



文件的內(nèi)容就是寫我們的拉模式的實現(xiàn)類:


代碼在niuh05-ms-alibaba-sentinelrulepersistencepull-order 工程的persistence包下。

8.3 Push推模式(以Nacos為例喂饥,生產(chǎn)推薦使用)

原理簡述

  • 控制臺推送規(guī)則:
    • 將規(guī)則推送至Nacos獲取其他遠(yuǎn)程配置中心
    • Sentinel客戶端連接Nacos消约,獲取規(guī)則配置;并監(jiān)聽Nacos配置變化员帮,如果發(fā)送變化或粮,就更新本地緩存(從而讓本地緩存總是和Nacos一致)
  • 控制臺監(jiān)聽Nacos配置變化,如果發(fā)送變化就更新本地緩存(從而讓控制臺本地緩存和Nacos一致)

改造方案

微服務(wù)改造方案

  • 第一步:在niuh05-ms-alibaba-sentinelrulepersistencepush-order工程加入依賴
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • 第二步:加入yml的配置
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:9999
        #namespace: bc7613d2-2e22-4292-a748-48b78170f14c  #指定namespace的id
      datasource:
        # 名稱隨意
        flow:
          nacos:
            server-addr: 47.111.191.111:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow
        degrade:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            rule-type: degrade
        system:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            rule-type: system
        authority:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            rule-type: authority
        param-flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: param-flow

Sentinel-dashboard改造方案

<!-- for Nacos rule publisher sample -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
   <!-- <scope>test</scope>--> // 需要把test注釋掉
</dependency>

控制臺改造主要是為規(guī)則實現(xiàn):

  • DynamicRuleProvider :從Nacos上讀取配置
  • DynamicRulePublisher :將規(guī)則推送到Nacis上

在sentinel-dashboard工程目錄com.alibaba.csp.sentinel.dashboard.rule 下創(chuàng)建一 個Nacos的包捞高,然后把我們的各個場景的配置規(guī)則類寫到該包下.



我們以ParamFlowRuleController(熱點(diǎn)參數(shù)流控類作為修改作為演示)

/**
 * @author Eric Zhao
 * @since 0.2.1
 */
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {

    private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);

    @Autowired
    private SentinelApiClient sentinelApiClient;
    @Autowired
    private AppManagement appManagement;
    @Autowired
    private RuleRepository<ParamFlowRuleEntity, Long> repository;

    @Autowired
    @Qualifier("niuhHotParamFlowRuleNacosPublisher")
    private DynamicRulePublisher<List<ParamFlowRuleEntity>> rulePublisher;

    @Autowired
    @Qualifier("niuhHotParamFlowRuleNacosProvider")
    private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;

    @Autowired
    private AuthService<HttpServletRequest> authService;

    private boolean checkIfSupported(String app, String ip, int port) {
        try {
            return Optional.ofNullable(appManagement.getDetailApp(app))
                .flatMap(e -> e.getMachine(ip, port))
                .flatMap(m -> VersionUtils.parseVersion(m.getVersion())
                    .map(v -> v.greaterOrEqual(version020)))
                .orElse(true);
            // If error occurred or cannot retrieve machine info, return true.
        } catch (Exception ex) {
            return true;
        }
    }

    @GetMapping("/rules")
    public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(HttpServletRequest request,
                                                                        @RequestParam String app,
                                                                        @RequestParam String ip,
                                                                        @RequestParam Integer port) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(app, PrivilegeType.READ_RULE);
        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app cannot be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip cannot be null or empty");
        }
        if (port == null || port <= 0) {
            return Result.ofFail(-1, "Invalid parameter: port");
        }
        if (!checkIfSupported(app, ip, port)) {
            return unsupportedVersion();
        }
        try {
/*            return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
                .thenApply(repository::saveAll)
                .thenApply(Result::ofSuccess)
                .get();*/
            List<ParamFlowRuleEntity> rules = ruleProvider.getRules(app);
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (ExecutionException ex) {
            logger.error("Error when querying parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when querying parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    private boolean isNotSupported(Throwable ex) {
        return ex instanceof CommandNotFoundException;
    }

    @PostMapping("/rule")
    public Result<ParamFlowRuleEntity> apiAddParamFlowRule(HttpServletRequest request,
                                                           @RequestBody ParamFlowRuleEntity entity) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(entity.getApp(), PrivilegeType.WRITE_RULE);
        Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
            return unsupportedVersion();
        }
        entity.setId(null);
        entity.getRule().setResource(entity.getResource().trim());
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            //publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (ExecutionException ex) {
            logger.error("Error when adding new parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when adding new parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
        if (entity == null) {
            return Result.ofFail(-1, "bad rule body");
        }
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getIp())) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (entity.getPort() == null || entity.getPort() <= 0) {
            return Result.ofFail(-1, "port can't be null");
        }
        if (entity.getRule() == null) {
            return Result.ofFail(-1, "rule can't be null");
        }
        if (StringUtil.isBlank(entity.getResource())) {
            return Result.ofFail(-1, "resource name cannot be null or empty");
        }
        if (entity.getCount() < 0) {
            return Result.ofFail(-1, "count should be valid");
        }
        if (entity.getGrade() != RuleConstant.FLOW_GRADE_QPS) {
            return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control");
        }
        if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
            return Result.ofFail(-1, "paramIdx should be valid");
        }
        if (entity.getDurationInSec() <= 0) {
            return Result.ofFail(-1, "durationInSec should be valid");
        }
        if (entity.getControlBehavior() < 0) {
            return Result.ofFail(-1, "controlBehavior should be valid");
        }
        return null;
    }

    @PutMapping("/rule/{id}")
    public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(HttpServletRequest request,
                                                              @PathVariable("id") Long id,
                                                              @RequestBody ParamFlowRuleEntity entity) {
        AuthUser authUser = authService.getAuthUser(request);
        if (id == null || id <= 0) {
            return Result.ofFail(-1, "Invalid id");
        }
        ParamFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofFail(-1, "id " + id + " does not exist");
        }
        authUser.authTarget(oldEntity.getApp(), PrivilegeType.WRITE_RULE);
        Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
            return unsupportedVersion();
        }
        entity.setId(id);
        Date date = new Date();
        entity.setGmtCreate(oldEntity.getGmtCreate());
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            //publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (ExecutionException ex) {
            logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when updating parameter flow rules, id=" + id, throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    @DeleteMapping("/rule/{id}")
    public Result<Long> apiDeleteRule(HttpServletRequest request, @PathVariable("id") Long id) {
        AuthUser authUser = authService.getAuthUser(request);
        if (id == null) {
            return Result.ofFail(-1, "id cannot be null");
        }
        ParamFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }
        authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE);
        try {
            repository.delete(id);
            /*publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get();*/
            publishRules(oldEntity.getApp());
            return Result.ofSuccess(id);
        } catch (ExecutionException ex) {
            logger.error("Error when deleting parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when deleting parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
        List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
    }

    private void publishRules(String app) throws Exception {
        List<ParamFlowRuleEntity> rules = repository.findAllByApp(app);
        rulePublisher.publish(app, rules);
    }

    private <R> Result<R> unsupportedVersion() {
        return Result.ofFail(4041,
            "Sentinel client not supported for parameter flow control (unsupported version or dependency absent)");
    }

    private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}

8.4 阿里云的 AHAS

第一步:訪問 https://help.aliyun.com/document_detail/90323.html


第二步:免費(fèi)開通

第三步:開通

第四步:接入應(yīng)用

第五步:點(diǎn)擊接入SDK

第六步:加入我們的應(yīng)用

以niuh05-ms-alibaba-sentinelrulepersistence-ahas-order工程為例

  • 加入ahas的依賴
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>spring‐boot‐starter‐ahas‐sentinel‐client</artifactId> 4 <version>1.5.0</version>
</dependency>
  • 加入配置:yml的配置
ahas.namespace: default
project.name: order-center
ahas.license: b833de8ab5f34e4686457ecb2b60fa46
  • 測試接口
@SentinelResource("hot-param-flow-rule")
@RequestMapping("/testHotParamFlowRule")
public OrderInfo testHotParamFlowRule(@RequestParam("orderNo") String orderNo) {
    return orderInfoMapper.selectOrderInfoById(orderNo);
}

第一次訪問接口:

AHas控制臺出現(xiàn)我們的微服務(wù)

添加我們直接的流控規(guī)則

瘋狂刷新我們的測試接口:

九氯材、Sentinel 線上環(huán)境的優(yōu)化

9.1 優(yōu)化錯誤頁面

  • 流控錯誤頁面
  • 降級錯誤頁面


發(fā)現(xiàn)這兩種錯誤都是醫(yī)院,顯然這里我們需要優(yōu)化 UrlBlockHandler 提供了一個接口硝岗,我們需要實現(xiàn)這個接口

/**
* @vlog: 高于生活氢哮,源于生活
* @desc: 類的描述:處理流控,降級規(guī)則
* @author: hejianhui
* @createDate: 2019/12/3 16:40
* @version: 1.0
*/
@Component
public class NiuhUrlBlockHandler implements UrlBlockHandler {

    public static final Logger log = LoggerFactory.getLogger(NiuhUrlBlockHandler.class);

    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {

        if(ex instanceof FlowException) {
            log.warn("觸發(fā)了流控");
            warrperResponse(response,ErrorEnum.FLOW_RULE_ERR);
        }else if(ex instanceof ParamFlowException) {
            log.warn("觸發(fā)了參數(shù)流控");
            warrperResponse(response,ErrorEnum.HOT_PARAM_FLOW_RULE_ERR);
        }else if(ex instanceof AuthorityException) {
            log.warn("觸發(fā)了授權(quán)規(guī)則");
            warrperResponse(response,ErrorEnum.AUTH_RULE_ERR);
        }else if(ex instanceof SystemBlockException) {
            log.warn("觸發(fā)了系統(tǒng)規(guī)則");
            warrperResponse(response,ErrorEnum.SYS_RULE_ERR);
        }else{
            log.warn("觸發(fā)了降級規(guī)則");
            warrperResponse(response,ErrorEnum.DEGRADE_RULE_ERR);
        }
    }


    private void warrperResponse(HttpServletResponse httpServletResponse, ErrorEnum errorEnum) throws IOException {
        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
        httpServletResponse.setContentType("application/json;charset=utf-8");

        ObjectMapper objectMapper = new ObjectMapper();
        String errMsg =objectMapper.writeValueAsString(new ErrorResult(errorEnum));
        httpServletResponse.getWriter().write(errMsg);
    }

}

優(yōu)化后:

  • 流控規(guī)則提示
  • 降級規(guī)則提示

9.2 針對來源編碼實現(xiàn)


Sentinel 提供了一個 RequestOriginParser 接口,我們可以在這里實現(xiàn)編碼從請求頭中區(qū)分來源

/**
* @vlog: 高于生活辈讶,源于生活
* @desc: 類的描述:區(qū)分來源接口
* @author: hejianhui
* @createDate: 2019/12/4 13:13
* @version: 1.0
*/
/*@Component*/
@Slf4j
public class NiuhRequestOriginParse implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getHeader("origin");
        if(StringUtils.isEmpty(origin)) {
            log.warn("origin must not null");
            throw new IllegalArgumentException("request origin must not null");
        }
        return origin;
    }
}

配置設(shè)置區(qū)分來源為:yijiaoqian



9.3 解決RestFul風(fēng)格的請求

例如:/selectOrderInfoById/2 命浴、 /selectOrderInfoById/1 需要轉(zhuǎn)為/selectOrderInfoById/{number}

/**
* @vlog: 高于生活,源于生活
* @desc: 類的描述:解決RestFule風(fēng)格的請求
 *       eg:    /selectOrderInfoById/2     /selectOrderInfoById/1 需要轉(zhuǎn)為/selectOrderInfoById/{number}
* @author: hejianhui
* @createDate: 2019/12/4 13:28
* @version: 1.0
*/
@Component
@Slf4j
public class NiuhUrlClean implements UrlCleaner {
    @Override
    public String clean(String originUrl) {
        log.info("originUrl:{}",originUrl);

        if(StringUtils.isEmpty(originUrl)) {
            log.error("originUrl not be null");
            throw new IllegalArgumentException("originUrl not be null");
        }
        return replaceRestfulUrl(originUrl);
    }

    /**
     * 方法實現(xiàn)說明:把/selectOrderInfoById/2 替換成/selectOrderInfoById/{number}
     * @author:hejianhui
     * @param sourceUrl 目標(biāo)url
     * @return: 替換后的url
     * @exception:
     * @date:2019/12/4 13:46
     */
    private String replaceRestfulUrl(String sourceUrl) {
        List<String> origins = Arrays.asList(sourceUrl.split("/"));
        StringBuffer targetUrl = new StringBuffer("/");

        for(String str:origins) {
            if(NumberUtils.isNumber(str)) {
                targetUrl.append("/{number}");
            }else {
                targetUrl.append(str);
            }

        }
        return targetUrl.toString();
    }
}

PS:以上代碼提交在 Githubhttps://github.com/Niuh-Study/niuh-cloud-alibaba.git

文章持續(xù)更新贱除,可以公眾號搜一搜「 一角錢技術(shù) 」第一時間閱讀生闲, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄,歡迎 Star月幌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碍讯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扯躺,更是在濱河造成了極大的恐慌捉兴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件录语,死亡現(xiàn)場離奇詭異倍啥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)澎埠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門虽缕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蒲稳,你說我怎么就攤上這事氮趋∥榕桑” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵剩胁,是天一觀的道長诉植。 經(jīng)常有香客問我,道長昵观,這世上最難降的妖魔是什么晾腔? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮索昂,結(jié)果婚禮上建车,老公的妹妹穿的比我還像新娘。我一直安慰自己椒惨,他們只是感情好缤至,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著康谆,像睡著了一般领斥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沃暗,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天谓松,我揣著相機(jī)與錄音内列,去河邊找鬼敬矩。 笑死引有,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惜辑。 我是一名探鬼主播唬涧,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盛撑!你這毒婦竟也來了碎节?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抵卫,失蹤者是張志新(化名)和其女友劉穎狮荔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體介粘,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殖氏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姻采。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雅采。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出总滩,到底是詐尸還是另有隱情,我是刑警寧澤巡雨,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布闰渔,位于F島的核電站,受9級特大地震影響铐望,放射性物質(zhì)發(fā)生泄漏冈涧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一正蛙、第九天 我趴在偏房一處隱蔽的房頂上張望督弓。 院中可真熱鬧,春花似錦乒验、人聲如沸愚隧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狂塘。三九已至,卻和暖如春鳄厌,著一層夾襖步出監(jiān)牢的瞬間荞胡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工了嚎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泪漂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓歪泳,卻偏偏與公主長得像萝勤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子夹囚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容