您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何理解Sentinel分布式系統限流降級框架”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何理解Sentinel分布式系統限流降級框架”吧!
在分布式系統中,許多服務之間通過遠程調用實現信息交互,調用時難免會出現調用失敗的情況,Sentinel能保證在一個服務出現問題的情況下,不會導致整體服務失敗,防止服務雪崩,提高分布式系統的可用性。
常用的容錯方式有:
1、超時:設置比較短的超時時間,如果調用不成功,在很短時間內就釋放連接,避免大量線程堵塞等待。
2、限流:超過設置的閾值就拒絕請求。
3、斷路器:保護服務過載,當有服務發生無法調用請求堆積時,能夠及時切換該服務,防止整個服務的崩潰。
Sentinel的地位和SpringCloud中的Hystrix類似,Sentinel以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
首先我們在sentinel的github官網上下載sentinel-dashboard-1.7.2.jar
然后通過jar命令啟動起來,端口默認8080,直接ip+端口訪問,這是sentinel的控制后臺,賬號密碼都是sentinel
接著在項目中引入依賴
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
在配置文件中,輸入Sentinel的后臺地址,地址和端口寫自己的,端口默認8080
spring.cloud.sentinel.transport.dashboard=localhost:8080
啟動后在Sentinel后臺就可以看到這個應用
sentinel控制臺調用的API默認是http://ip:8719/api,在本地啟動了引入sentinel依賴的項目啟動后,默認在8719這個端口上會暴露一個api出來。
寫了一個簡單的請求:
@RestController public class TestController { @GetMapping("/test") public String test(){ return "test"; } }
接下來通過這個請求去了解sentinel的流控規則,所謂流控規則,就是對請求的控制
資源名:默認是請求的路徑
針對來源:Sentinel可以針對調用者進行限流,填寫微服務名,指定對哪個微服務進行限流 ,默認default(不區分來源,全部限制)
閾值類型:可選QPS和線程數,可以設置達到多少后執行流控策略
是否集群:你的環境是否是集群
流控模式:
1、直接:直接對該資源進行控制,比如上面的/test訪問達到閾值,就限流
2、關聯:當關聯的資源達到閾值時,就限流自己。
看圖,如果/test2訪問達到閾值,就限流/test
3、鏈路:只記錄指定鏈路上的流量(指定資源從入口資源進來的流量,如果達到閾值,就可以限流),這個鏈路的名稱可以從簇點鏈路中獲取。
流控效果
1、快速失敗:直接失敗
2、Warm Up:預熱模式,根據codeFactory的值(默認3),從閾值/codeFactory,經過預熱時長,才達到設置的QPS閾值。比如設置QPS為90,設置預熱為10秒,則最初的閾值為90/3=30,經過10秒后才達到90。
3、排隊等待:比如設置閾值為10,超時時間為500毫秒,當第11個請求到的時候,不會直接報錯,而是等待500毫秒,如果之后閾值還是超過10,則才會被限流。
設置完流控規則后,不停刷新去觸發到閾值,看到出現了下面的提示:
這樣的提示不太友好,可以自定義限流后返回的數據信息,分別對應于五種限流異常:
public class MyBlockException implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { JSONObject object=new JSONObject(); if (e instanceof FlowException){ object.put("status","100"); object.put("message","接口限流"); object.put("data",null); }else if (e instanceof DegradeException){ object.put("status","101"); object.put("message","服務降級"); object.put("data",null); }else if (e instanceof ParamFlowException){ object.put("status","102"); object.put("message","熱點參數限流"); object.put("data",null); }else if (e instanceof SystemBlockException){ object.put("status","103"); object.put("message","觸發系統保護"); object.put("data",null); }else if (e instanceof AuthorityException){ object.put("status","104"); object.put("message","授權規則不通過"); object.put("data",null); } httpServletResponse.setStatus(500); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(httpServletResponse.getWriter(),object); } }
代碼中定義了五種不同的
降級規則指的是在應用高峰期,將個別的服務關閉,使得能夠有更多地資源去處理重要的業務,比如雙十一的時候,我們會發現支付寶的部分功能會被暫時關閉。
RT模式:平均響應時間、當1S內持續進入N個請求,如果平均響應時間超過閾值,那么在接下來的時間窗口內,按降級邏輯進行處理(報一個DegradeException錯誤)。
異常比例:可以輸入一個0.0~1.0的數字,表示出現異常的比例。如果一秒內超過這個比例,那么在接下來的時間窗口內,按降級邏輯進行處理。
異常數:指當資源近一分鐘的異常數超過閾值之后會,那么在接下來的時間窗口內,按降級邏輯進行處理。
所謂熱點規則,就是對某些經常訪問的數據(熱點數據),對其訪問進行限制,
比如以商品ID為參數,限制這個商品的訪問次數。在代碼中需要對要限制的請求進行埋點
@GetMapping("/test3") @SentinelResource(value = "test3",blockHandler = "handHotKey") public String test3(@RequestParam(value = "a",required = false)String a, @RequestParam(value = "b",required = false)String b){ return "test3"+a+b; } public String handHotKey(String a1, String a2, BlockException e){ return "熱點數據限流"; }
設置熱點規則,這里的資源名就是@SentinelResource中所設置的value,參數索引的表示對第幾個參數進行限流控制,閾值和窗口時長表示在1秒內如果有2個對參數0的請求,就限流。限流后會執行自己設置的blockHandler方法。
高級設置中可以設置參數例外項,即根據設置參數的值進行限流:
這樣設置后,如果訪問/test3?a=1,則按照下面的閾值進行控制。
通過監控系統的一些參數進行規則限流:
Load:這個參數只能在Linux或類Unix機器生效,將系統的1分鐘的loadAverage作為指標,這個值一般設置為CPU核心數量*2.5。
RT:當單臺機器上所有入口流量的平均RT達到閾值就觸發系統保護,單位是毫秒。
并發線程數:當單臺機器上所有入口流量的并發線程數達到閾值就觸發系統保護。
入口QPS:當單臺機器上所有入口流量的QPS達到閾值就觸發閾值就觸發系統保護。
CPU使用率:當系統CPU使用率超過閾值就觸發系統保護。
授權規則可以指定哪些請求可以訪問哪些不能訪問,首先來看如何配置:
資源名就是請求名,流控應用中可以手動輸入一些應用名,如果是白名單,則流控應用中設置的這些可以訪問,如果是黑名單則流控應用中設置的不能訪問。
接著需要在代碼中去獲取請求:
@Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { String origin=httpServletRequest.getParameter("origin"); if (origin==null){ throw new IllegalArgumentException("origin參數未指定"); } return origin; } }
通過設置后,所有請求必須帶上參數origin=XXX,以上面的配置為例,只有origin=javayz的請求才能通過訪問。如果不喜歡參數的方式,可以在代碼中換成header傳遞,效果一樣。
使用Sentinel需要引入引入sentinel依賴,其中sentinel-transport-simple-http依賴會將微服務注冊到SentinelDashboard中。啟動微服務之后,會在8719端口自動開放一系列api接口,我們也可以通過http://ip:8719/api訪問到這些api接口,SentinelDashboard就是通過這些API與微服務之間進行通信。
這個8719端口可以在配置文件中修改:
spring.cloud.sentinel.transport.port=8719
默認情況下,我們代碼中的所有GetMapping、PostMapping請求都會被Sentinel保護,也就是都會經過Sentinel的攔截器,但是也可以手動關閉這個攔截。
spring.cloud.sentinel.filter.enabled=false
這樣的話sentinel就沒法捕捉請求了。但是還是可以通過代碼的方式使用Sentinel
@GetMapping("/test4") public String test4(){ ContextUtil.enter("test4","abc"); Entry entry=null; try { entry= SphU.entry("test4"); //業務代碼 return "業務處理結束"; } catch (BlockException e) { e.printStackTrace(); //一系列的異常處理。參考MyBlockException //..... return "觸發限流"; }catch (NullPointerException e){ //對異常進行監控 Tracer.trace(e); return "空指針異常"; }finally { if (entry!=null){ entry.exit(); } ContextUtil.exit(); } }
或者還可以使用注解方式加入埋點
@SentinelResource(value = "test3",blockHandler = "handHotKey")
這樣配置后就增加了資源test3的攔截,如果觸發限流策略,就會進入當前類的handHotKey方法,或者配置blockHandlerClass,就會進入blockHandlerClass所配置類中的handHotKey方法。
首先需要在配置文件中開啟對RestTemplate的支持,默認也是true
resttemplate.sentinel.enabled=true
接著在RestTemplate注入Bean的代碼中增加一個Sentinel注解:
@SentinelRestTemplate(blockHandler = "fallback",blockHandlerClass = MyBlockHandlerClass.class) @LoadBalanced //負載均衡 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }
調用的限流處理方法如下:
public class MyBlockHandlerClass { public static SentinelClientHttpResponse block(){ return new SentinelClientHttpResponse("block info"); } }
首先需要在配置文件中開啟對Feign的支持,默認為false:
feign.sentinel.enabled=true
接在在@FeignClient注解中增加fallback的類:
@FeignClient(name = "nacos-discovery-provider",fallback = TestServiceFallback.class,configuration = FeignConfiguration.class) public interface TestService { @GetMapping("/{name}") String index(@PathVariable("name") String string); } 最后設置規則后就會觸發fallback中對應的方法,具體實現 public class TestServiceFallback implements TestService{ @Override public String index(String string) { return "fallback"; } }
默認模式:
在前面介紹Sentinel時,會發現每次重啟微服務后Sentinel中的配置都會丟失,這是因為API將規則推送到了客戶端的內存中,重啟后就消失了。
Pull模式:
在Sentinel Dashboard中設置規則之后,推送給客戶端后不僅保存在內存中,還會保存到本地文件中。Pull模式需要通過代碼實現,這段代碼可以直接拿去復用:
@Slf4j public class FileDataSourceInit implements InitFunc { @Override public void init() throws Exception { String ruleDir=System.getProperty("user.home")+"/sentinel/rules"; log.info(ruleDir); //限流規則路徑 String flowRulePath = ruleDir + "/flow-rule.json"; //降級規則路徑 String degradeRulePath = ruleDir + "/degrade-rule.json"; //熱點規則路徑 String paramFlowRulePath = ruleDir+"/param-flow-rule.json"; //系統規則路徑 String systemRulePath = ruleDir+"/system-rule.json"; //權限規則路徑 String authorityRulePath=ruleDir+"/authority-rule.json"; this.mkdirIfNotExists(ruleDir); this.createFileIfNotExists(flowRulePath); this.createFileIfNotExists(flowRulePath); this.createFileIfNotExists(flowRulePath); this.createFileIfNotExists(flowRulePath); this.createFileIfNotExists(flowRulePath); // 流控規則,可讀取數據 ReadableDataSource<String, List<FlowRule>> flowRuleRDS=new FileRefreshableDataSource<>( flowRulePath, flowRuleListParser ); //將可讀數據源注入到FlowRuleManager,當文件發生變化時就會更新規則到緩存 FlowRuleManager.register2Property(flowRuleRDS.getProperty()); //流控規則:可寫數據源 WritableDataSource<List<FlowRule>> flowRuleWDS=new FileWritableDataSource<List<FlowRule>>( flowRulePath, this::encodeJson ); WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS); //降級規則:可讀數據源 ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS=new FileRefreshableDataSource<>( degradeRulePath, degradeRuleListParser ); DegradeRuleManager.register2Property(degradeRuleRDS.getProperty()); //降級規則:可寫數據源 WritableDataSource<List<DegradeRule>> degradeRuleWDS=new FileWritableDataSource<List<DegradeRule>>( flowRulePath, this::encodeJson ); WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS); //熱點參數:可讀數據源 ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS=new FileRefreshableDataSource<>( paramFlowRulePath, paramFlowRuleListParser ); ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty()); //熱點參數:可寫數據源 WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS=new FileWritableDataSource<List<ParamFlowRule>>( paramFlowRulePath, this::encodeJson ); ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS); //系統規則:可讀數據源 ReadableDataSource<String, List<SystemRule>> systemRuleRDS=new FileRefreshableDataSource<>( systemRulePath, systemRuleListParser ); SystemRuleManager.register2Property(systemRuleRDS.getProperty()); //系統規則:可寫數據源 WritableDataSource<List<SystemRule>> systemRuleWDS=new FileWritableDataSource<List<SystemRule>>( systemRulePath, this::encodeJson ); WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS); //授權規則:可讀數據源 ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS=new FileRefreshableDataSource<>( authorityRulePath, authorityRuleListParser ); AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty()); WritableDataSource<List<AuthorityRule>> authorityRuleWDS=new FileWritableDataSource<List<AuthorityRule>>( authorityRulePath, this::encodeJson ); WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS); } private Converter<String,List<FlowRule>> flowRuleListParser=source-> JSON.parseObject( source, new TypeReference<List<FlowRule>>(){} ); private Converter<String,List<DegradeRule>> degradeRuleListParser=source-> JSON.parseObject( source, new TypeReference<List<DegradeRule>>(){} ); private Converter<String,List<SystemRule>> systemRuleListParser=source-> JSON.parseObject( source, new TypeReference<List<SystemRule>>(){} ); private Converter<String,List<AuthorityRule>> authorityRuleListParser=source-> JSON.parseObject( source, new TypeReference<List<AuthorityRule>>(){} ); private Converter<String,List<ParamFlowRule>> paramFlowRuleListParser=source-> JSON.parseObject( source, new TypeReference<List<ParamFlowRule>>(){} ); private void mkdirIfNotExists(String filePath) { File file=new File(filePath); if (!file.exists()){ file.mkdirs(); } } private void createFileIfNotExists(String filePath) throws IOException { File file=new File(filePath); if (!file.exists()){ file.createNewFile(); } } private <T> String encodeJson(T t){ return JSON.toJSONString(t); } }
sentinel的數據持久化是通過SPI機制實現的,因此需要在resource下新建文件夾META-INF/services,然后新建一個文件com.alibaba.csp.sentinel.init.InitFunc
文件中寫入上面這個類的全限定名:
之后產生規則后就會在代碼中設定的路徑下產生json文件,重啟后之前的配置也不會消失。
Push模式: 客戶端通過注冊監聽器的方式時刻監聽變化,比如使用Nacos、Zookeeper等配置中心,這種方式保證了很好的實時性和一致性,生產環境中一般采用push模式。我們用Nacos實現
1、添加sentinel-datasource-nacos依賴
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
2、配置持久化數據源
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=192.168.78.128:8848 spring.cloud.sentinel.datasource.ds1.nacos.data-id=${spring.application.name}.json spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP spring.cloud.sentinel.datasource.ds1.nacos.data-type=json spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
3、在Nacos中手動添加配置文件,這里的配置文件取的就是和本地配置文件相同格式
[ { "clusterMode":false, "controlBehavior":0, "count":2, "grade":1, "limitApp":"default", "maxQueueingTimeMs":500, "resource":"/test", "strategy":0, "warmUpPeriodSec":10 } ]
push模式目前還有缺點,Nacos修改配置文件后可同步到Sentinel,但是在Sentinel中修改配置后無法同步到Nacos,需要手動去同步數據。
感謝各位的閱讀,以上就是“如何理解Sentinel分布式系統限流降級框架”的內容了,經過本文的學習后,相信大家對如何理解Sentinel分布式系統限流降級框架這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。