前言
Sentinel 是什么巴元?
隨著微服務(wù)的流行,服務(wù)和服務(wù)之間的穩(wěn)定性變得越來越重要陋率。Sentinel 以流量為切入點(diǎn)球化,從流量控制、熔斷降級(jí)瓦糟、系統(tǒng)負(fù)載保護(hù)等多個(gè)維度保護(hù)服務(wù)的穩(wěn)定性筒愚。
Sentinel 的歷史
- 2012 年,Sentinel 誕生菩浙,主要功能為入口流量控制巢掺。
- 2013-2017 年,Sentinel 在阿里巴巴集團(tuán)內(nèi)部迅速發(fā)展劲蜻,成為基礎(chǔ)技術(shù)模塊陆淀,覆蓋了所有的核心場(chǎng)景。Sentinel 也因此積累了大量的流量歸整場(chǎng)景以及生產(chǎn)實(shí)踐先嬉。
- 2018 年轧苫,Sentinel 開源,并持續(xù)演進(jìn)疫蔓。
- 2019 年含懊,Sentinel 朝著多語言擴(kuò)展的方向不斷探索,推出 C++ 原生版本鳄袍,同時(shí)針對(duì) Service Mesh 場(chǎng)景也推出了 Envoy 集群流量控制支持,以解決 Service Mesh 架構(gòu)下多語言限流的問題拗小。
- 2020 年哀九,推出 Sentinel Go 版本,繼續(xù)朝著云原生方向演進(jìn)阅束。
Sentinel 特征
- 豐富的應(yīng)用場(chǎng)景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場(chǎng)景茄唐,例如秒殺(即突發(fā)流量控制在系統(tǒng)容量可以承受的范圍)蝇更、消息削峰填谷呼盆、集群流量控制、實(shí)時(shí)熔斷下游不可用應(yīng)用等厨幻。
- 完備的實(shí)時(shí)監(jiān)控:Sentinel 同時(shí)提供實(shí)時(shí)的監(jiān)控功能。您可以在控制臺(tái)中看到接入應(yīng)用的單臺(tái)機(jī)器秒級(jí)數(shù)據(jù)腿时,甚至 500 臺(tái)以下規(guī)模的集群的匯總運(yùn)行情況况脆。
- 廣泛的開源生態(tài):Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud批糟、Dubbo格了、gRPC 的整合。您只需要引入相應(yīng)的依賴并進(jìn)行簡單的配置即可快速地接入 Sentinel徽鼎。
- 完善的 SPI 擴(kuò)展點(diǎn):Sentinel 提供簡單易用盛末、完善的 SPI 擴(kuò)展接口。您可以通過實(shí)現(xiàn)擴(kuò)展接口來快速地定制邏輯纬傲。例如定制規(guī)則管理满败、適配動(dòng)態(tài)數(shù)據(jù)源等。
Sentinel 的主要特性
Sentinel 的開源生態(tài)
Sentinel 分為兩個(gè)部分
- 核心庫(Java 客戶端)不依賴任何框架/庫叹括,能夠運(yùn)行于所有 Java 運(yùn)行時(shí)環(huán)境算墨,同時(shí)對(duì) Dubbo / Spring Cloud 等框架也有較好的支持。
- 控制臺(tái)(Dashboard)基于 Spring Boot 開發(fā)汁雷,打包后可以直接運(yùn)行净嘀,不需要額外的 Tomcat 等應(yīng)用容器。
快速開始
1.添加pom依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
注意: 從 Sentinel 1.5.0 開始僅支持 JDK 1.7 或者以上版本侠讯。Sentinel 1.5.0 之前的版本最低支持 JDK 1.6挖藏。
2.定義資源
接下來,我們把需要控制流量的代碼用 Sentinel API SphU.entry("HelloWorld") 和 entry.exit() 包圍起來即可厢漩。
public static void main(String[] args) {
// 配置規(guī)則.
initFlowRules();
while (true) {
Entry entry = null;
try {
entry = SphU.entry("HelloWorld");
/*您的業(yè)務(wù)邏輯 - 開始*/
System.out.println("hello world");
/*您的業(yè)務(wù)邏輯 - 結(jié)束*/
} catch (BlockException e1) {
/*流控邏輯處理 - 開始*/
System.out.println("block!");
/*流控邏輯處理 - 結(jié)束*/
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
3.定義規(guī)則
接下來膜眠,通過規(guī)則來指定允許該資源通過的請(qǐng)求次數(shù),例如下面的代碼定義了資源 HelloWorld 每秒最多只能通過 20 個(gè)請(qǐng)求溜嗜。
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);
}
注解支持
Sentinel 提供了 @SentinelResource
注解用于定義資源宵膨,并提供了 AspectJ 的擴(kuò)展用于自動(dòng)定義資源、處理 BlockException
等炸宵。使用 Sentinel Annotation AspectJ Extension 的時(shí)候需要引入以下依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>x.y.z</version>
</dependency>
將 SentinelResourceAspect 注冊(cè)為一個(gè) Spring Bean
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
示例代碼
public class TestService {
// 對(duì)應(yīng)的 `handleException` 函數(shù)需要位于 `ExceptionUtil` 類中辟躏,并且必須為 static 函數(shù).
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}
// 原函數(shù)
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}
// Fallback 函數(shù)会涎,函數(shù)簽名與原函數(shù)一致或加一個(gè) Throwable 類型的參數(shù).
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}
// Block 異常處理函數(shù)末秃,參數(shù)最后多一個(gè) BlockException蛔溃,其余與原函數(shù)一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}
代碼實(shí)戰(zhàn)
新建一個(gè)SpringBoot的項(xiàng)目
1.pom依賴
<?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 https://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>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.sentinel</groupId>
<artifactId>sentinel-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sentinel-demo</name>
<description>sentinel demo</description>
<properties>
<java.version>1.8</java.version>
<spring.boot.version>2.2.1.RELEASE</spring.boot.version>
<sentinel.version>1.7.0</sentinel.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.Controller
package com.example.sentinel.sentineldemo.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.example.sentinel.sentineldemo.service.TestSentinelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author: Monday
* @Date: 2020/4/1 0001 上午 11:44
* @Description:
*/
@Controller
@RequestMapping("test")
public class TestSentinelController {
private static final String KEY = "queryOne";
@Autowired
private TestSentinelService testSentinelService;
/**
* 代碼不加任何限流 熔斷
*
* @return
*/
@RequestMapping("/getValue_0")
@ResponseBody
@SentinelResource("queryZero")
public String getValue_0(@RequestParam("key") String key) {
return testSentinelService.getValue_0(key);
}
/**
* 限流實(shí)現(xiàn)方式一: 拋出異常的方式定義資源
*
* @param key
* @return
*/
@RequestMapping("/getValue_1")
@ResponseBody
public String getValue_1(@RequestParam("key") String key) {
Entry entry = null;
// 資源名
String resourceName = KEY;
try {
// entry可以理解成入口登記
entry = SphU.entry(resourceName);
// 被保護(hù)的邏輯, 這里為查詢接口
return testSentinelService.getValue_1(key);
} catch (BlockException blockException) {
// 接口被限流的時(shí)候, 會(huì)進(jìn)入到這里
return "接口限流, 返回空";
} finally {
// SphU.entry(xxx) 需要與 entry.exit() 成對(duì)出現(xiàn),否則會(huì)導(dǎo)致調(diào)用鏈記錄異常
if (entry != null) {
entry.exit();
}
}
}
/**
* 限流實(shí)現(xiàn)方式二: 注解定義資源
*
* @param key
* @return
*/
@RequestMapping("/getValue_2")
@ResponseBody
public String getValue_2(@RequestParam("key") String key) {
String res = testSentinelService.getValue_2(key);
return res;
}
}
3.Service
package com.example.sentinel.sentineldemo.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Monday
* @Date: 2020/4/1 0001 上午 11:45
* @Description: 商品查詢接口
*/
@Component
@Slf4j
public class TestSentinelService {
private static final String KEY = "queryTwo";
/**
* 代碼不加任何限流 熔斷
*
* @param key
* @return
*/
public String getValue_0(String key) {
System.out.println("獲取Value:" + key);
return "return value :" + key;
}
/**
* 拋出異常的方式定義資源
*
* @param key
* @return
*/
public String getValue_1(String key) {
System.out.println("獲取Value:" + key);
return "return value :" + key;
}
/**
* 注解定義資源
*
* @param key
* @return
*/
@SentinelResource(value = KEY, blockHandler = "blockHandlerMethod", fallback = "queryFallback")
public String getValue_2(String key) {
// 模擬調(diào)用服務(wù)出現(xiàn)異常
if ("0".equals(key)) {
throw new RuntimeException();
}
return "query value success, " + key;
}
public String blockHandlerMethod(String key, BlockException e) {
return "queryValue error, blockHandlerMethod res: " + key;
}
public String queryFallback(String key, Throwable e) {
return "queryValue error, return fallback res: " + key;
}
/**
* 初始化限流配置
*/
@PostConstruct
public void initDegradeRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
// QPS控制在2以內(nèi)
rule1.setCount(2);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
}
4.控制臺(tái)
4.1下載
從 release 頁面 下載截止目前為止最新版本的控制臺(tái) jar 包
注意:
啟動(dòng) Sentinel 控制臺(tái)需要 JDK 版本為 1.8 及以上版本
從 Sentinel 1.6.0 起,Sentinel 控制臺(tái)引入基本的 登錄 功能雁比,默認(rèn)用戶名和密碼都是 sentinel
用戶可以通過如下參數(shù)進(jìn)行配置
-
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制臺(tái)的登錄用戶名為 sentinel -
-Dsentinel.dashboard.auth.password=123456
用于指定控制臺(tái)的登錄密碼為 123456偎捎;如果省略這兩個(gè)參數(shù)茴她,默認(rèn)用戶和密碼均為 sentinel -
-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服務(wù)端 session 的過期時(shí)間丈牢,如 7200 表示 7200 秒瞄沙;60m 表示 60 分鐘泛粹,默認(rèn)為 30 分鐘
4.2啟動(dòng)
java -jar sentinel-dashboard-1.7.1.jar
4.3登錄
可以看到當(dāng)前控制臺(tái)中沒有任何的應(yīng)用,因?yàn)檫€沒有應(yīng)用接入伪货。
5.客戶端接入
啟動(dòng)了控制臺(tái)模塊后碱呼,控制臺(tái)頁面都是空的愚臀,需要接入客戶端。
5.1導(dǎo)入與控制臺(tái)通信的jar包
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
5.2 配置應(yīng)用啟動(dòng)參數(shù)
引入了依賴之后馋袜,接著就是在我們的應(yīng)用中配置 JVM 啟動(dòng)參數(shù)欣鳖,如下所示:
-Dproject.name=xxx -Dcsp.sentinel.dashboard.server=consoleIp:port
其中的consoleIp和port對(duì)應(yīng)的就是我們部署的 sentinel dashboard 的ip和port泽台,我這里對(duì)應(yīng)的是 127.0.0.1 和 8080怀酷,按照實(shí)際情況來配置 dashboard 的ip和port就好了蜕依,如下圖所示:
5.3 啟動(dòng)應(yīng)用
啟動(dòng)上邊的springboot項(xiàng)目
5.4 測(cè)試效果
本demo中http://localhost:8083/test/getValue_2?key=kobe接口執(zhí)行多次笔横,會(huì)觸發(fā)限流操作,這時(shí)候再去看控制臺(tái):