Sentine
1.背景
Sentinel 是阿里中間件團隊開源的,面向分布式服務架構的輕量級高可用流量控制組件,主要以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助用戶保護服務的穩定性。這里大家可能會問:Sentinel 和之前常用的熔斷降級庫 Netflix Hystrix 有什么異同呢?Sentinel官網有一個對比和Hystrix遷移到sentinel的文章,這里摘抄一個總結的表格,具體的對比可以點此 鏈接 查看。
功能對比
從對比的表格可以明顯看到,Sentinel比Hystrix在功能性上還要強大一些。
2.功能
Sentinel 功能主要體現在三個方面
2.1 流量控制
對於系統來說,任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制。
控制角度如下:
- 資源的調用關系,例如資源的調用鏈路,資源和資源之間的關系
- 運行指標,例如 QPS、線程池、系統負載等
- 控制的效果,例如直接限流、冷啟動、排隊等
2.2 熔斷降級
當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯故障。手段如下
- 通過並發線程數進行限制 :當線程數在特定資源上堆積到一定的數量之后,對該資源的新請求就會被拒絕。堆積的線程完成任務后才開始繼續接收請求。
- 通過響應時間對資源進行降級:當依賴的資源出現響應時間過長后,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口之后才重新恢復。
2.3 系統負載保護
Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防護中重要的一環。當系統負載較高的時候,如果還持續讓請求進入,可能會導致系統崩潰,無法響應。在集群環境下,網絡負載均衡會把本應這台機器承載的流量轉發到其它的機器上去。如果 這個時候其它的機器也處在一個邊緣狀態的時候,這個增加的流量就會導致這台機器也崩潰,最后導致整個集群不可用。
針對這個情況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力范圍之內處理最多的請求。
3.使用
3.1 依賴
這里我使用sentinel 是基於gradle配置,兼容spring clould alibaba,所以添加如下依賴
compile'com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel:2.1.0.RELEASE' compile group: 'com.alibaba.csp', name: 'sentinel-transport-simple-http', version: '1.6.3'
3.2 注解
Sentinel 提供了 @SentinelResource 注解用於定義資源,並提供了 AspectJ 的擴展用於自動定義資源、處理 BlockException等,當然也支持使用aop的方式,這里演示使用aop的方式,添加如下配置類
@Configuration public class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect();
}
}
@SentinelResource
用於定義資源,並提供可選的異常處理和 fallback 配置項 。該注解包含以下屬性
- value:資源名稱,必需項(不能為空)
- entryType:entry 類型,可選項EntryType.OUT/EntryType.IN(默認為 EntryType.OUT),對應入口控制/出口控制
- blockHandler / blockHandlerClass: blockHandler 對應處理 BlockException 的函數名稱。
- fallback:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進行處理
- 返回值類型必須與原函數返回值類型一致
- fllback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
- defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,通常用於通用的 fallback 邏輯(即可以用於很多服務或方法)。默認 fallback 函數可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。函數簽名和fallback一致
- exceptionsToIgnore(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。
3.3 示例
服務具體實現類
@Service @Slf4j public class HelloProviderServiceImpl implements HelloProviderService { @Autowired private ConfigurableEnvironment configurableEnvironment; // 對應的 `handleException` 函數需要位於 `ExceptionUtil` 類中,並且必須為 static 函數 @Override @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class}) public void test() { log.info("Test"); } @Override @SentinelResource(value = "sayHi", blockHandler = "exceptionHandler", fallback = "helloFallback") public String sayHi(long time) { if (time < 0) { throw new IllegalArgumentException("invalid arg"); } try { Thread.sleep(time); } catch (InterruptedException e) { throw new IllegalArgumentException("inter arg"); } return String.format("Hello time %d", time); }
// 這里俗稱資源埋點,在設置限流策略的時候會根據此埋點來控制 @Override @SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback", exceptionsToIgnore = {IllegalStateException.class}) public String helloAnother(String name) { if (name == null || "bad".equals(name)) { throw new IllegalArgumentException("oops"); } if ("foo".equals(name)) { throw new IllegalStateException("oops"); } return "Hello, " + name; } // Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數. public String helloFallback(long s, Throwable ex) { log.error("fallbackHandler:" + s); return "Oops fallbackHandler, error occurred at " + s; } //默認的 fallback 函數名稱 public String defaultFallback() { log.info("Go to default fallback"); return "default_fallback"; } // Block 異常處理函數,參數最后多一個 BlockException,其余與原函數一致. public String exceptionHandler(long s, BlockException ex) { // Do some log here. return "Oops,exceptionHandler, error occurred at " + s; } }
服務接口
public interface HelloProviderService { public String sayHi(long t) throws InterruptedException; String helloAnother(String name); void test(); }
ExceptionUtil類
@Slf4j public final class ExceptionUtil { public static void handleException(BlockException ex) { log.info("Oops: " + ex.getClass().getCanonicalName()); } }
controller 類
@RestController @Slf4j public class HelloProviderController { @Autowired HelloProviderServiceImpl helloServiceProviderService; @GetMapping("/sayHi") public String sayHi(@RequestParam(required = false) Long time) throws Exception { if (time == null) { time = 300L; } helloServiceProviderService.test();
return helloServiceProviderService.sayHi(time); } @GetMapping("baz/{name}") public String apiBaz(@PathVariable("name") String name) { return helloServiceProviderService.helloAnother(name); } }
3.4 Sentinel 控制台
一個輕量級的開源控制台,它提供機器發現以及健康情況管理、監控(單機和集群),規則管理和推送的功能。主要可以通過該控制台對服務端設置的資源埋點進行動態的限流配置推送,這樣可以靈活的設置限流策略而不用在代碼里寫死
- 提供web界面,可視化資源和流量監控、對資源埋點進行配置
- 具體安裝比較簡單,所以這里不再提及,可以參考鏈接
3.5 降級策略
-
平均響應時間 (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 控制台,在控制台上直接配置熔斷降級規則。
- 打開控制台界面,點擊簇點鏈路,選擇程序里的資源埋點,點擊降級
- 配置降級規則
-
- 配置RT模式測試,控制台輸入RT和窗口時間
- url:ip:port/sayHi?time=delayTime, 當 1s 內持續進入 5 個請求 平均delayTime>RT 進入降級服務
- 配置異常比例,控制台輸入異常比例
- url:ip:port/baz/bad, 當資源的每秒請求量 >= 5,並且每秒異常總數占通過量的比值設定的異常比例 將在接下來設置的窗口時間內進入降級服務
- 配置RT模式測試,控制台輸入RT和窗口時間
Feign
1. 背景
Feign是Netflix公司開源的輕量級的一種負載均衡的HTTP客戶端,,使用Feign調用API就像調用本地方法一樣,從避免了 調用目標微服務時,需要不斷的解析/封裝json 數據的繁瑣。 Spring Cloud引入Feign並且集成了Ribbon實現客戶端負載均衡調用。 通俗一點講:可以像調用本地方法一樣的調用遠程服務的方法。
當然其中也有不少坑等踩。
2.使用
Sentinel 適配了 Fegin組件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel
的依賴外還需要 2 個步驟:
-
配置文件打開 Sentinel 對 Feign 的支持:
feign.sentinel.enabled=true
- 加入
openfeign starter
依賴使sentinel starter
中的自動化配置類生效:
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '2.1.3.RELEASE'
2.1示例
添加接口 EchoService類,該接口通過@FeignClient(name = "service-provider")注解來綁定該接口對應service01服務
@FeignClient(name = "nacos-provider-sentianel1", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @GetMapping(value = "/sayHi") String sayHi(@RequestParam(value = "time", required = false) Long time); @RequestMapping("/api/{name}") String apiBaz(@PathVariable("name") String name); }
其中 @FeignClient 中name 中的值作為 提供服務提供方的名稱,該接口中配置當前服務需要調用nacos-provider-sentianel1服務提供的接口。nacos-provider-sentianel1注冊到注冊服務上,我這里使用的是Nacos.
服務配置如下
nacos-provider-sentianel1 中的controller是這個樣子的,這里可以看到 和EchoService中的方法簽名都是一致的
@RestController
public class HelloProviderController2 { @GetMapping("/echo") public String helloConsumer(@RequestParam(required = false) Long time) { return "echo"; } @GetMapping("/api/{name}") public String apiBaz(@PathVariable("name") String name) { return "another provider " + name; } }
添加 EchoServiceFallback,這里是fegin的Fallback機制,主要用來做容錯處理。因為
在網絡請求時,可能會出現異常請求,如果還想再異常情況下使系統可用,那么就需要容錯處理。
@Component。 public class EchoServiceFallback implements EchoService { @Override public String sayHi(Long time) { return "sayHi fallback"; } @Override public String apiBaz(String name) { return "apiBaz fallback"; } }
添加FeignConfiguration
@Configuration
public class FeignConfiguration { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } }
在上文HelloProviderServiceImpl的基礎上添加EchoService調用
@Service @Slf4j public class HelloProviderServiceImpl implements HelloProviderService { @Autowired private ConfigurableEnvironment configurableEnvironment; @Autowired EchoService echoService; // 對應的 `handleException` 函數需要位於 `ExceptionUtil` 類中,並且必須為 static 函數 @Override @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class}) public void test() { log.info("Test"); } @Override @SentinelResource(value = "sayHi", blockHandler = "exceptionHandler", fallback = "helloFallback") public String sayHi(long time) { if (time < 0) { throw new IllegalArgumentException("invalid arg"); } try { Thread.sleep(time); } catch (InterruptedException e) { throw new IllegalArgumentException("inter arg"); } return String.format("Hello time %d", time); } @Override @SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback", exceptionsToIgnore = {IllegalStateException.class}) public String helloAnother(String name) { if (name == null || "bad".equals(name)) { throw new IllegalArgumentException("oops"); } if ("foo".equals(name)) { throw new IllegalStateException("oops"); } return "Hello, " + name; } // Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數. public String helloFallback(long s, Throwable ex) { log.error("fallbackHandler:" + s); return "Oops fallbackHandler, error occurred at " + s; } //默認的 fallback 函數名稱 public String defaultFallback() { log.info("Go to default fallback"); return echoService.apiBaz("bad"); //return "default_fallback"; } // Block 異常處理函數,參數最后多一個 BlockException,其余與原函數一致. public String exceptionHandler(long s, BlockException ex) { // Do some log here. return "Oops,exceptionHandler, error occurred at " + s; } }
這里我們在defaultFallback中使用 echoService.apiBaz("bad") 來調用nacos-provider-sentianel1 的apiBaz方法
在sentinel控制台中配置helloAnother的降級規則,當觸發降級后,將會調用acos-provider-sentianel1服務的apiBaz方法,返回結果。
總結
使用sentinel控制系統流量,當系統流超出當前服務的接受范圍的時候,可以通過Feign 調用降級服務,這樣就可構成一個最基礎的熔斷降級模塊,當然Feign中還集成了Ribbon,可以通過配置實現客戶端負載均衡調用。