除了流量控制以外,對調用鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。由於調用關系的復雜性,如果調用鏈路中的某個資源不穩定,最終會導致請求發生堆積。Sentinel 熔斷降級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。當資源被降級后,在接下來的降級時間窗口之內,對該資源的調用都自動熔斷(默認行為是拋出 DegradeException
)。
熔斷的基本認識:
在大型分布式架構中,一個用戶的請求,可能是這樣
如果這個時候一個服務出現異常
- 服務提供者不可用(硬件故障、程序bug、網絡故障、用戶請求量較大)
- 重試導致的流量過大
- 服務調用者使用同步調用,產生大量的等待線程占用系統資源,一旦線程資源被耗盡,調用者提供的服務也會變成不可用狀態
就會導致請求堆集從而出現整個服務不可用的問題。用古話來講就是:千里之堤毀於蟻穴
在復雜的分布式架構的應用程序有很多的依賴,都會不可避免的出現服務故障等問題。高並發的依賴失敗時如果沒有隔離措施,當前應用服務就有被拖垮的風險。
引入熔斷機制:
在分布式架構中,有一種解決方法,就是熔斷機制。也就是說當下游服務因為訪問壓力過大或者其他原因導致響應變慢的時候,上游服務為了保護自己以及系統整體的可用性,可以暫時切斷對於下游服務的調用。熔斷在生活中也隨處可見,
- 比如“跳閘”,當電壓超過負荷時,開關會自動跳閘。從而防止出現電路燒毀帶來的火災。
- 比如股票市場的熔斷,對於股票設置一個熔斷價格,當價格觸發到熔斷點之后,交易會被暫停一段時間。或者交易可以繼續進行,但是報價會限制在一定的范圍
那生活中的這種場景,能不能應用在架構設計中呢?大家會發現,架構是基於人的架構,所以架構的設計都是基於人對於事務的基本認識來實施的。因此越往后面學習,越能夠發現很多設計思想都來自於生活。
Sentinel熔斷降級:
Sentinel 熔斷降級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。當資源被降級后,在接下來的降級時間窗口之內,對該資源的調用都自動熔斷。那么怎么去判斷資源是否處於穩定狀態呢?
- 平均響應時間 (
DEGRADE_GRADE_RT
):當 1s 內持續進入 5 個請求,對應時刻的平均響應時間(秒級)均超過閾值(count
,以 ms 為單位),那么在接下的時間窗口(DegradeRule
中的timeWindow
,以 s 為單位)之內,對這個方法的調用都會自動地熔斷(拋出DegradeException
)。注意 Sentinel 默認統計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項-Dcsp.sentinel.statistic.max.rt=xxx
來配置。 - 異常比例 (
DEGRADE_GRADE_EXCEPTION_RATIO
):當資源的每秒請求量 >= 5,並且每秒異常總數占通過量的比值超過閾值(DegradeRule
中的count
)之后,資源進入降級狀態,即在接下的時間窗口(DegradeRule
中的timeWindow
,以 s 為單位)之內,對這個方法的調用都會自動地返回。異常比率的閾值范圍是[0.0, 1.0]
,代表 0% - 100%。 - 異常數 (
DEGRADE_GRADE_EXCEPTION_COUNT
):當資源近 1 分鍾的異常數目超過閾值之后會進行熔斷。注意由於統計時間窗口是分鍾級別的,若timeWindow
小於 60s,則結束熔斷狀態后仍可能再進入熔斷狀態。
針對這些規則,Sentinel中給出了響應的字段來設置:
在實戰之前我們再來回顧以下Sentinel限流中最重要的處理鏈:
public class DefaultSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); chain.addLast(new NodeSelectorSlot()); chain.addLast(new ClusterBuilderSlot()); chain.addLast(new LogSlot()); chain.addLast(new StatisticSlot()); chain.addLast(new SystemSlot()); chain.addLast(new AuthoritySlot()); chain.addLast(new FlowSlot()); chain.addLast(new DegradeSlot()); return chain; } }
可以看到最后一個Slot,就是熔斷降級部分,他與限流是可以並存的。
Sentinel集成Dubbo實現服務熔斷:
這個熔斷是跟着Sentinel集成Dubbo實現限流部分改造,小伙伴們可以先去瀏覽一下上一篇博文。
1.加入pom依賴
<dependency>
<!--這里面就一個接口,可以直接寫到本項目中--> <groupId>com.wuzz.demo</groupId> <artifactId>sentinel-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.2</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-dubbo-adapter</artifactId> <version>1.6.3</version> </dependency>
2.實現類:
@Service//把當前服務發布成dubbo服務
public class SentinelServiceImpl implements SentinelService { @Override public String sayHello(String name) { try { Thread.sleep(500); //為演示平均響應時間,讓他睡500毫秒
} catch (InterruptedException e) { e.printStackTrace(); } System.out.println("begin execute sayHello:" + name); return "Hello World:" + name + "->timer:" + LocalDateTime.now(); } }
3.添加Dubbo配置:
@Configuration @DubboComponentScan("com.wuzz.demo") public class DubboConfig { @Bean public ApplicationConfig applicationConfig(){ ApplicationConfig applicationConfig=new ApplicationConfig(); applicationConfig.setName("sentinel-dubbo"); applicationConfig.setOwner("wuzz"); return applicationConfig; } @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig=new RegistryConfig(); registryConfig.setAddress("zookeeper://192.168.1.101:2181"); return registryConfig; } @Bean public ProtocolConfig protocolConfig(){ ProtocolConfig protocolConfig=new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } }
4.添加熔斷拓展點:
public class DataSourceInitFunc implements InitFunc { @Override public void init() throws Exception { List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(); //下面這個配置的意思是,當1s內持續進入5個請求,平均響應時間都超過count(10ms), // 那么在接下來的timewindow(10s)內,對 //這個方法的調用都會自動熔斷,拋出異常:degradeException. //指定被保護的資源
rule.setResource("com.wuzz.demo.SentinelService"); //降級模式, RT(平均響應時間)、異常比例(DEGRADE_GRADE_EXCEPTION_RATIO)/異常數量 //1s內處理5個請求
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setCount(100); //閾值 5個請求平均響應時間超過100ms 觸發降級
rule.setTimeWindow(10);//降級的時間單位, 單位為s
rules.add(rule); DegradeRuleManager.loadRules(rules); } }
在resource/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc中配置改類的全路徑,這樣的話sentinel在觸發限流時會去調用這個initFunc來解析規則
com.wuzz.demo.DataSourceInitFunc
5.主啟動類:
@SpringBootApplication public class SentinelProviderDegradeApplication { public static void main(String[] args) throws IOException { initFlowRules(); SpringApplication.run(SentinelProviderDegradeApplication.class, args); System.in.read(); } //初始化規則
private static void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); //限流規則的集合
FlowRule flowRule = new FlowRule(); flowRule.setResource("com.wuzz.demo.SentinelService:sayHello(java.lang.String)");//資源(方法名稱、接口)
flowRule.setCount(1000);//限流閾值 qps=1000
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);//限流閾值類型(QPS 或並發線程數) //流量控制手段(直接拒絕、Warm Up、勻速排隊)
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); flowRule.setLimitApp("sentinel-web");//流控針對的調用來源,若為 default 則不區分調用來源
rules.add(flowRule); FlowRuleManager.loadRules(rules); } }
其中qps盡量大一點,因為這里主要是想測試熔斷,不然拋出來的異常就不是我們想要的了,可能被限流了。
然后啟動服務端,客戶端,用JMeter壓測,會看到拋出異常:
我們可以捕獲這個異常進行降級處理。