一、什么是Sentinel:Sentinel是阿里開源的項目,提供了流量控制、熔斷降級、系統負載保護等多個維度來保障服務之間的穩定性。
官網:https://github.com/alibaba/Sentinel/wiki
Sentinel主要特性:
Sentinel與Hystrix的區別
關於Sentinel與Hystrix的區別見:https://yq.aliyun.com/articles/633786/
總體來說:
Hystrix常用的線程池隔離會造成線程上下切換的overhead比較大;Hystrix使用的信號量隔離對某個資源調用的並發數進行控制,效果不錯,但是無法對慢調用進行自動降級;Sentinel通過並發線程數的流量控制提供信號量隔離的功能;
此外,Sentinel支持的熔斷降級維度更多,可對多種指標進行流控、熔斷,且提供了實時監控和控制面板,功能更為強大。
二、代碼示例
下面跟隨官網中提供的Quick Start Demo,去看看如何使用Sentinel實現限流和降級。
注意:
JDK >= 1.7;
Sentinel版本為1.7.0;
引入Sentinel jar包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.0</version>
</dependency>
1、限流
關於限流的使用和介紹見:Sentinel流量控制
流量控制(Flow Control),原理是監控應用流量的QPS或並發線程數等指標,當達到指定閾值時對流量進行控制,避免系統被瞬時的流量高峰沖垮,保障應用高可用性。
下面寫個簡單的示例,看看如何使用Sentinel實現限流。
首先寫個簡單的訂單查詢接口,用於后續接口限流示例:
@Component
public class OrderQueryService {
public String queryOrderInfo(String orderId) {
System.out.println("獲取訂單信息:" + orderId);
return "return OrderInfo :" + orderId;
}
}
@Controller
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderQueryService orderQueryService;
@RequestMapping("/getOrder")
@ResponseBody
public String queryOrder1(@RequestParam("orderId") String orderId) {
return orderQueryService.queryOrderInfo(orderId);
}
}正常情況下,調用OrderController中訂單查詢接口,會返回訂單信息,如何控制接口訪問的QPS在2以下呢?Sentienl限流提供了以下實現方式:
首先需要定義限流規則,比如對哪個接口進行限流,限制的QPS為多少,限制調用方app是什么等:
public void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
// QPS控制在2以內
rule1.setCount(2);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
限流實現方式有多種,本文只列出常見兩種:
(1)限流實現方式一: 拋出異常的方式定義資源
此方式對代碼侵入性較高,需要在接口調用的地方通過try-catch風格的API對代碼進行包裝:
/**
* 限流實現方式一: 拋出異常的方式定義資源
*
* @param orderId
* @return
*/
@RequestMapping("/getOrder1")
@ResponseBody
public String queryOrder2(@RequestParam("orderId") String orderId) {
Entry entry = null;
// 資源名
String resourceName = KEY;
try {
// entry可以理解成入口登記
entry = SphU.entry(resourceName);
// 被保護的邏輯, 這里為訂單查詢接口
return orderQueryService.queryOrderInfo(orderId);
} catch (BlockException blockException) {
// 接口被限流的時候, 會進入到這里
log.warn("---getOrder1接口被限流了---, exception: ", blockException);
return "接口限流, 返回空";
} finally {
// SphU.entry(xxx) 需要與 entry.exit() 成對出現,否則會導致調用鏈記錄異常
if (entry != null) {
entry.exit();
}
}
}
測試,當QPS > 2時,接口返回:
(2)限流實現方式二: 注解方式定義資源
上述通過try-catch風格的API可以實現限流,但是對代碼侵入性太高,推薦使用注解的方式來實現。下文若不做注明,默認都會采用注解的方式實現。
首先需要引入支持注解的jar包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>${sentinel.version}</version>
</dependency>
Sentinel切面類配置:
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
在接口OrderQueryService中,使用注解實現訂單查詢接口的限流:
/**
* 訂單查詢接口, 使用Sentinel注解實現限流
*
* @param orderId
* @return
*/
@SentinelResource(value = "getOrderInfo", blockHandler = "handleFlowQpsException",
fallback = "queryOrderInfo2Fallback")
public String queryOrderInfo2(String orderId) {
// 模擬接口運行時拋出代碼異常
if ("000".equals(orderId)) {
throw new RuntimeException();
}
System.out.println("獲取訂單信息:" + orderId);
return "return OrderInfo :" + orderId;
}
/**
* 訂單查詢接口拋出限流或降級時的處理邏輯
*
* 注意: 方法參數、返回值要與原函數保持一致
* @return
*/
public String handleFlowQpsException(String orderId, BlockException e) {
e.printStackTrace();
return "handleFlowQpsException for queryOrderInfo2: " + orderId;
}
/**
* 訂單查詢接口運行時拋出的異常提供fallback處理
*
* 注意: 方法參數、返回值要與原函數保持一致
* @return
*/
public String queryOrderInfo2Fallback(String orderId, Throwable e) {
return "fallback queryOrderInfo2: " + orderId;
}
blockHandler = "handleFlowQpsException"用來處理Sentinel 限流/熔斷等錯誤;
fallback = "queryOrderInfo2Fallback"用來處理接口中業務代碼所有異常(如業務代碼異常、sentinel限流熔斷異常等);
注:以上兩種處理方法中方法名、參數都需與受保護的函數保持一致。
測試:
/**
* 限流實現方式二: 注解定義資源
*
* @param orderId
* @return
*/
@RequestMapping("/getOrder2")
@ResponseBody
public String queryOrder3(@RequestParam("orderId") String orderId) {
return orderQueryService.queryOrderInfo2(orderId);
}
測試結果在這里就不貼出來了,結果類似。
下面就使用基於注解的方式實現Sentinel的熔斷降級的demo。
@Component
@Slf4j
public class GoodsQueryService {
private static final String KEY = "queryGoodsInfo2";
/**
* 模擬商品查詢接口
*
* @param spuId
* @return
*/
@SentinelResource(value = KEY, blockHandler = "blockHandlerMethod", fallback = "queryGoodsInfoFallback")
public String queryGoodsInfo(String spuId) {
// 模擬調用服務出現異常
if ("0".equals(spuId)) {
throw new RuntimeException();
}
return "query goodsinfo success, " + spuId;
}
public String blockHandlerMethod(String spuId, BlockException e) {
log.warn("queryGoodsInfo222 blockHandler", e.toString());
return "queryGoodsInfo error, blockHandlerMethod res: " + spuId;
}
public String queryGoodsInfoFallback(String spuId, Throwable e) {
log.warn("queryGoodsInfo222 fallback", e.toString());
return "queryGoodsInfo error, return fallback res: " + spuId;
}
@PostConstruct
public void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// 80s內調用接口出現異常次數超過5的時候, 進行熔斷
rule.setCount(5);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setTimeWindow(80);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
// 測試類
@Controller
@RequestMapping("goods")
public class GoodsController {
@Autowired
private GoodsQueryService goodsQueryService;
@RequestMapping("/queryGoodsInfo")
@ResponseBody
public String queryGoodsInfo(@RequestParam("spuId") String spuId) {
String res = goodsQueryService.queryGoodsInfo(spuId);
return res;
}
}
三、控制台的使用
Sentinel 提供一個輕量級的開源控制台,它提供機器發現以及健康情況管理、監控(單機和集群),規則管理和推送的功能。
主要功能有:
查看機器列表以及健康情況;
監控;
規則管理和推送;
鑒權;
1、啟動Sentinel控制台
如何啟動控制台見:啟動控制台
啟動方式:
(1)下載jar包,通過命令方式
在https://github.com/alibaba/Sentinel/releases下載對應版本的dashboard jar包,進入到該jar所在的目錄,然后通過java命令運行該jar包即可:
java -Dserver.port=8080 \
-Dcsp.sentinel.dashboard.server=localhost:8080 \
-jar target/sentinel-dashboard.jar
(2)git clone 整個sentinel源碼,進入sentinel-dashboard模塊,執行打包命令:mvn clean package,生成一個可執行的 fat jar包,然后啟動控制台,輸入url:localhost:8080后即可進入主頁面。
2、客戶端接入(Spring Boot項目接入控制台)
啟動了控制台模塊后,控制台頁面都是空的,需要接入客戶端。
(1)首先導入與控制台通信的jar包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
(2)配置 JVM 啟動參數:
-Dproject.name=sentinel-demo -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dcsp.sentinel.api.port=8719
啟動應用。
注:在應用中需要有觸發限流、降級的初始化代碼。
本demo中http://localhost:8083/order/getOrder1?orderId=123接口執行多次,會觸發限流操作,這時候再去看控制台:
可以看到,getOrderInfo接口被限流的次數。
4、控制台的使用
如果我們想使用Sentinel的限流和熔斷功能,除了在代碼中可以硬編碼外,也可直接通過控制台進行粗略的對接口進行限流和熔斷。
/**
* 代碼不加任何限流 熔斷
* @return
*/
@RequestMapping("/test")
@ResponseBody
@SentinelResource("test")
public String test() {
return "test";
}
執行http://localhost:8083/goods/test接口動作后,在頁面上簇點鏈路中即可看到該接口已經被統計到:
點擊流控,對該接口新增一條流控規則,配置如下:
點擊新增后,
再去執行接口http://localhost:8083/goods/test,當QPS超過2時,即可看到接口返回異常,達到流控效果。
當然,控制台流控規則、降級規則的使用還有很多,可以參考官方文檔看看具體如何使用。
PS
在接入控制台過程中,發現控制台一直會報error:
ERROR 5317 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher : Failed to fetch metric from <http://10.200.183.30:8721/metric?startTime=1575117735000&endTime=1575117741000&refetch=false> (ConnectionException: Connection refused)
錯誤原因是控制台在收集客戶端數據時,從IP地址http://10.200.183.30:8721連接不上;
后發現原來開啟了代理,關閉代理后就正常。
參考:
官方文檔:https://github.com/alibaba/Sentinel/wiki
原文鏈接:https://blog.csdn.net/noaman_wgs/article/details/103328793