一、Sentinel 控制檯部署
# 啟動控制檯命令
java -jar sentinel-dashboard-1.8.1.jar
使用者可透過如下引數進行配置:
-Dsentinel.dashboard.auth.username=sentinel 用於指定控制檯的登入使用者名稱為 sentinel
-Dsentinel.dashboard.auth.password=123456 用於指定控制檯登入密碼為 123456,如果省略這兩個引數,預設使用者名稱和密碼都為 sentinel
-Dserver.servlet.session.timeout=7200 用於指定 Spring Boot 服務端 session 的過期時間,單位為秒,預設為 30 分鐘
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=hudu -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.1.jar
二、SpringBoot 整合 Sentinel
專案引入依賴
<!-- 整合控制檯 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.1</version>
</dependency>
專案設定 sentinel 控制檯的服務地址和埠
-Dcsp.sentinel.dashboard.server=192.168.33.62:8858
隨便請求一下專案中的介面
三、Spring Cloud Alibaba 整合 Sentinel
3.1、引入依賴
<!--sentinel 啟動器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
application.yml 配置
server:
port: 8070
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 192.168.33.62:8858
# 防止實時監控不顯示
clientIp: 192.168.33.27
3.2、流控規則
流量控制(flow-control):其原理是監控應用流量的 QPS 或併發執行緒數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時流量高峰擊垮,從而保障應用的高可用性
應用場景
應對洪峰流量:秒殺,大促,下單,訂單迴流處理
訊息型場景:削峰填谷,冷熱啟動
付費系統:根據使用流量付費
API Gateway:精準控制 API 流量
任何應用:探測應用中執行的慢程式塊,進行限制
3.2.1、QPS 流控
給當前資源設定流控 QPS 為 2
當介面請求請求頻繁,出現被流控情況
自定義流控處理
由於沒有持久化,服務重啟之後,需要重新設定流控規則
再次測試,當請求頻繁之後,返回的值為自定義流控處理後返回的值
3.2.2、併發執行緒數流控
併發數控制用於保護業務執行緒池不被慢呼叫耗盡。例如,當應用所依賴的下游應用由於某種原因導致服務不穩定、響應延遲增加,對於呼叫者來說,意味著吞吐量下降和更多的執行緒數佔用,極端情況下甚至導致執行緒池耗盡。為應對太多執行緒佔用的情況,業內有使用隔離的方案,比如透過不同業務邏輯使用不同執行緒池來隔離業務自身之間的資源爭搶(執行緒池隔離)。這種隔離方案雖然隔離性比較好,但是代價就是執行緒數目太多,執行緒上下文切換的overhead比較大,特別是對低延時的呼叫有比較大的影響。`Sentinel 併發控制不負責建立和管理執行緒池,而是簡單統計當前請求上下文的執行緒數目(正在執行的呼叫數目),如果超出閾值,新的請求會被立即拒絕,效果類似於訊號量隔離。併發數控制通常在呼叫端進行配置`。
介面程式碼如下
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return "正常訪問";
}
可以看到,當前一個請求還在處理中時,如果有下個請求進入,會直接處理為流控
三、BlockException 異常統一處理
springwebmvc 介面資源限流流入在 HandlerInterceptor 的實現類 AbstractSentinelInterceptor 的 preHandle 方法中,對異常的處理時 BlcokExceptionHandler 的實現累,sentinel 1.7.1 引入 sentinel-spring-webmvc-adapter.jar
自定義 BlockException 的實現類統一處理 BlockException
註釋掉@SentinelResource
註解
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
// getRule() 資源 規則的詳細資訊
log.info("BlockExceptionHandler BlockException =====" + e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100, "介面限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101, "服務降級了");
} else if (e instanceof ParamFlowException) {
r = Result.error(100, "熱點引數限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(100, "觸發系統保護規則了");
} else if (e instanceof AuthorityException) {
r = Result.error(100, "授權規則不透過");
}
// 返回 json
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(), r);
}
}
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result error(Integer code,String msg) {
return new Result(code,msg);
}
}
四、流控模式
4.1、直接
之前的例子都是使用的直接模式,資源達到設定的閾值之後直接被流控丟擲異常
4.2、關聯
當兩個資源之間具有資源爭搶或者依賴關係的時候,這兩個資源便具有了關聯。比如對資料庫同一個欄位的讀操作和寫操作存在爭搶,讀的速度過高會影響寫得速度,寫的速度過高會影響讀的速度。如果放任讀寫操作爭搶資源,則爭搶本身帶來的開銷會降低整體的吞吐量。可使用關聯限流來避免具有關聯關係的資源之間過度的爭搶,舉例來說,read_db
和write_db
這兩個資源分別代表資料庫讀寫,我們可以給 read_db
設定限流規則來達到寫優先的目的:設定strategy
為RuleConstant.STRATEGY_RELATE
同時設定refResource
為rite_db
。 這樣當寫庫操作過於頻繁時,讀資料的請求會被限流。
準備兩個介面,實現效果為,當生成訂單訪問量比較大時,對查詢訂單進行一個限流
@RequestMapping("/add")
public String add() {
System.out.println("下單成功!");
return "生成訂單";
}
@RequestMapping("/get")
public String get() {
return "查詢訂單";
}
使用 JMeter 進行輔助測試
然後啟動,JMeter會進行每秒三次的請求,我們請求查詢介面發現,查詢介面被限流了
4.3、鏈路
根據呼叫鏈路入口限制
下面中記錄了資源之間的呼叫鏈路,這些資源透過呼叫關係,相互之間構成一棵呼叫樹。這棵樹的根節點是一個名字為 getUser 的虛擬節點(方法),呼叫鏈的入口都是這個虛節點的子節點。一棵典型的呼叫樹如下圖所示:
實現效果為,當 test2 呼叫查過閾值,只有 test2 會被限流,test1 不受影響
專案結構如下
@Autowired
private IOrderService orderService;
@RequestMapping("/test1")
public String test1() {
return orderService.getUser();
}
@RequestMapping("/test2")
public String test2() {
return orderService.getUser();
}
@Service
public class OrderServiceImpl implements IOrderService {
@Override
@SentinelResource(value = "getUser")
public String getUser() {
return "查詢使用者";
}
}
預設情況下 sentinel 沒有給我們維護呼叫鏈路樹,預設將呼叫鏈路收斂,需要我們設定spring.cloud.sentinel.web-context-unify=false
可以發現現在生成的資源變了,隨便選擇一個進行鏈路流控規則設定。
一旦我們使用了@SentinelResource
註解,就不會應用統一異常處理
修改 service 程式碼
@Service
public class OrderServiceImpl implements IOrderService {
@Override
@SentinelResource(value = "getUser",blockHandler = "getUserBlockHandler")
public String getUser() {
return "查詢使用者";
}
public String getUserBlockHandler(BlockException e) {
return "流控使用者";
}
}
五、流控效果
5.1、快速失敗
之前的例子都是快速失敗,當請求的量超過設定的閾值之後,新的請求直接被立即拒絕,拒絕方式為丟擲FlowException
。這種方式使用於對系統處理能力確切已知的情況下,比如透過壓測確定了系統的準確水位時。
5.2、Warm Up(預熱流控)
主要針對激增流量處理
Warm Up (RuleConstant.CONTROL BEHAVIOR WARM_UP)方式,即預熱冷啟動方式。當系統長期處於低水位的情況下,當流量突然增加時,直接把系統拉昇到高水位可能瞬間把系統壓垮。透過”冷啟動”,
讓透過的流量緩慢增加,在一定時間內逐漸增加到閾值 上限,給冷系統一個預熱的時間, 避免冷系統被壓垮。
冷載入因子: codeFactor 預設是3,即請求 QPS 從 threshold/3 開始,經預熱時長逐漸升至設定的 QPS 閾值。
表示在五秒鐘之內,10 個請求慢慢遞增,根據上面所說的冷載入因子的演算法,預設開始會進入 3 個請求,然後再根據閾值除以 3,然後慢慢遞增,最終達到閾值設定的值。
可以實時監控就可以明顯看到,開始 QPS 超過 3 會進行限流,然後慢慢的 QPS 上限越來越高,直到達到閾值
5.3、排隊等待(勻速排隊)
主要針對脈衝流量處理
勻速排隊(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式會嚴格控制請求透過的間隔時間,也即是讓請求以均勻的速度透過,對應的是漏桶演算法。該方式的作用如下圖所示:
這種方式主要用於處理間隔性突發的流量,例如訊息佇列。想象一下這樣的場景,在某一秒有大量的請求到來, 而接下來的幾秒則處於空閒狀態,我們希望系統能夠在接下來的空閒期間逐漸處理這些請求,而不是在第一秒直接拒絕多餘的請求。
注意:勻速排隊模式暫時不支援 QPS > 100 的場景
先檢視直接失敗的效果
新增定時器,每次執行完暫停 5 秒
介面請求設定
可以看到當遇到脈衝流量,會觸發限流策略,然後中間會有大概五秒的停頓,排隊等待就時利用空閒的時間,對脈衝流量進行一個緩衝,提高伺服器的利用率
修改策略為排隊等待
當有脈衝流量時,對超出閾值的請求,在五秒內進行一個排隊處理。
再次請求,可以看到所有的請求抖執行成功了,並且中間還有空閒時段,有餘量可以接受更多的請求
再次調整為 20 個執行緒,現在可以看到空閒時間就被利用了起來
六、熔斷策略
除了流量控制以外,對呼叫鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。 我們需要對不穩定的弱依賴服務呼叫進行熔斷降級,暫時切斷不穩定呼叫,避免區域性不穩定因素導致整體的雪崩。熔斷降級作為保護自身的手段,通常在客戶端(呼叫端)進行配置。
熔斷降級規則(DegradeRule)包含下面幾個重要的屬性:
Field | 說明 | 預設值 |
---|---|---|
resource | 資源名,即規則的作用物件 | |
grade | 熔斷策略,支援滿呼叫比例/異常比例/異常數策略 | 慢呼叫比例 |
count | 慢呼叫比例模式下為慢呼叫臨界RT(超出該閾值為慢呼叫);異常比例/異常數模式下為對應的閾值 | |
timeWindow | 熔斷時長,單位為 s | |
minRequestAmount | 熔斷觸發的最小請求數,請求數小於該閾值時即使異常比例超出閾值也不會熔斷(1.7.0引入) | 5 |
statIntervalMs | 統計時長(單位 ms),如 60x1000 代表 60 分鐘 (1.8.0 引入) | 1000ms |
slowRatioThreshold | 慢呼叫比例閾值,僅慢呼叫比例模式有效(1.8.0引入) |
6.1、慢呼叫比例
慢呼叫比例(SLOW REQUEST RATIO):選擇以慢呼叫比例作為閾值,需要設定允許的慢呼叫RT (即最大的響應時間), 請求的響應時間大於該值則統計為慢呼叫。當單位統計時長(statIntervalMs)內請求數目大於設定的最小請求數目,並且慢呼叫的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求響應時間小於設定的慢呼叫RT則結束熔斷,若大於設定的慢呼叫RT則會再次被熔斷。
準備如下介面
@RequestMapping("/flowThread")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "正常訪問";
}
設定如下降級規則,當請求處理時間大於 1000 ms,請求就為慢呼叫,這裡的最小請求數代表和比例閾值表示,在 1000 ms 內,有 8 次請求,並且 50% 是慢請求,也就是 4 次慢呼叫,介面就會進行熔斷處理,經過 10 s 後恢復半開狀態,如果恢復後的第一次呼叫還是慢呼叫,直接熔斷。
使用 JMeter 進行測試,設定每秒請求 10 次
可以看到當我們再次請求時,服務被降級了。
6.2、異常比例
異常比例(ERROR_RATIO): 當單位統計時長(statIntervalMs)內請求數目大於設定的最小請求數目,並且異常的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長後熔斷器會
進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。異常比率的閾值範圍是[0.0, 1.0], 代表0%~100%。
準備介面
@RequestMapping("/err")
public String err() {
int i = 1 / 0;
return "hello";
}
當一秒內,前五個請求中,異常的介面超過 10 %,也就是隻要有一個介面異常,介面會熔斷
同樣用 JMeter 進行輔助測試,當再次請求介面,發現介面被熔斷了
6.3、異常數
一秒內,前五個請求只要有一個異常,就會熔斷
本作品採用《CC 協議》,轉載必須註明作者和本文連結