最近公司裏面再進行微服務開發,由於有使用到限流降級,因此取調研學習了一下Sentinel,在這裏作一個總結。java
Sentinel官方文檔:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8Dgit
Sentinel---分佈式系統的流量衛兵。github
主要面向分佈式架構的流量控制產品。以流量爲切入點,從流量控制、熔斷降級、系統負載保護、等各個維度對服務提供保護。其官方稱其爲輕量級產品。spring
2.一、資源:Sentinel的核心概念,一切須要Sentinel保護的東西都稱爲資源。它能夠是一段代碼、由應用程序提供的服務、或由應用程序調用的其它應用程序提供的服務。在程序中最直觀的呈現就是由Sentinel Api包圍起來的一段代碼。數據庫
2.二、規則:對資源的保護規則。如流量規則、熔斷降級規則、系統保護規則等。全部規則都是能夠實時動態調整的。json
2.三、埋點:定義資源的過程。服務器
由於流量是不規則的,在流量高峯期,大批量流量瞬間涌入系統會瞬間沖垮服務,這個時候咱們須要限制流量對系統進行保護;同時在高峯期,若是直接丟棄掉超過服務承載能力的流量,而在流量低谷期由只有不多的流量或者無流量,那麼此時服務又是一種資源浪費,而且也無沒法給用戶一個很好的體驗感,由於咱們須要將流量高峯期的部分流量分流到低谷期,而不是在高峯期全數丟棄超過服務負載能力的流量。架構
其原理就是監控應用流量的QPS或併發線程數,當達到指定的閾值時對流量進行控制。併發
流控規則在Sentinel控制檯的直觀體現。app
流控模式:
直接:當接口達到限流條件時,開啓限流;
關聯:當關聯的資源達到限流條件時,開啓限流;適合作應用讓步;好比一個查詢的接口添加關聯限流,關聯限流資源爲一個更新的接口,當更新的接口達到閾值時,開啓查詢接口的限流,爲更新接口讓步服務器資源。
好比說對於訂單,如今有兩種請求,一種是使用更新訂單量請求【QUERY_ORDER_NUM】,另外一種是查詢請求【UPDATE_ORDER_NUM】。
當正處於訂單使用高峯期,更新請求達到了設定閾值,這時能夠暫時下降訂單查詢服務的可用性,優先保障訂單扣減邏輯的執行。
鏈路:當從某個接口過來的資源達到限流條件時,開啓限流。這是一種更精細化的資源管理方式。
好比說定義一個資源 【SAY_HELLO】:
且同時有兩個接口 【/friendly】【/haughty】都調用該資源:
這時咱們配置SAY_HELLO的入口資源爲 【/friendly】,那麼當【/friendly】調用超過閾值時就會觸發限流開啓,而【/haughty】則不會觸發。
流控效果
快速失敗:當請求達到限流閾值的時候,後續請求會當即拒絕,拒絕方式就是拋出FlowException。這種方式適用於對系統處理能力確切已知的狀況下,好比經過壓測肯定了系統的準確水位時。
Warm Up(預熱/冷啓動):當系統長期處於低水位的狀況下,而流量忽然增長時,直接把系統拉昇到高水位可能瞬間把系統壓垮。經過"冷啓動",在指定的預熱時間內,讓經過的流量緩慢增長,在必定時間內逐漸增長到閾值上限,給冷系統一個預熱的時間,避免冷系統被壓垮。主要用於啓動須要額外開銷的場景;
排隊等待:該種模式會嚴格控制請求經過的間隔時間,也便是讓請求以均勻的速度經過,從而達到一種流量整形的效果。
一個服務經常會調用別的模塊,多是另外的一個遠程服務、數據庫,或者第三方 API 等。對調用鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。
例如,支付的時候,可能須要遠程調用銀聯提供的 API;查詢某個商品的價格,可能須要進行數據庫查詢。然而,這個被依賴服務的穩定性是不能保證的。若是依賴的服務出現了不穩定的狀況,請求的響應時間變長,那麼調用服務的方法的響應時間也會變長,線程會產生堆積,最終可能耗盡業務自身的線程池,服務自己也變得不可用。
所以,咱們須要對不穩定的依賴服務調用進行熔斷降級。
熔斷策略說明:
慢調用比例:選擇以慢調用比例做爲閾值,須要設置容許的慢調用 RT(即最大的響應時間),請求的響應時間大於該值則統計爲慢調用。當單位統計時長(statIntervalMs)內請求數目大於設置的最小請求數目,而且慢調用的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。
異常比例:當單位統計時長內請求數目大於設置的最小請求數目,而且異常的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。
異常數:當單位統計時長內的異常數目超過閾值以後會自動進行熔斷。
1.一、引入jar包
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.0</version> </dependency>
1.二、定義資源
用 Sentinel API SphU.entry("HelloWorld") 和 entry.exit()將須要進行流量控制的代碼包圍起來。被包圍起來的代碼就做爲資源,用API包圍起來便是埋點。
public static void fun() { Entry entry = null; try { entry = SphU.entry(SOURCE_KEY); pass.incrementAndGet(); // todo 業務邏輯 int temp = 10 / 0; } catch (BlockException e1) { block.incrementAndGet(); // todo 流控處理 } catch (Throwable e) { Tracer.traceEntry(e, entry); // todo 業務異常處理 } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } }
1.三、定義規則
private static void initFlowQpsRule() { List<FlowRule> rules = new ArrayList<FlowRule>(); FlowRule rule1 = new FlowRule(); rule1.setResource(SOURCE_KEY); // 採用qps策略,每秒容許經過1個請求 rule1.setCount(1); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); }
1.四、完整代碼
/** * 流量控制 */ public class FlowQpsDemo { private static final String SOURCE_KEY = "CESHI_KEY"; private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { initFlowQpsRule(); for (int i = 0;i < 10;i++) { fun(); } System.out.println("total=" + total.get() + " pass=" + pass.get() + " block=" + block.get()); } public static void fun() { Entry entry = null; try { entry = SphU.entry(SOURCE_KEY); // todo 業務邏輯 pass.incrementAndGet(); } catch (BlockException e1) { // todo 流控處理 block.incrementAndGet(); } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } } private static void initFlowQpsRule() { List<FlowRule> rules = new ArrayList<FlowRule>(); FlowRule rule1 = new FlowRule(); rule1.setResource(SOURCE_KEY); // 採用qps策略,每秒容許經過1個請求 rule1.setCount(1); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); } private static void sleep(int sleep) { try { Thread.sleep(sleep); } catch (InterruptedException e) { } } }
上面的示例代碼演示了基礎的使用方式,可是實際生產環境不會如此使用。一是直接將Sentinel Api代碼寫入到代碼,對業務代碼形成了侵入;二是將規則寫死在代碼裏面,不能動態調整。因此針對實際生產使用,咱們使用Sentinel註解埋點,且集成Nacos,將配置數據同步到Nacos配置中心進行存儲;
在介紹生產環境使用Sentinel的時候,首先介紹一下Sentinel控制檯、Sentinel客戶端、配置中心的關係:
首先在Sentinel Dashboard中維護各類限流、降級規則,Sentinel Dashboard將規則推送到Nacos進行持久化保存;同時Sentinel客戶端註冊Nacos數據源,訂閱Nacos數據中心的流控規則,當發生變化時更新客戶端本地內存規則。
<!-- spring cloud 與 sentinel 集成包 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.2.RELEASE</version> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 配置nacos動態配置中心 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.0</version> </dependency>
import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.cloud.sentinel.datasource.config.NacosDataSourceProperties; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; 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.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.util.List; @Configuration public class SentinelConfig { @Autowired private SentinelProperties sentinelProperties; @PostConstruct public void run() throws Exception { // sentinel客戶端註冊流控nacos動態數據源 NacosDataSourceProperties flowConfiguration = sentinelProperties.getDatasource().get("flow").getNacos(); ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>( flowConfiguration.getServerAddr(), flowConfiguration.getGroupId(), flowConfiguration.getDataId(), source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() { })); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); // sentinel客戶端註冊降級nacos動態數據源 NacosDataSourceProperties degradeConfiguration = sentinelProperties.getDatasource().get("degrade").getNacos(); ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>( degradeConfiguration.getServerAddr(), degradeConfiguration.getGroupId(), degradeConfiguration.getDataId(), source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() { })); DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty()); } }
@GetMapping("/ceshi/flow") @SentinelResource(value = "CESHI_FLOW", blockHandler = "ceshiFlow") public String ceshiFlow(@RequestParam String message) { return "hello " + message; } public String ceshiFlow(String message, BlockException exception) { return "當前服務被限流,暫不可用! message=" + message; } @GetMapping("/ceshi/degrade") @SentinelResource(value = "CESHI_DEGRADE", blockHandler = "ceshiDegradeBlock", fallback = "ceshiDegradeFallback") public String ceshiDegrade(@RequestParam int age) throws InterruptedException { if (age < 0) { throw new ApplicationBaseException("年齡非法"); } else if (age == 0) { Thread.sleep(500); return "***剛出生***"; } return "您的年齡爲:" + age; } public String ceshiDegradeBlock(int age, BlockException e) { return "當前請求被阻塞,age=" + age + " exMessage=" + e.getMessage(); } public String ceshiDegradeFallback(int age, Throwable t) { return "當前請求執行失敗,age=" + age + " exMessage=" + t.getMessage(); }
各項屬性以下:
一、value:資源名稱,必需項(不能爲空)。
二、entryType:entry 類型,可選項(默認爲 EntryType.OUT)。
三、blockHandler / blockHandlerClass:
blockHandler 對應處理 BlockException 的函數名稱,可選項。blockHandler 函數訪問範圍須要是 public,返回類型須要與原方法相匹配,參數類型須要和原方法相匹配而且最後加一個額外的參數,類型爲 BlockException。blockHandler 函數默認須要和原方法在同一個類中。
若但願使用其餘類的函數,則能夠指定blockHandlerClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,不然沒法解析。
四、fallback / fallbackClass:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數能夠針對全部類型的異常(除了 exceptionsToIgnore 裏面排除掉的異常類型)進行處理。
fallback 函數簽名和位置要求:返回值類型必須與原函數返回值類型一致;方法參數列表須要和原函數一致,或者能夠額外多一個 Throwable 類型的參數用於接收對應的異常。
fallback 函數默認須要和原方法在同一個類中。若但願使用其餘類的函數,則能夠指定 fallbackClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,不然沒法解析。
五、defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,一般用於通用的 fallback 邏輯(便可以用於不少服務或方法)。默認 fallback 函數能夠針對全部類型的異常(除了exceptionsToIgnore 裏面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。
defaultFallback 函數簽名要求:返回值類型必須與原函數返回值類型一致;方法參數列表須要爲空,或者能夠額外多一個 Throwable 類型的參數用於接收對應的異常。defaultFallback 函數默認須要和原方法在同一個類中。若但願使用其餘類的函數,則能夠指定 fallbackClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,不然沒法解析。
六、exceptionsToIgnore(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。
七、特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandler、fallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出(若方法自己未定義 throws BlockException 則會被 JVM 包裝一層 UndeclaredThrowableException)。