Dubbo學習系列之十(Sentinel之限流與降級)

做者原創文章,謝絕任何形式轉載,不然追究法律責任! java

 

各位看官,先提個問題,若是讓你設計一套秒殺系統,核心要點是啥???我認爲有三點:緩存、限流和分離。想當年12306大面積崩潰,還有現在的微博總體宕機狀況,git

感受就是限流降級沒作好,"用有限的資源響應過量請求"——這就是限流降級的核心。限流降級組件,當今開源界應該是Hystrix最爲出名,這也得益於SpringCloud的流github

行,固然,挑戰者老是有的,因而Sentinel橫空出世,正因實際生產使用中彷佛並很少見,因此纔有必要拿來一用,否則就脫離了此係列文章的主旨了,就是要見些不同的風景!redis

 

工具:Idea201902/JDK11/ZK3.5.5/Gradle5.4.1/RabbitMQ3.7.13/Mysql8.0.11/Lombok0.26/Erlang21.2/postman7.5.0/Redis3.2/RocketMQ4.5.2/Sentinel1.6.3/SpringBoot2.1.6spring

難度: 新手--戰士--老兵--大師sql

目標緩存

  1. 使用Sentinel實現交易業務特定方法的限流微信

  2. AOP註解模式實現交易業務方法限流降級架構

  3. 使用Sentinel實現受權模式控制併發

步驟

1.總體框架依舊,多模塊微服務框架商城系統,一個共享模塊,多個功能模塊。

2.先說幾個Sentinel基本概念:

  • 資源Resource,能夠是任意對象,一個字符串、一個方法,一個類對象;

  • 規則Rule,須要如何限制或者降級,好比按照「QPS/失敗比率」作出相應的處理;

  • 入口Entry,每次資源調用都會建立一個Entry對象,Entry 建立的時候,同時也會建立一系列功能插槽(slot chain),鏈裏面的slot各司其職,好比其中的FlowSlot 則用於根據預設的限流規則以及前面 slot 統計的狀態,來進行流量控制,DegradeSlot 則經過統計信息以及預設的規則,來作熔斷降級;AuthoritySlot 則根據配置的黑白名單和調用來源信息,來作黑白名單控制;

3.引入的依賴項目:

// 引入此依賴後,Dubbo 的服務接口和方法(包括調用端和服務端)就會成爲 Sentinel 中的資源
compile group: 'com.alibaba.csp', name: 'sentinel-dubbo-adapter', version: '1.6.3'
// Sentinel 控制檯依賴,
compile group: 'com.alibaba.csp', name: 'sentinel-transport-simple-http', version: '1.6.3'
// compile group: 'com.alibaba.csp', name: 'sentinel-core', version: '1.6.3'
// 可使用URL:查詢規則:http://localhost:8083/actuator/sentinel
compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '2.1.6.RELEASE'
// https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-annotation-aspectj
compile group: 'com.alibaba.csp', name: 'sentinel-annotation-aspectj', version: '1.6.3'
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-sentinel
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-alibaba-sentinel', version: '0.9.0.RELEASE'

 

4.Sentinel的使用方式都是:定義資源——定義規則——適配規則,先看個最基本的樣例:com.biao.mall.business.controller.SentinelTestController中,方式很完整,定義一個字符串爲資源,initFlowQpsRule定義規則,QPS限制爲2(即<2),testSentinel方法中使用規則。

@RestController public class SentinelTestController { private String resourceName = "testSentinel"; @GetMapping("/testSentinel") public String testSentinel(){ initFlowQpsRule(); Entry entry = null; String retVal; try{ entry = SphU.entry(resourceName,EntryType.IN); retVal = "passed"; }catch(BlockException e){ retVal="block"; }finally{ if (entry != null){ entry.exit(); } } return retVal; } private void initFlowQpsRule() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule1 = new FlowRule(); rule1.setResource(resourceName); // set limit qps to 2
        rule1.setCount(2); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); } }

 

