在微服務等分佈式架構中,服務容錯是老生常談的問題了,咱們都知道在微服務架構中會存在多個微服務,而絕大部分微服務之間都會存在調用關係,若因爲某個底層服務不可用從而產生連鎖反應,致使一系列的上層服務崩潰、故障,這種現象被稱爲雪崩效應或級聯故障。以下圖所示:
html
因此在微服務等分佈式架構中,可以防護服務雪崩效應的容錯方案是必不可少的,常見的容錯方案以下:java
一、超時:node
設置請求超時時間,讓請求線程在等待超過必定的時間後就斷定爲請求失敗從而釋放請求線程,在某些場景下線程釋放得夠快的話,就不會由於不斷建立線程而致使資源耗盡引發的服務崩潰git
二、限流:github
例如,上圖中的服務A只能承受1k左右的QPS,那麼就設置一個最大請求數量閾值,當QPS達到1k時就拒絕在這以後的請求。就像是我只能吃一碗飯,就算給我三碗我也只吃一碗web
三、艙壁模式:spring
艙壁模式實際上就是借鑑於現實生活中的船艙結構而設計,一艘船想要不那麼容易沉也須要具有有必定的」容錯「能力,而早期的船因爲設計上的欠缺,只要一個地方進水了,那麼水就會逐漸漫進整個船艙,這種結構的船幾乎沒有「容錯」能力,因此就比較容易沉。因而此時就有人想到將本來一體的船艙分隔成一個個獨立的船艙,船艙之間都使用鋼板焊死隔開,這些鋼板就是所謂的艙壁了。採用這種設計後,就算當其中一個兩個船艙進水了,也不會影響到其餘船艙,這艘船依舊可以正常行駛。瀏覽器
在軟件層面上借鑑這種思想,咱們可讓每一個服務都運行在本身獨立的線程池中,線程池之間是互不干擾的,服務A的線程池資源耗盡也不會影響到服務B。此時線程池就像船艙的艙壁同樣將不一樣的服務資源隔離開來,這樣某個服務掛掉也不會影響其餘服務的運行bash
四、斷路器模式:session
斷路器模式的思想實際上和家裏的斷路器同樣,在軟件層面大體就是對某個服務的API進行監控,若在必定時間內調用的失敗率或失敗次數達到指定的閾值就認爲該API是不可用的從而觸發「跳閘」,即此時斷路器就處於打開狀態。過了一段時間後斷路器會處於一個半開狀態,若在半開狀態時嘗試調用該API成功後就會關閉斷路器,不然依舊認爲不可用讓斷路器繼續處於打開狀態
斷路器三態轉換以下圖:
斷路器模式原文:CircuitBreaker
而Spring Cloud已經提供了相關的服務容錯組件,組件裏已經整合了這些經常使用的方案,不須要咱們手動去實現。在此以前Spring Cloud提供的惟一服務容錯組件是Hystrix,不過如今多了一個選擇,那就是Spring Cloud Alibaba的Sentinel組件。關於Hystrix能夠參考以下文章,本文主要介紹Sentinel:
Sentinel 是什麼,官方描述以下:
隨着微服務的流行,服務和服務之間的穩定性變得愈來愈重要。Sentinel 是面向分佈式服務架構的輕量級流量控制、熔斷降級框架,主要以流量爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助您保護服務的穩定性。
官方GitHub倉庫地址以下:
如今咱們來爲項目整合Sentinel,第一步添加以下依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- actuator,用於暴露監控端點 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Tips:該項目使用的Spring Cloud版本爲Greenwich.SR1,Spring Cloud Alibaba版本爲0.9.0.RELEASE
第二步配置actuator:
# 暴露全部端點 management: endpoints: web: exposure: include: '*'
完成以上兩步後,啓動項目,使用瀏覽器訪問http://localhost:8080/actuator/sentinel
,返回以下結果表明整合成功:
在上一小節中,咱們已經爲項目成功整合了Sentinel,但這也只不過是完成了第一步。由於此時沒有一個可視化的界面能讓咱們看到Sentinel具體的監控信息,因此還須要搭建官方提供的可視化Sentinel控制檯,而後在控制檯中整合項目的監控信息。
Sentinel控制檯的下載地址以下:
Sentinel Dashboard有多個release版本,應該選擇哪一個呢?若是你是用在生產環境則選擇與項目中sentinel-core版本對應的便可,以下:
若只是學習或測試使用那就能夠隨便選擇了,只要能用就行,因此我這裏選擇最新版本1.6.3,注意這裏選擇jar包進行下載:
下載完成後,存放到一個你以爲ok的目錄下,而後打開cmd,經過命令運行該jar包。以下:
E:\Spring Cloud Alibaba\Sentinel>java -jar sentinel-dashboard-1.6.3.jar
啓動成功,監聽的端口是8080:
使用瀏覽器訪問http://localhost:8080
進入到登陸頁面,默認的帳戶密碼都是sentinel:
登陸成功,此時控制檯上是空白的,由於尚未監控任何的項目:
因此接着到項目中整合一下Sentinel Dashboard的請求地址,在配置文件中添加以下配置:
spring: cloud: sentinel: transport: # 配置sentinel控制檯的地址 dashboard: 127.0.0.1:8080
配置完成啓動項目後須要先訪問一下該項目的接口,由於Sentinel Dashboard是懶加載的,只有監控的項目被訪問後纔會收集監控信息。這樣才能看到下圖的實時監控信息,我這裏的服務名是content-center:
客戶端(微服務)鏈接控制檯相關配置項:
spring: cloud: sentinel: transport: #指定控制檯的地址 dashboard: localhost:8080 #指定和控制檯通訊的IP #如不配置,會自動選擇一個IP註冊 client-ip: 127.0.0.1 #指定和控制檯通訊的端口,默認值8719 #如不設置,會自動從8719開始掃描,依次+1,直到找到未被佔用的端口 port: 8719 #心跳發送週期,默認值null #但在S impleHttpHeartbeatSender會用默認值10秒 heartbeat- interval-ms : 10000
控制檯相關配置項:
配置項 | 默認值 | 最小值 | 描述 |
---|---|---|---|
sentinel.dashboard.app.hideAppNoMachineMillis | 0 | 60000 | 是否隱藏無健康節點的應用,距離最近一次主機心跳時間的毫秒數,默認關閉 |
sentinel.dashboard.removeAppNoMachineMillis | 0 | 120000 | 是否自動刪除無健康節點的應用,距離最近一次其下節點的心跳時間毫秒數,默認關閉 |
sentinel.dashboard.unhealthyMachineMillis | 60000 | 30000 | 主機失聯斷定,不可關閉 |
sentinel.dashboard.autoRemoveMachineMillis | 0 | 300000 | 距離最近心跳時間超過指定時間是否自動刪除失聯節點,默認關閉 |
server.port | 8080 | - | 指定端口 |
csp.sentinel.dashboard.server | localhost:8080 | - | 指定地址 |
project.name | - | - | 指定程序的名稱 |
sentinel.dashboard.auth.username [1.6版本支持] | sentinel | - | Sentinel Dashboard登陸帳號 |
sentinel.dashboard.auth.password [1.6版本支持] | sentinel | - | Sentinel Dashboard登陸密碼 |
server.servlet.session.timeout [1.6版本支持] | 30分鐘 | - | 登陸Session過時時間。配置爲7200表示7200秒;配置爲60m表示60分鐘 |
控制檯配置項需在啓動命令中指定,例如指定帳戶密碼,以下:
java -jar -Dsentinel.dashboard.auth.username=admin -Dsentinel.dashboard.auth.password=123456 sentinel-dashboard-1.6.3.jar
咱們能夠在Sentinel控制檯中給某個接口添加流控規則,點擊簇點鏈路,能夠看到該服務曾經被訪問過的路徑:
而後點擊接口右邊的流控按鈕就能夠添加流控規則:
添加成功:
此時訪問該服務的接口,QPS超過設定的閾值1,就會返回以下信息:
關於流控規則中的流控模式:
/shares/1
關聯了/query
,那麼/query
達到閾值,就會對/shares/1
限流鏈路模式稍微有些抽象,這裏舉個簡單的例子說明一下。下圖中有兩個調用鏈路,圖中的/test-b
和/test-a
實際就是兩個接口,它們都調用了同一個common
資源,因此/test-b
和/test-a
就稱爲common
的入口資源:
此時我爲common
添加一個限流規則以下:
能夠看到流控模式選擇鏈路後,須要填寫一個入口資源,我這裏填的是/test-a
,那麼這意味着什麼呢?意味着當/test-a
的QPS達到該規則的閾值後,就會對/test-a
限流,同時/test-b
不會受到任何影響。說明這種流控模式能夠針對接口級別的來源進行限流,而「針對來源」則是對微服務級別的來源進行限流。
關於流控規則中的監控效果:
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
關於流控的官方文檔:
服務降級實際就是斷路器模式的應用,相對於流控規則,降級規則要簡單一些。降級規則能夠在「簇點鏈路」或「降級規則」中添加:
例如,這裏給/shares/1
添加降級規則,降級策略先以RT爲例:
該降級規則的含義以下圖:
此時訪問/shares/1
接口,秒級平均響應時間超出閾值1,而且在時間窗口內經過的請求大於等於5,就會返回以下信息:
關於RT這種降級策略須要注意的點:
-Dcsp.sentinel.statistic.max.rt=xxx
若將降級策略改成異常比例,則含義以下:
若將降級策略改成異常數,則含義以下:
關於異常數這種降級策略須要注意的點:
降級規則的相關源碼:
com.alibaba.csp.sentinel.slots.block.degradeDegradeRule#passCheck
(對降級的判斷都在這個方法裏完成)在文章的開頭咱們介紹過斷路器有三個狀態,因此這裏須要說起一下的是目前Sentinel的降級斷路器是不支持半開狀態的,只有打開和關閉兩個狀態,據官方人員描述說是會預計在將來添加半開的支持。
關於降級的官方文檔:
熱點規則全稱是熱點參數限流規則,從名稱能夠得知,須要有參數的接口才可以使用熱點規則。例如,有一個接口的代碼以下:
@GetMapping("/test-hot") @SentinelResource("hot") // 該註解用於聲明是Sentinel須要監控的資源 public String testHot(@RequestParam(required = false) String a, @RequestParam(required = false) String b) { return a + " " + b; }
在控制檯中爲hot添加熱點規則,以下:
添加完該規則後,此時訪問這個接口,兩個參數都傳值,當QPS達到閾值時,就會拋出以下異常信息:
若是不傳參數a,僅傳參數b的話,則不會受到該規則的限流,以下:
說明該規則表達的含義是:在時間窗口內,一旦該規則指定的索引參數QPS達到了閾值,則會觸發限流
除此以外,還有高級選項,在這裏能夠添加參數例外項,以下示例:
添加完成後,此時將參數a的值設置爲5,而後頻繁發送請求,會發現即使QPS超過1也不會觸發限流:
這是由於參數a的值設置爲5時,限流閾值是1000,設置爲其餘值時,限流閾值纔是1。這就是所謂的參數例外項了,即參數的爲某個特定的值時,只受參數例外項裏的限流閾值影響。
熱點規則適用的場景:
使用熱點規則須要注意的點:
熱點規則相關源碼:
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
(對熱點參數規則的判斷邏輯都在這個方法裏)系統規則全稱爲系統保護規則,從名稱能夠得知該規則是用於保護系統、防止系統負載太高而崩潰的,因此觸發系統規則後會對整個系統限流。添加系統規則以下圖所示:
設置系統規則比較簡單,選擇一個合適的閾值類型並填寫閾值便可:
關於閾值類型:
maxQPS * minRT
;(由Sentinel計算 )
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkBbr
系統規則的判斷邏輯所在的源碼以下:
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem
受權規則用於限制某個資源僅容許哪一個服務訪問,因此一般用於對服務消費者的訪問權進行控制。咱們能夠在簇點鏈路中爲某個接口添加受權規則,這裏以/shares/1
接口爲例,以下:
新增受權規則:
/shares/1
接口,若是受權類型設置爲黑名單則表示/shares/1
接口不容許test服務訪問。即白名單是受權某個服務訪問,黑名單則是限制某個服務訪問,從而實現訪問控制的效果。上面幾個關於規則的小節中已經介紹瞭如何在Sentinel控制檯中配置各類規則,除此以外,Sentinel還支持在代碼中配置這些規則,因此本小節將簡單介紹一下如何在代碼中進行配置。
代碼以下(Tips:代碼基於sentinel-core 1.5.2版本):
package com.zj.node.contentcenter.controller.content; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 添加Sentinel規則 * * @author 01 * @date 2019-07-31 **/ @Slf4j @RestController public class SentinelRuleController { /** * 測試添加流控規則 */ @PostMapping("/test-add-flow-rule") public String testAddFlowRile(String resourceName) { log.info("add flow rule. resourceName is {}", resourceName); addFlowQpsRule(resourceName); return "add flow rule success!"; } /** * 添加流控規則 * * @param resourceName 資源名稱 */ private void addFlowQpsRule(String resourceName) { // 規則列表 List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); // 針對來源 rule.setLimitApp("default"); // 設置閾值類型爲QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 單機閾值 rule.setCount(20); // 將規則添加到規則列表 rules.add(rule); // 加載規則列表 FlowRuleManager.loadRules(rules); } /** * 添加降級規則 * * @param resourceName 資源名稱 */ private void addDegradeRule(String resourceName) { List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(resourceName); // 設置降級策略爲 RT rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // set threshold RT, 10 ms(設置RT時間閾值) rule.setCount(10); // 時間窗口 rule.setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); } /** * 添加熱點規則 * * @param resourceName 資源名稱 */ private void addHotRule(String resourceName) { ParamFlowRule rule = new ParamFlowRule(resourceName); // 參數索引 rule.setParamIdx(0); // 單機閾值 rule.setCount(5); // 添加參數例外項 ParamFlowItem item = new ParamFlowItem(); // 參數類型 item.setClassType(int.class.getName()); // 參數值 item.setObject("5"); // 限流閾值 item.setCount(10); rule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); } /** * 添加系統規則 */ private void addSystemRule() { List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); // 設置系統最高負載閾值 rule.setHighestSystemLoad(10); rules.add(rule); SystemRuleManager.loadRules(rules); } /** * 添加受權規則 * * @param resourceName 資源名稱 * @param limitApp 流控應用(指調用方,多個調用方名稱使用英文逗號分隔) */ private void addAuthorityRule(String resourceName, String limitApp) { AuthorityRule rule = new AuthorityRule(); // 資源名稱 rule.setResource(resourceName); // 流控應用 rule.setLimitApp(limitApp); // 設置受權類型爲白名單 rule.setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); } }
咱們來測試添加流控規則,使用postman訪問測試接口,以下:
添加成功後,到Sentinel控制檯中,查看是否存在該規則:
從上圖中能夠看到該流控規則已經成功添加到Sentinel中了,證實測試成功。至於其餘的規則也可使用相似的方式添加,而且也都給出了代碼,這裏就不一一去演示了。
下面總結一下Alibaba Sentinel各類規則的參數,而且提供了官方文檔的連接,若將來本文再也不適用,能夠自行點擊連接前往官方文檔查看
一、流控規則:
Field | 說明 | 默認值 |
---|---|---|
resource | 資源名,資源名是限流規則的做用對象 | 無 |
count | 限流閾值 | 無 |
grade | 限流閾值類型,QPS 或線程數模式 | QPS 模式 |
limitApp | 流控針對的調用來源 | default ,表明不區分調用來源 |
strategy | 判斷的根據是資源自身,仍是根據其它關聯資源 (refResource ),仍是根據鏈路入口 |
根據資源自己 |
controlBehavior | 流控效果(直接拒絕 / 排隊等待 / 慢啓動模式) | 直接拒絕 |
官方文檔:
二、降級規則:
Field | 說明 | 默認值 |
---|---|---|
resource | 資源名,即限流規則的做用對象 | 無 |
count | 閾值 | 無 |
grade | 降級模式,根據 RT 降級仍是根據異常比例或異常數降級 | RT |
timeWindow | 降級的時間,單位爲 s | 無 |
官方文檔:
三、熱點規則:
Field | 說明 | 默認值 |
---|---|---|
resource | 資源名,即熱點規則的做用對象 | 無 |
count | 限流閾值,必填 | 無 |
grade | 限流模式 | QPS 模式 |
durationInSec | 統計窗口時間長度(單位爲秒),1.6.0 版本開始支持 | 1s |
controlBehavior | 流控效果(支持快速失敗和勻速排隊模式),1.6.0 版本開始支持 | 快速失敗 |
maxQueueingTimeMs | 最大排隊等待時長(僅在勻速排隊模式生效),1.6.0 版本開始支持 | 0ms |
paramIdx | 熱點參數的索引,必填,對應 SphU.entry(xxx, args) 中的參數索引位置 | 無 |
paramFlowItemList | 參數例外項,能夠針對指定的參數值單獨設置限流閾值,不受前面 count 閾值的限制。僅支持基本類型和字符串類型 | 無 |
clusterMode | 是不是集羣參數流控規則 | false |
clusterConfig | 集羣流控相關配置 | 無 |
官方文檔:
四、系統規則:
Field | 說明 | 默認值 |
---|---|---|
highestSystemLoad | 最大的 load1 ,參考值 |
-1 (不生效) |
avgRt | 全部入口流量的平均響應時間 | -1 (不生效) |
maxThread | 入口流量的最大併發數 | -1 (不生效) |
qps | 全部入口資源的 QPS | -1 (不生效) |
官方文檔:
五、受權規則:
Field | 說明 | 默認值 |
---|---|---|
resource | 資源名,即受權規則的做用對象 | 無 |
limitApp | 流控應用(指調用方,即服務消費者),對應的黑名單/白名單,不一樣 origin 用英文逗號(, )分隔,如 appA,appB |
無 |
strategy | 限制模式,AUTHORITY_WHITE 爲白名單模式,AUTHORITY_BLACK 爲黑名單模式,默認爲白名單模式 |
AUTHORITY_WHITE |
官方文檔: