作者原創文章,謝絕任何形式轉載,否則追究法律責任!
本文只發表在"公眾號"和"博客園",其他均屬復制粘貼!如果覺得排版不清晰,請查看公眾號文章。
各位看官,先提個問題,如果讓你設計一套秒殺系統,核心要點是啥???我認為有三點:緩存、限流和分離。想當年12306大面積崩潰,還有如今的微博整體宕機情況,
感覺就是限流降級沒做好,"用有限的資源響應過量請求"
——這就是限流降級的核心。限流降級組件,當今開源界應該是Hystrix最為出名,這也得益於SpringCloud的流
行,當然,挑戰者總是有的,於是Sentinel橫空出世,正因實際生產使用中似乎並不多見,所以才有必要拿來一用,不然就脫離了此系列文章的主旨了,就是要見些不一樣的風景!
工具: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.6
難度: 新手--戰士--老兵--大師
目標:
-
使用Sentinel實現交易業務特定方法的限流
-
AOP注解模式實現交易業務方法限流降級
-
使用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 中間,不要放后面,會丟失參數 。
作者原創文章,謝絕任何形式轉載,否則追究法律責任!
推薦閱讀:
個人微信公眾號,只發原創文章!