5.測試運行一下,先啓動各組件(zk-->redis-->Rabbit-->Rocket):快速刷新幾回,就顯示限流特徵了!

 

6.再看第2個目標實現,註解模式,這裏我先只改動business模塊:com.biao.mall.business.controller.DubboOrderController中,實現目標對saveOrder方法進行限流,對比以前版本,變化不大,只是先initFlowQpsRule初始化規則,而後再初始化一個Entry ,這裏使用了try-with-resource語法糖,固然也可以使用try-catch-finally語法,參數EntryType.IN表示監視「進入流量」,這裏的"saveOrder"是資源的名稱,具體定義請見第7點,

@RequestMapping(value = "/order",method = RequestMethod.POST) public ResEntity<String> saveOrder(@RequestBody OrderBO orderBO ) throws Exception { logger.debug(orderBO.toString()); initFlowQpsRule("saveOrder"); try(Entry entry = SphU.entry("saveOrder",EntryType.IN)) { //存未付款訂單
 orderService.saveOrder(orderBO); //響應封裝
            ResEntity<String> resEntity = new ResEntity<>(); resEntity.setCode(ResConstant.SUCCESS_CODE); resEntity.setMsg(ResConstant.SUCCESS_STRING); resEntity.setData("order saved"); return resEntity; } }

 

進一步,看初始化規則initFlowQpsRule方法:生成一個規則鏈,每一個資源能夠對應多個規則,sentinel將遍歷匹配每一個規則,具體做用見代碼中的註釋:

private void initFlowQpsRule(String resourceName) { List<FlowRule> rules = new ArrayList<>(); FlowRule rule1 = new FlowRule(); //定義資源,resourceName只能是String類型
 rule1.setResource(resourceName); // set limit QPS
        rule1.setCount(2); //流控制的門檻類型
 rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); //設置應用的名稱,
        rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); }

這裏爲了測試效果,我將QPS設置較小,其參數爲double類型,應設置爲>1,不然測試時會直接流量拒絕,由於其按每秒計數。setGrade是設置流量控制的門檻類型0: thread count, 1: QPS,即常量值爲0按照併發線程數標準,爲1按照QPS標準;

7.再看com.biao.mall.business.impl.DubboOrderServiceImpl,其中的saveOrder方法,這裏使用了AOP模式,使用了引入的sentinel-annotation-aspectj依賴,其實就是sentinel對AspectJ作了封裝處理,這樣註解後,saveOrder方法即成了一個resource,就對應上了DubboOrderController中"saveOrder"資源的名稱,同時還指定了blockHandler,即對應處理 BlockException 的函數名稱。fallback,用於在拋出異常的時候提供 fallback 處理邏輯。

 @Override @Transactional @SentinelResource(value = "saveOrder", blockHandler = "saveOrderExHandler", fallback = "saveOrderFallback") public boolean saveOrder(OrderBO orderBO) throws Exception {...}

這裏注意兩點:

  • 若但願使用其餘類的函數,則能夠在SentinelResource註解參數中指定 blockHandlerClass 爲對應的類的 Class 對象,注意對應的函數必須爲 static 函數,不然沒法解析,實例見payOrder方法上的註解模式

@Override @Transactional @SentinelResource(value = "payOrder", blockHandler = "PayOrderExHandler",blockHandlerClass = {ExceptionUtil.class}) public boolean payOrder(String orderId) throws MQClientException, UnsupportedEncodingException {...}
  • 若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandler、fallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出

8.再看下定義的blockHandler 和 fallback 方法,比較簡單,真實業務系統確定會作些其餘處理,好比顯示靜態內容,提示些用戶友好型信息等。

// Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數.
    public String saveOrderFallback(long s) { logger.info("saveOrderFallback"); return String.format("saveOrderFallback %d", s); } // BlockHandle 異常處理函數,參數最後多一個 BlockException,其他與原函數一致.
    public String saveOrderExHandler(long s, BlockException ex) { logger.error("saveOrderExHandler"); ex.printStackTrace(); return "Oops, error occurred at saveOrder" + s; }

