一、Sentinel 是什么?
隨着微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
Sentinel 具有以下特征:
-
豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
-
完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應用的單台機器秒級數據,甚至 500 台以下規模的集群的匯總運行情況。
-
廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
-
完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定制邏輯。例如定制規則管理、適配動態數據源等。
Sentinel 的主要特性:

Sentinel 的開源生態:

二、如何使用Sentinel
我們說的資源,可以是任何東西,服務,服務里的方法,甚至是一段代碼。使用Sentinel 來進行資源保護,主要分為兩個步驟:
- 定義資源
- 定義規則
先把可能需要保護的資源定義好,之后再配置規則。也可以理解為,只要有了資源,我們就可以在任何時候靈活地定義各種流量控制規則。在編碼的時候,只需要考慮這個代碼是否需要保護,如果需要保護,就將之定義為一個資源。
對於主流的框架,我們提供適配,只需要按照適配中的說明配置,Sentinel 就會默認定義提供的服務,方法等為資源。
三、示例
3.1、Sentinel編碼限流
先來簡單的體驗下 Sentinel 吧,在你的Maven項目中增加 Sentinel 的依賴:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.4.0</version> </dependency>
Sentinel中需要限流的稱之為資源,對資源進行處理,下面來看最簡單的一段代碼:
package com.example.demo.service; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; public class LimitByCoding { public static void main(String[] args) throws InterruptedException { initFlowRules(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { /* 您的業務邏輯 - 開始 */ System.out.println("hello world"); } long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); long startTime2 = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Entry entry = null; try { entry = SphU.entry("HelloWorld"); /* 您的業務邏輯 - 開始 */ System.out.println("hello world"); /* 您的業務邏輯 - 結束 */ } catch (BlockException e1) { /* 流控邏輯處理 - 開始 */ System.out.println("block!"); /* 流控邏輯處理 - 結束 */ } finally { if (entry != null) { entry.exit(); } } } long endTime2 = System.currentTimeMillis(); System.out.println(endTime2 - startTime2); } private static void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("HelloWorld"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // Set limit QPS to 20. rule.setCount(20); rules.add(rule); FlowRuleManager.loadRules(rules); } }
第一行中初始化限流的規則,創建了一個資源叫 HelloWorld,設置了這個資源的QPS 為 20。
結果:

上面的例子中次數為1000次,只輸出一句話,不做任何限制,執行完成的時間大概在9毫秒左右。
下面加上限流的邏輯,執行完成的時間基本上就超過91毫秒了,可見限流起了作用。
在業務開始前使用SphU.entry();方法標識開始,結束使用entry.exit();,如果觸發了流控邏輯就會拋出BlockException異常讓用戶自行處理。
代碼運行之后,我們可以在日志 ~/logs/csp/${appName}-metrics.log.xxx 里看到下面的輸出:
|--timestamp---|------date time--|-resource-|p |block|s |e|rt 1529998904000|2018-06-26 15:41:44|HelloWorld|20|0 |20|0|0 1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728 1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0 1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0 1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0 1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0
-
p:通過的請求,
-
block:被阻止的請求
-
s:成功執行完成的請求個數
-
e:用戶自定義的異常
-
rt:平均響應時長。
3.2、Sentinel注解限流
這種做法不好的地方在於每個需要限制的地方都得寫代碼,從 0.1.1 版本開始,Sentinel 提供了 @SentinelResource 注解的方式,非常方便。
要使用注解來保護資源需要引入下面的Maven依賴:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.4.0</version> </dependency>
完整的pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.19.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.dxz</groupId> <artifactId>LimitDemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>LimitDemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.3.17.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> <version>1.7.10</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
引入之后我們需要配置SentinelResourceAspect切面讓其生效,因為是通過SentinelResourceAspect切面來實現的,我這邊以Spring Boot中使用進行配置示列:
package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; @Configuration public class AopConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } }
然后在需要限制的方法上加SentinelResource注解即可:
package com.example.demo.service; import org.springframework.stereotype.Component; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.example.demo.ExceptionUtil; @Component public class BuzService { @SentinelResource(value = "get", blockHandler = "exceptionHandler") public String get(String id) { return "http://cxytiandi.com"; } @SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class }) public String get2() { return "http://cxytiandi.com"; } public String exceptionHandler(String id, BlockException e) { e.printStackTrace(); return "錯誤發生在" + id; } }
SentinelResource:value
表示資源名,必填項
SentinelResource:blockHandler
處理 BlockException 的方法名,可選項。若未配置,則將 BlockException 直接拋出。
-
blockHandler 函數訪問范圍需要是 public
-
返回類型需要與原方法相匹配
-
參數類型需要和原方法相匹配並且最后加一個額外的參數,類型為 BlockException
-
blockHandler 函數默認需要和原方法在同一個類中
如果你不想讓異常處理方法跟業務方法在同一個類中,可以使用 blockHandlerClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
業務方法:
@SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class })
public String get2() {
return "http://cxytiandi.com";
}
異常處理類:
package com.example.demo; import com.alibaba.csp.sentinel.slots.block.BlockException; public final class ExceptionUtil { public static String handleException(BlockException ex) { System.err.println("錯誤發生: " + ex.getClass().getCanonicalName()); return "error"; } }
如何測試?
我們可以在Spring Boot的啟動類中定義規則,然后快速訪問接口,就可以看出效果啦,或者用壓力測試工具ab等。
package com.example.demo; import java.util.ArrayList; import java.util.List; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; @SpringBootApplication public class LimitDemoApplication { public static void main(String[] args) { initFlowRules(); SpringApplication.run(LimitDemoApplication.class, args); } private static void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("get"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rules.add(rule); rule = new FlowRule(); rule.setResource("get2"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rules.add(rule); FlowRuleManager.loadRules(rules); } }
結果:

源碼分析
只需要配置了SentinelResourceAspect就可以使用注解,我們來簡單的看下SentinelResourceAspect的源碼
@Aspect public class SentinelResourceAspect extends AbstractSentinelAspectSupport { @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { } @Around("sentinelResourceAnnotationPointcut()") public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable { // 獲取當前訪問的方法 Method originMethod = resolveMethod(pjp); // 獲取方法上的SentinelResource注解 SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class); if (annotation == null) { // Should not go through here. throw new IllegalStateException("Wrong state for SentinelResource annotation"); } // 獲取資源名 String resourceName = getResourceName(annotation.value(), originMethod); EntryType entryType = annotation.entryType(); Entry entry = null; try { entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs()); Object result = pjp.proceed(); return result; } catch (BlockException ex) { // 處理被限制的異常,回調事先配置的異常處理方法 return handleBlockException(pjp, annotation, ex); } catch (Throwable ex) { Tracer.trace(ex); throw ex; } finally { if (entry != null) { entry.exit(); } } } }
上面是整個切面的代碼,對所有加了SentinelResource注解的方法進去切入。細節代碼在AbstractSentinelAspectSupport中,大家自己去看看。
