Spring Cloud Alibaba - Sentinel
Sentinel
簡介
在專案配置檔案中新增配置
開啟控制檯dashboard頁面需要下載Sentinel-dashboard.jar,並啟動該spring boot專案。控制頁面預設為:localhost:8080 密碼和使用者名稱都為:sentinel
Sentinel控制檯呼叫的AIP:
localhost:8719/api
Dashboard頁面所有的資訊都是通過這個地址進行獲取的
流量控制規則
開啟對應的服務面板,點選簇點鏈路 進入配置頁面。選擇對應的資源名稱,點選流控按鈕開始配置。
在面板中直接配置即可
配置完成後會顯示配置記錄資訊:
當訪問請求數量超過設定的閾值時,預設返回的資訊為:
此資訊可以自定義
tips:沒有做持久化的Sentinel每次重啟都不會儲存做過的配置,所有的配置在無持久化的情況下都是儲存在記憶體中的
實現限流自定義返回結果
自定義RestObject,返回結果部分資訊存在此物件中:
@Builder //生成構建器模式
@Data
public class RestObject {
private int statusCode;
private String statusMessage;
private Object data;
}
編寫一個類實現BlockExceptionHandler
介面:
@Slf4j @SuppressWarnings("all") @Component public class MyBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { log.info("UrlBolckHandler--------------------------"); RestObject restObject = null; //不同異常返回不同提示語 if (e instanceof FlowException){ restObject = RestObject.builder().statusCode(100).statusMessage("介面限流了").build(); }else if (e instanceof DegradeException){ restObject = RestObject.builder().statusCode(101).statusMessage("服務降級了").build(); }else if (e instanceof ParamFlowException){ restObject = RestObject.builder().statusCode(102).statusMessage("熱點引數限流了").build(); }else if (e instanceof SystemBlockException){ restObject = RestObject.builder().statusCode(103).statusMessage("觸發系統保護規則").build(); }else if (e instanceof AuthorityException){ restObject = RestObject.builder().statusCode(104).statusMessage("授權規則不通過 ").build(); } httpServletResponse.setStatus(500); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); //Spring MVC 的一個JSON轉換類(Jackson) new ObjectMapper().writeValue(httpServletResponse.getWriter(),restObject); } }
當超過限流設定的閾值時,返回資訊:
如果需要限流以後返回一個頁面則需要更改MyBlockExceptionHandler
中的部分程式碼:
@Slf4j @SuppressWarnings("all") @Component public class MyBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { log.info("UrlBolckHandler--------------------------"); RestObject restObject = null; //不同異常返回不同提示語 if (e instanceof FlowException){ restObject = RestObject.builder().statusCode(100).statusMessage("介面限流了").build(); }else if (e instanceof DegradeException){ restObject = RestObject.builder().statusCode(101).statusMessage("服務降級了").build(); }else if (e instanceof ParamFlowException){ restObject = RestObject.builder().statusCode(102).statusMessage("熱點引數限流了").build(); }else if (e instanceof SystemBlockException){ restObject = RestObject.builder().statusCode(103).statusMessage("觸發系統保護規則").build(); }else if (e instanceof AuthorityException){ restObject = RestObject.builder().statusCode(104).statusMessage("授權規則不通過 ").build(); } httpServletResponse.sendRedirect("http://www.baidu.com"); //進行頁面重定向,前後端分離不知道是否可行 } }
Sentinel配置中一些引數
關聯設定:
鏈路:
對入口資源監聽,超過設定閾值限制本資源的訪問。入口資源為所屬組名
Sentinel服務降級RT
Sentinel服務降級異常比例和異常數
異常比例:範圍為0.0 - 1.0,總請求數量*異常比例的請求數會進入服務降級時間為時間視窗設定的時間
異常數:
Sentinel熱點引數規則
通過@SentinelResource註解對方法加入Sentinel監控:
@RestController
@SuppressWarnings("all")
public class SentinelController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/app")
@SentinelResource(value = "app",fallback = "fallback2",fallbackClass = MyFallbackClass.class)
public String app(@RequestParam(value = "a",required = false)String a,
@RequestParam(value = "b",required = false)String b){
System.out.println("/app/--->"+a+"---"+b);
return restTemplate.getForObject("http://nacos-discovery-provider/test",String.class);
}
}
設定的發生熔斷時,呼叫的類:MyFallbackClass
@Component
public class MyFallbackClass {
public static String fallback(String a,String b){
System.out.println("Fall back--->"+a+"---"+b);
return "Fall back";
}
public static String fallback2(String a,String b){
System.out.println("Fall back2--->"+a+"---"+b);
return "Fall back2";
}
}
blockHandler = "block",blockHandlerClass = MyBlockHanlderClass.class處理限流
fallback = "fallback",fallbackClass = MyFallbackClass.class 處理降級
在這裡,發生熔斷時呼叫MyFallbackClass中的fallback2方法
設定規則:
超過閾值則顯示:
如果設定如下:
則表示,當設定的引數值為5時,它的閾值為2。超過閾值進行限流服務降級。
Sentinel系統保護規則
Sentinel授權規則
白名單:可以呼叫;黑名單:不能呼叫
對於黑白名單的標識可以放在引數、header中。
對於下列設定:
則請求為:
localhost:9000/app?origin=order
對於這裡的源origin,我們需要實現一個解析類,將其中的源名返回,如果和設定的流程應用名相同,並且設定為黑名單則該請求無法訪問。返回:
解析類:
@Component
public class MyRequestOriginParser implements RequestOriginParser {
//其中獲取的欄位名字需要和請求中的名字相對應
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//從引數中獲取
String origin = httpServletRequest.getParameter("origin");
//從header中獲取
String origin_header = httpServletRequest.getHeader("origin");
if (StringUtils.isBlank(origin)){
try {
throw new IllegalAccessException("origin引數未指定");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return origin;
}
}
請求中的origin欄位也可以變為其他的,但是需要和解析類中的獲取的欄位一致即可(猜測)。如果使用origin_header,則請求中就不應該再帶有origin=order,而是應該將該資訊放入到請求header中去。
Sentinel-dashboard控制檯通訊原理
微服務啟動後,暴露很多http介面,Sentinel-Dashboard通過呼叫這些介面(localhost:8719/api)進行對微服務的操控。
tips: Sentinel的一些配置
Sentinel三種保護應用的方式
這種方式用的比較少:
@GetMapping("/protect")
public String protect(@PathVariable("/app")String app){
System.out.println("protect: "+app);
ContextUtil.enter("protect","protect");//origin 服務請求來源,去掉也可以。用來實現授權規則的。
Entry entry = null;
try {
entry = SphU.entry("protect"); //分散式鎖,開始保護下面的程式碼
//業務邏輯
int a = 10/0;
return restTemplate.getForObject("http://nacos-discovery-provider/test",String.class);
} catch (BlockException e) {
e.printStackTrace();
//手動寫服務降級程式碼 (參考MyBlockExceptionHandler.class)
return "熔斷降級了";
}catch (ArithmeticException e){
Tracer.trace(e); //對此類異常進行跟蹤(trace),Sentinel才能對異常進行監控
return "除數不能為0";
}finally {
if (entry!=null){
entry.exit();
}
ContextUtil.exit();
}
}
MyFallbackClass:
@Component
public class MyFallbackClass {
public static String fallback(String a,String b){
System.out.println("Fall back--->"+a+"---"+b);
return "Fall back";
}
public static String fallback2(String a,String b){
System.out.println("Fall back2--->"+a+"---"+b);
return "Fall back2";
}
}
也可使用blockHandler+blockHandlerClass,規則與fallback+fallbackClass一致。需要注意的是:在MyBlockHandlerClass和MyFallbackClass中的所有被呼叫的方法都要定義為Static型別!!!
RestTemplate整合Sentinel
這樣Sentinel可以對RestTemplate的呼叫進行保護(限流,許可權......)注意註解中需要的是降級還是限流。
限流-->blockHandler+blockHandlerClass
降級-->fallback+fallbackClass
Feign整合Sentinel
第一種方法-配置fallback:
//name 遠端呼叫服務提供者的名字 fallback 降級處理 configuration feign 配置
@FeignClient(name = "nacos-discovery-provider",
fallback = EchoServiceFallback.class,
configuration = FeignConfiguration.class)
public interface EchoService {
//getMapping 中的定義需要和對應的服務提供者中的介面呼叫一致
@GetMapping("/echo-feign")
public String echo();
}
EchoServiceFallback:
@SuppressWarnings("all")
public class EchoServiceFallback implements EchoService{
//對EchoService中的同名方法進行服務降級時的操作
@Override
public String echo() {
return "has been low down";
}
}
第二種方法-配置fallbackFactory:
@FeignClient(name = "nacos-discovery-provider",
// fallback = EchoServiceFallback.class,
fallbackFactory = EchoServiceFallbackFactory.class,
configuration = FeignConfiguration.class)
public interface EchoService {
//getMapping 中的定義需要和對應的服務提供者中的介面呼叫一致
@GetMapping("/echo-feign")
public String echo();
}
EchoServiceFallbackFactory.class:
@SuppressWarnings("all")
//FallbackFactory<EchoService>中泛型的種類為controller介面中呼叫的型別
public class EchoServiceFallbackFactory implements FallbackFactory<EchoService> {
@Override
public EchoService create(Throwable throwable) {
return new EchoService() {
//對其中的所有方法設定降級實現
@Override
public String echo() {
return "from fallbackFactory";
}
};
}
Sentinel持久化
原始模式
拉模式
Pull模式
pull 模式的資料來源(如本地檔案、RDBMS 等)一般是可寫入的。使用時需要在客戶端註冊資料來源:將對應的讀資料來源註冊至對應的 RuleManager,將寫資料來源註冊至 transport 的 WritableDataSourceRegistry
中。以本地檔案資料來源為例:
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
String flowRulePath = "xxx";
ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
);
// 將可讀資料來源註冊至 FlowRuleManager.
FlowRuleManager.register2Property(ds.getProperty());
WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
// 將可寫資料來源註冊至 transport 模組的 WritableDataSourceRegistry 中.
// 這樣收到控制檯推送的規則時,Sentinel 會先更新到記憶體,然後將規則寫入到檔案中.
WritableDataSourceRegistry.registerFlowDataSource(wds);
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
本地檔案資料來源會定時輪詢檔案的變更,讀取規則。這樣我們既可以在應用本地直接修改檔案來更新規則,也可以通過 Sentinel 控制檯推送規則。以本地檔案資料來源為例,推送過程如下圖所示:
首先 Sentinel 控制檯通過 API 將規則推送至客戶端並更新到記憶體中,接著註冊的寫資料來源會將新的規則儲存到本地的檔案中。使用 pull 模式的資料來源時一般不需要對 Sentinel 控制檯進行改造。
這種實現方法好處是簡單,不引入新的依賴,壞處是無法保證監控資料的一致性。
實操:自定義類FileDataSourceInit:
@SuppressWarnings("all")
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
//可以根據需要指定規則檔案的存放位置
String ruleDir = "E:\\software\\JAVA\\springcloud-alibaba\\package\\Sentinel-dashboard";
String flowRulePath = ruleDir+"/flow-rule.json";
String degradeRulePath = ruleDir+"/degrade-rule.json";
String paramFlowRulePath = ruleDir+"/param-flow-rule.json";
String systemRulePath = ruleDir+"/system-rule.json";
String authorityRulePath = ruleDir+"/authority-rule.json";
this.mkdirIfNotExist(ruleDir);
this.createFileIfNotExist(flowRulePath);
this.createFileIfNotExist(degradeRulePath);
this.createFileIfNotExist(paramFlowRulePath);
this.createFileIfNotExist(systemRulePath);
this.createFileIfNotExist(authorityRulePath);
//流控規則:可讀資料來源
ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
);
// 將可讀資料來源註冊至 FlowRuleManager.
FlowRuleManager.register2Property(ds.getProperty());
WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
// 將可寫資料來源註冊至 transport 模組的 WritableDataSourceRegistry 中.
// 這樣收到控制檯推送的規則時,Sentinel 會先更新到記憶體,然後將規則寫入到檔案中.
WritableDataSourceRegistry.registerFlowDataSource(wds);
//降級規則,可讀資料來源
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath, source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {})
);
// 將可讀資料來源註冊至 FlowRuleManager.
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(degradeRulePath, this::encodeJson);
// 將可寫資料來源註冊至 transport 模組的 WritableDataSourceRegistry 中.
// 這樣收到控制檯推送的規則時,Sentinel 會先更新到記憶體,然後將規則寫入到檔案中.
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
//熱點引數規則:可讀資料來源
ReadableDataSource<String,List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<List<ParamFlowRule>>(
paramFlowRulePath,source-> JSON.parseObject(source,new TypeReference<List<ParamFlowRule>>(){})
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
//熱點引數規則:可寫資料來源
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<List<ParamFlowRule>>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
//系統規則:可讀資料來源
ReadableDataSource<String,List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<List<SystemRule>>(
systemRulePath,source-> JSON.parseObject(source,new TypeReference<List<SystemRule>>(){})
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
//系統規則:可寫資料來源
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<List<SystemRule>>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
//授權規則:可讀資料來源
ReadableDataSource<String,List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<List<AuthorityRule>>(
authorityRulePath,source-> JSON.parseObject(source,new TypeReference<List<AuthorityRule>>(){})
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
//授權規則:可寫資料來源
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<List<AuthorityRule>>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
private void mkdirIfNotExist(String filePath) throws IOException{
File file = new File(filePath);
if (!file.exists())
{
file.mkdirs();
}
}
private void createFileIfNotExist(String filePath) throws IOException{
File file = new File(filePath);
if (!file.exists())
{
file.createNewFile();
}
}
}
然後在專案的 resources/META-INF/services
目錄下建立檔案,名為 com.alibaba.csp.sentinel.init.InitFunc
,內容為:
# 改成上面FileDataSourceInit的包名類名全路徑即可。
#(copy reference)
com.nacos.consumer.nacosconsumer.sentinel.FileDataSourceInit
優點
- 簡單易懂
- 沒有多餘依賴(比如配置中心、快取等)
缺點
- 由於規則是用 FileRefreshableDataSource 定時更新的,所以規則更新會有延遲。如果FileRefreshableDataSource定時時間過大,可能長時間延遲;如果FileRefreshableDataSource過小,又會影響效能;
- 規則儲存在本地檔案,如果有一天需要遷移微服務,那麼需要把規則檔案一起遷移,否則規則會丟失。
推模式
我們提供了 ZooKeeper, Apollo, Nacos 等的動態資料來源實現。以 ZooKeeper 為例子,如果要使用第三方的配置中心作為配置管理,您需要做下面的幾件事情:
- 實現一個公共的 ZooKeeper 客戶端用於推送規則,在 Sentinel 控制檯配置項中需要指定 ZooKeeper 的地址,啟動時即建立 ZooKeeper Client。
- 我們需要針對每個應用(appName),每種規則設定不同的 path(可隨時修改);或者約定大於配置(如 path 的模式統一為
/sentinel_rules/{appName}/{ruleType}
,e.g.sentinel_rules/appA/flowRule
)。 - 規則配置頁需要進行相應的改造,直接針對應用維度進行規則配置;修改同個應用多個資源的規則時可以批量進行推送,也可以分別推送。Sentinel 控制檯將規則快取在記憶體中(如
InMemFlowRuleStore
),可以對其進行改造使其支援應用維度的規則快取(key 為 appName),每次新增/修改/刪除規則都先更新記憶體中的規則快取,然後需要推送的時候從規則快取中獲取全量規則,然後通過上面實現的 Client 將規則推送到 ZooKeeper 即可。 - 應用客戶端需要註冊對應的讀資料來源以監聽變更,可以參考 相關文件。
從 Sentinel 1.4.0 開始,Sentinel 控制檯提供 DynamicRulePublisher
和 DynamicRuleProvider
介面用於實現應用維度的規則推送和拉取,並提供了相關的示例。Sentinel 提供應用維度規則推送的示例頁面(/v2/flow
),使用者改造控制檯對接配置中心後可直接通過 v2 頁面推送規則至配置中心。改造詳情可參考 應用維度規則推送示例。
部署多個控制檯例項時,通常需要將規則存至 DB 中,規則變更後同步向配置中心推送規則。
實操:
在元件中有的話就可以不用新增
#基於Nacos配置中心進行規則持久化
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:80
spring.cloud.sentinel.datasource.ds1.nacos.data-id=${spring.application.name}.json
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
ds1為自定義名字,需要報紙配置資訊中的一致
Data id :nacos-discovery-consumer.json 專案名稱.json
Group:DEFAULT_GROUP
配置格式:JSON
配置內容:根據上圖的格式,自行配置。count = QPS 配置
流程:從Nacos讀取配置,快取到本地。Sentinel 從本地讀取配置完成持久化操作。