一. Sentinel: 分佈式系統的流量防衛兵 - 阿里巴巴產品
具體介紹可以看官方文檔:https://github.com/alibaba/Sentinel/wiki/介紹,下面我們說點官方沒有的東西:
服務保護的基本概念:
【服務限流/熔斷】
服務限流目的是爲了更好的保護我們的服務,在高併發的情況下,如果客戶端請求的數量達到一定極限(後臺可以配置閾值),請求的數量超出了設置的閾值,開啓自我的保護,直接調用我們的服務降級的方法,不會執行業務邏輯操作,直接走本地falback的方法,返回一個友好的提示。
【服務降級】
在高併發的情況下, 防止用戶一直等待,採用限流/熔斷方法,使用服務降級的方式返回一個友好的提示給客戶端,不會執行業務邏輯請求,直接走本地的falback的方法。提示語:當前排隊人數過多,稍後重試~
【服務雪崩】
默認的情況下,Tomcat或者是Jetty服務器只有一個線程池去處理客戶端的請求,這樣的話就是在高併發的情況下,如果客戶端所有的請求都堆積到同一個服務接口上, 那麼就會產生tomcat服務器所有的線程都在處理該接口,可能會導致其他的接口無法訪問。
【服務隔離機制】
線程池隔離機制:每個服務接口都有自己獨立的線程池,互不影響,缺點就是佔用cpu資源非常大。
信號量隔離機制:最多隻有一定的閾值線程數處理我們的請求,超過該閾值會拒絕請求。
Sentinel | Hystrix | |
---|---|---|
隔離策略 | 基於併發數 | 線程池隔離/信號量隔離 |
熔斷降級策略 | 基於響應時間或失敗比率 | 基於失敗比率 |
實時指標實現 | 滑動窗口 | 滑動窗口(基於 RxJava) |
規則配置 | 支持多種數據源 | 支持多種數據源 |
擴展性 | 多個擴展點 | 插件的形式 |
基於註解的支持 | 即將發佈 | 支持 |
調用鏈路信息 | 支持同步調用 | 不支持 |
限流 | 基於 QPS / 併發數,支持基於調用關係的限流 | 不支持 |
流量整形 | 支持慢啓動、勻速器模式 | 不支持 |
系統負載保護 | 支持 | 不支持 |
實時監控 API | 各式各樣 | 較爲簡單 |
控制檯 | 開箱即用,可配置規則、查看秒級監控、機器發現等 | 不完善 |
常見框架的適配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
二. SpringBoot整合Sentinel
1. 手動代碼配置
@RestController
public class OrderService {
private static final String GETORDER_KEY = "getOrder";
// 手動配置管理Api限流接口
@RequestMapping("/initFlowQpsRule")
public String initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以內
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
return "....限流配置初始化成功..";
}
@RequestMapping("/getOrder")
public String getOrders() {
Entry entry = null;
try {
entry = SphU.entry(GETORDER_KEY);
// 執行我們服務需要保護的業務邏輯(省略)
return "getOrder接口";
} catch (Exception e) {
e.printStackTrace();
return "流量已達上限.";
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
啓動項目,首先執行initFlowQpsRule接口;由於我們配置的是QPS(QueriesPerSecond意思是“每秒查詢率”,是一臺服務器每秒能夠相應的查詢次數)爲1,所以一秒內超過一次請求,則走降級,即會執行catch的返回結果。(下圖演示效果爲一秒內刷新兩次):
那麼問題來了,每次重啓都需要執行initFlowQpsRule接口,太繁瑣了,那麼我們可以加入到啓動加載類中:
@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
private static final String GETORDER_KEY = "getOrder";
@Override
public void run(ApplicationArguments args) throws Exception {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以內
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
log.info(">>>限流服務接口配置加載成功>>>");
}
}
2. 註解形式配置管理Api限流
該方式也必須先執行initFlowQpsRule或者加入啓動配置類,只是接口中不用Entry和try catch了:
@RestController
public class OrderService {
@RequestMapping("/initFlowQpsRule")
public String initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource("getOrderAnnotation");
// QPS控制在2以內
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
return "限流配置初始化成功..";
}
@SentinelResource(value = "getOrderAnnotation", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderAnnotation")
public String getOrderAnnotation() {
return "getOrderAnnotation接口";
}
/**
* 被限流後返回的提示
*/
public String getOrderQpsException(BlockException e) {
e.printStackTrace();
return "該接口已經被限流啦!";
}
}
該效果和方式1效果一樣,一秒內連續請求兩次getOrderAnnotation接口,則會走限流回調方法getOrderQpsException。
3. Sentinel控制檯管理限流接口
① 控制檯環境搭建
下載對應Sentinel-Dashboardhttps://github.com/alibaba/Sentinel/releases/tag/1.7.1 下載後執行命令:
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\MyTools\sentinel-dashboard-1.6.2.jar
啓動後訪問:http://localhost:8718,默認用戶名和密碼都爲sentinel,登錄成功頁面如下:
② SpringBoot接入Sentinel控制檯
spring:
application:
# 服務名稱
name: service-order
cloud:
nacos:
discovery:
# nacos註冊地址
server-addr: 127.0.0.1:8848
config:
# 配置中心連接地址
server-addr: 127.0.0.1:8848
# 分組
group: DEFAULT_GROUP
# 類型
file-extension: yaml
sentinel:
transport:
dashboard: 127.0.0.1:8718
eager: true
服務加入身sentinel配置後,重啓可以看到sentinel控制檯多出了一個應用:
新寫一個測試接口:
@RestController
public class OrderService {
@SentinelResource(value = "getOrderByConsole", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderByConsole")
public String getOrderByConsole() {
return "getOrderByConsole接口";
}
public String getOrderQpsException(BlockException e) {
e.printStackTrace();
return "該接口已經被限流啦!";
}
}
控制檯需要配置資源名稱getOrderByConsole:點擊流控規則 → 新增流控規則:
三. Sentinel持久化
默認的情況下Sentinel的規則是存放在內存中,如果Sentinel客戶端重啓後,Sentinel數據規則可能會丟失。
Sentinel持久化機制支持四種持久化的機制:(這裏我們只講整合Nacos)
1. 本地文件
2. 攜程阿波羅
3. Nacos
4. Zookeeper
1. 首先,在Nacos配置中心創建流控規則:(注意resource不要加/)
resource:資源名,即限流規則的作用對象
limitApp:流控針對的調用來源,若爲 default 則不區分調用來源
grade:限流閾值類型(QPS 或併發線程數);0代表根據併發數量來限流,1代表根據QPS來進行流量控制
count:限流閾值
strategy:調用關係限流策略
controlBehavior:流量控制效果(直接拒絕、Warm Up、勻速排隊)
clusterMode:是否爲集羣模式
2. 修改訂單服務sentinel配置如下:
引入依賴並新增測試接口:
<!--sentinel 整合nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.5.2</version>
</dependency>
@SentinelResource(value = "getOrderSentinel", blockHandler = "getOrderSentinelBlockHandler")
@RequestMapping("/getOrderSentinel")
public String getOrderSentinel() {
return "getOrderSentinel";
}
public String getOrderSentinelBlockHandler(BlockException e) {
return "當前訪問人數過多,請稍後重試!";
}
啓動訂單服務,此時,可以看到Sentinel控制檯已經出現1中在Nacos配置的流控規則:
訪問http://localhost:8090/getOrderSentinel,一秒連續兩次請求,則也會走blockHandler方法。
【小結】:注意一定要刪掉 SentinelApplicationRunner類,因爲SpringCloudAlibaba默認Sentinel整合Nacos持久化已經在服務啓動的時候,加載Nacos的配置進服務內存,所以應該註釋掉啓動加載類,否則會產生衝突。
四. Gateway整合Sentinel實現服務限流
上面我們都是在單個服務(訂單服務)演示流控規則,下面我們說一下在微服務網關Gateway中如何整合Sentiel。
官方文檔:https://github.com/alibaba/Sentinel/wiki/網關限流
1. 首先在網關服務中額外引入以下依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.6.0</version>
</dependency>
2. 編寫固定配置類(直接拷貝如下代碼即可,無需修改):
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
3. 在網關配置啓動加載類,配置限流規則:
@Slf4j
@Component
public class SentinelApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
initGatewayRules();
}
// 配置限流規則
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("order")
// 限流閾值
.setCount(1)
// 統計時間窗口,單位是秒,默認是 1 秒
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
}
注意new GatewayFlowRule("order") 裏面的order即爲配置文件中routes中的-id,而不是path。
啓動網關,端口爲81,訪問返回如下:
可見返回的提示不太直觀,我們可以更改返回錯誤碼:
① 新建Handler實現WebExceptionHandler
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
}
② GatewayConfiguration註釋默認的SentinelGatewayBlockExceptionHandler,把自定義的注入到Spring容器中:
// @Bean
// @Order(Ordered.HIGHEST_PRECEDENCE)
// public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// // Register the block exception handler for Spring Cloud Gateway.
// return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
// }
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
重啓項目,一秒連續兩次訪問,返回結果如下:(getOrderSentinel有自己的降級方法,網關優先級更高,先被執行)
五. Sentinel熔斷降級
官方文檔:https://github.com/alibaba/Sentinel/wiki/熔斷降級
服務降級的策略:
1. rt(平均響應時間)【注意是單位時間內,並不是累加】
如果在1s內訪問五次,平均的響應時間超出了我們在平臺設置的閾值的情況下,直接觸發我們的熔斷執行我們服降級的方法。
在規定的時間窗口內(單位爲秒)一直執行我們的服務降級的方法,不能夠執行我們的真實業務邏輯。
// 平均響應時間RT
@SentinelResource(value = "getOrderRt", fallback = "getOrderRtFallback")
@RequestMapping("/getOrderRt")
public String getOrderRt() {
try {
Thread.sleep(300);
} catch (Exception e) {
}
return "正常執行我們業務邏輯";
}
public String getOrderRtFallback() {
return "Rt - 服務降級";
}
一秒連續訪問5次接口,則會走降級方法,因爲平均響應時間肯定大於10,下次訪問則走降級方法,3秒後解除。
注意:① 代碼中Thread.sleep(300),如果直接return如上圖IDEA代碼,則平均響應時間肯定不會大於10,則一秒內訪問10次甚至更多,也不會走降級方法。
② 一旦服務重啓後,降級規則失效,需要重新配置,同理也可以用Nacos持久化,自行學習,這裏不討論了~
2. 異常比例
比如客戶端每秒s內發出5個請求,5個請求全部錯誤,這說明錯誤率爲百分百。
每秒內發出5個請求,如果請求的異常佔比超過我們設置的閾值佔比的情況下,就會出發我們熔斷,執行我們的服務降級方法,在規定的時間窗口內(單位爲秒)不能執行我們真實業務邏輯。
@SentinelResource(value = "getOrderException", fallback = "getOrderExceptionFallback")
@RequestMapping("/getOrderException")
public String getOrderException(int age) {
int j = 1 / age;
return "正常執行我們業務邏輯:j" + j;
}
public String getOrderExceptionFallback(int age) {
return "錯誤率太高,展示無法訪問該接口";
}
每秒請求 >= 5次,異常比例爲100%,則走降級,同樣持續3秒。
注意:如果代碼中手動catch,則出異常直接進catch返回了,不會走降級方法,永遠不會走~
3. 異常數
當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。注意由於統計時間窗口是分鐘級別的(單位爲分鐘),若 timeWindow 小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態。
該方式也用getOrderException測試(同一個資源名稱可以有多個降級方式),會發現近一分鐘連續出現5次異常,第6次訪問則會走降級方法,注意時間窗口單位爲分鐘,所以解封時間爲1分鐘。注意該方式手動catch,則也不會走降級方法。
【總結】:個人認爲,異常比例和異常數沒多大用處,Rt平均響應時間還是比較重要的;順便說一下fallback與blockHandler的區別:fallback是服務熔斷或者業務邏輯出現異常執行的方法(1.6版本以上),blockHandler 限流出現錯誤執行的方法。
六. Sentinel熱點參數限流
官方文檔:https://github.com/alibaba/Sentinel/wiki/熱點參數限流
何爲熱點?熱點即經常訪問的數據。很多時候我們希望統計某個熱點數據中訪問頻次最高的 Top K 數據,並對其訪問進行限制。比如:
- 商品 ID 爲參數,統計一段時間內最常購買的商品 ID 並進行限制
- 用戶 ID 爲參數,針對一段時間內頻繁訪問的用戶 ID 進行限制
熱點參數限流會統計傳入參數中的熱點參數,並根據配置的限流閾值與模式,對包含熱點參數的資源調用進行限流。熱點參數限流可以看做是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。
Sentinel 利用 LRU 策略統計最近最常訪問的熱點參數,結合令牌桶算法來進行參數級別的流控。熱點參數限流支持集羣模式。
1. 基本使用:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
@RestController
@Slf4j
public class SeckillService {
// 秒殺的限流規則
private static final String SEKILL_RULE = "seckill";
public SeckillService() {
// Spring容器初始化時把熱點參數配置加載到jvm內存
initSeckillRule();
}
private void initSeckillRule() {
ParamFlowRule rule = new ParamFlowRule(SEKILL_RULE)
// 對我們秒殺接口第0個參數實現限流
.setParamIdx(0)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
// 每秒QPS最多訪問1次,如一秒2次即開始限流
.setCount(1);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
log.info(">>>秒殺接口限流策略配置成功<<<");
}
// 秒殺接口
@RequestMapping("/seckill")
public String seckill(Long userId, Long orderId) {
try {
Entry entry = SphU.entry(SEKILL_RULE, EntryType.IN, 1, userId);
return "秒殺成功";
} catch (Exception e) {
return "當前該用戶訪問頻率過多,請稍後重試!";
}
}
}
啓動服務,訪問接口(帶userId),一秒第二次訪問則會走catch:
注意:如果不傳熱點參數userId,則永遠都不會走限流,如http://localhost:8090/seckill?order=123123
2. 控制檯自定義:(推薦)
@RestController
@Slf4j
public class SeckillService {
// 秒殺的限流規則
private static final String SEKILL_RULE = "seckill";
@RequestMapping("/seckill")
@SentinelResource(value = SEKILL_RULE)
public String seckill(Long userId, Long orderId) {
return "秒殺成功";
}
}
@RestControllerAdvice
public class InterfaceExceptionHandler {
@ResponseBody
@ExceptionHandler(ParamFlowException.class)
public String businessInterfaceException(ParamFlowException e) {
return "您當前訪問的頻率過高,請稍後重試!";
}
}
重啓服務,控制檯配完熱點規則,效果和上面一樣。
注意:① 窗口時間只能設爲1秒,可能是版本bug (sentinel-dashboard-1.6.2.jar)。
② 熱點參數限流後,不會走fallback和blockHandler,只能全局捕獲異常來自定義返回信息。
③ Sentinel支持熱詞參數Vip通道,即可以指定某些參數的規則,這個比如在某些秒殺活動中可以作弊使用,當然僅做了解,還是不推薦這樣做的 ! 方式:編輯熱點規則 - 高級選項,如下圖,指定第一個參數爲136,qps閾值爲10,即一秒最多可以有10次請求,添加後則可以在瀏覽器無限刷新,手速快的除外 ~
【本篇總結】
Sentinel可以說是分佈式流量防禦的神器,在SpringCloudAlibba中可以完全替代SpringCloud的Hystrix,易於維護,可持久化,官方文檔詳細,最後再貼出一遍官網文檔:https://github.com/alibaba/Sentinel/wiki/介紹,有不懂的歡迎留言 ~