9.測試:啓動ZK-->Redis-->RabbitMQ-->RocketMQ-->各模塊-->postman, 快速點擊send按鈕3次,就發現報錯500:

 

 同時sentinelDashboard(下篇文章)的監控圖:

 

 

10.實現受權模式,先修改下business模中RPC調用的stockService.updateStockRPC(stockEntity)方法,其中的ContextUtil.enter(resourceName,"mall-business")模擬了請求應用名爲「mall-business」:

@Override @Transactional public int updateStockRPC(DubboStockEntity stockEntity) throws BlockException { String resourceName = "updateStockRPC"; this.initWhiteRules(resourceName); //獲取app來源
        ContextUtil.enter(resourceName,"mall-business"); try(Entry entry = SphU.entry(resourceName)){ if (Objects.equals(null, stockEntity)) { return -1; } return dubboStockDao.updateById(stockEntity); } }

再看initWhiteRules方法:即只容許應用名爲 appA/appE 的應用請求經過,

/**白名單規則*/
    private void initWhiteRules(String resourceName){ AuthorityRule rule = new AuthorityRule(); rule.setResource(resourceName); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); rule.setLimitApp("appA,appE"); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); }

測試效果以下:

 

 同時sentinelDashboard也能夠看到拒絕的請求:

 

11.經測試,受權模式(黑白名單)模式的資源定義適合於service調用,若是放在controller中,將不起做用,具體可看com.biao.mall.logistic.controller.DubboDeliveryController中的代碼,測試運行時無任何做用,這是由於REST調用使用http請求,sentinel規則將被忽略。

12.代碼地址:其中的day13

https://github.com/xiexiaobiao/dubbo-project.git

 

後記:

1.運行sentinel官方源碼時,JDK11下提示sun.misc does not exist,可是類文件又能夠在jdk文件夾下找到,這是由於sun.misc.Unsafe自JDK9起已經再也不是標準API,被移除,編譯時會提示出錯,解決方法:要麼重構代碼,或者下載1.8的rt.jar包,再做爲依賴導入。我直接下載導入解決問題。

2.java.util.stream.longstream 或者相似java.util.XXX not found 經過設置java language level解決。

3.Sentinel的核心規則類:

  • AuthorityRule:黑白名單根據資源的請求來源(origin)限制資源是否經過,若配置白名單則只有請求來源位於白名單內時纔可經過;若配置黑名單則請求來源位於黑名單時不經過,其他的請求經過。

  • FlowRule:監控應用流量的 QPS 或併發線程數等指標,當達到指定的閾值時對流量進行控制,以免被瞬時的流量高峯沖垮。

  • DegradeRule:在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而致使級聯錯誤

  • SystemRule:單臺機器的整體 Load、RT(ResponseTime)、入口 QPS 和線程數四個維度監控應用數據,讓系統儘量跑在最大吞吐量的同時保證系統總體的穩定性。

相關核心方法我整理以下圖:

 

 

 

4.下載源碼 打包 dashboard生成jar,提示缺乏各種依賴,可先install:sentinel-parent,生成各個依賴的jar。

5.Sentinel與Hystrix的對比,借用一張圖:大體來說,sentinel輕量級、組件獨立、適配好、規則豐富。

 

6.若是使用springBoot開發,必須添加依賴spring-cloud-starter-alibaba-sentinel,不然沒法鏈接sentinelDashboard。

7.如想使用URL輸出Rules: http://localhost:8083/actuator/sentinel,需添加actuator依賴。

8.注意啓動參數好比 -Dserver.port=8718 放 java <-D參數位置> -jar sentinel-dashboard.jar 中間,不要放後面,會丟失參數 。

 

做者原創文章,謝絕任何形式轉載,不然追究法律責任! 

 

推薦閱讀:

 

我的微信公衆號,只發原創文章! 

相關文章
相關標籤/搜索