「微服務(wù)設(shè)計之禪」隔離模式
前言
微服務(wù)本質(zhì)上分布式架構(gòu),當(dāng)我們使用分布式系統(tǒng)時任何不可預(yù)知的問題都會發(fā)生(例如網(wǎng)絡(luò)可用性問題、服務(wù)可用性問題、中間件可用性問題)。一個系統(tǒng)的問題可能會直接影響另外一個系統(tǒng)的使用或性能。所以在系統(tǒng)設(shè)計過程既要保證自身運(yùn)行的彈性需求,也要避免對下游服務(wù)級聯(lián)故障。
隔離模式

如上圖,如果我們仔細(xì)觀察船的結(jié)構(gòu)時會發(fā)現(xiàn),使用了多個隔板將船分為了多個小的隔間。隔板用于密封船體的各個部分,避免由于某個部位泄漏導(dǎo)致整個船淹沒。當(dāng)我們進(jìn)行軟件設(shè)計時,應(yīng)該將整個應(yīng)用拆分為多個組件,并且一個組件的故障不會影響到全局,這就所謂的隔離模式。
例如:假設(shè)有兩個服務(wù) A 和 B,服務(wù) A 只能同時處理 5 個并發(fā)請求。服務(wù) A 里面有兩個接口
/a/b依賴于服務(wù) B (耗時比較多)/a???? ?本地服務(wù)

當(dāng)有 10 個并發(fā)請求 A 服務(wù)接口, 其中 5 個請求 /a/b接口 ,5 個請求 /a由于/a/b 需要調(diào)用服務(wù) B 耗時比較多,可能會造成 5 個線程阻塞應(yīng)用無法處理 /a 雖然只是本地處理。由于一個接口耗時占用了整個應(yīng)用的資源導(dǎo)致其他接口無法使用,導(dǎo)致用戶體驗很差。

通過隔離模式分配特定接口的資源限制,避免資源瓶頸。如上圖,將a/b 接口最大處理線程設(shè)置為 2,這樣應(yīng)用始終會有剩余線程處理其他接口請求。
示例程序
架構(gòu)圖

如上圖所示,簡單模擬電商下單邏輯
- 用戶登錄瀏覽商品 (商品庫存模塊)
- 扣減商品庫存 (商品庫存模塊)
- 創(chuàng)建商品訂單 (訂單模塊)
product-service 提供兩個接口
- /products 查詢?nèi)可唐氛故?/li>
- /order 調(diào)用 order 服務(wù)下單
代碼實現(xiàn)
├──?bulkhead-demo
???├──?order-service????????#訂單服務(wù)??(8070)
???└──?product-service??????#商品庫存服務(wù)??(8050)
- 依賴說明。由于 hystrix 年久失修,這里使用 resilience4j 斷路保護(hù)器做演示
<dependency>
????<groupId>io.github.resilience4jgroupId>
????<artifactId>resilience4j-spring-boot2artifactId>
????<version>1.6.1version>
dependency>
- 消費(fèi)方定義隔離策略
server:
??tomcat:
????threads:
??????max:?5?#限制應(yīng)用只能處理5?個并發(fā),方便測試
resilience4j.bulkhead:
??instances:
????createOrder:??#?限制此接口只能使用兩個
??????maxConcurrentCalls:?2
- 消費(fèi)方接口。product-service 8050
??/**
???*?用戶點(diǎn)擊購買
???*/
??@SneakyThrows
??@GetMapping("/order")
??public?String?buy()?{
??????//?模擬調(diào)用?訂單服務(wù)下單
??????orderService.createOrder().get();
??????return?"success";
??}
??/***
???*?獲取全部商品接口
???*/
??@SneakyThrows
??@GetMapping("/products")
??public?String?products()?{
??????//?模擬查詢DB?耗時
??????Thread.sleep(100);
??????return?"products";
??}
- 定義遠(yuǎn)程調(diào)用類使用 Bulkhead 包裝
/**
?*?創(chuàng)建訂單
?*?name:?指定接口隔離配置名稱
?*?fallbackMethod:?超出隔離限制降級方法
?*/
@Bulkhead(name?=?"createOrder",?fallbackMethod?=?"getError")
public?CompletableFuture?createOrder()?{
????return?CompletableFuture.supplyAsync(()?->?restTemplate.getForEntity("http://localhost:8070/createOrder"
????????????,?String.class).getBody());
}
public?CompletableFuture?getError(Throwable?error)?{
????log.warn("創(chuàng)建訂單失敗了?{}",?error.getMessage());
????return?CompletableFuture.completedFuture("");
}
- 服務(wù)提供方。order-service 8070
@RestController
public?class?PayController?{
????@SneakyThrows
????@GetMapping("/pay")
????public?String?pay(){
????????//?模擬調(diào)用支付渠道耗時?10s
????????Thread.sleep(10000);
????????return?"支付成功";
????}
}
測試
- Jmeter 10 個線程請求 /order 接口, 10 個線程請求 /products ,30S 請求服務(wù)性能如下
測試使用資源隔離
資源隔離下,30S 共計執(zhí)行樣本 1750 次,通過率 43.5/每秒
1607306553測試不使用資源隔離(去掉@Bulkhead 邏輯)
由于未做資源隔離,30S 共計執(zhí)行樣本 50 次,通過率 49.8/每分鐘

總結(jié)
在應(yīng)用資源緊張的前提下,通過設(shè)置資源隔離策略能大大提升整個應(yīng)用的性能。所謂資源緊張即請求并發(fā)大于容器的處理前最大值,如上邊壓測 tomcat 限制只能處理 5 個并發(fā),但是我們測試是 20 個并發(fā)。
源碼:https://github.com/lltx/microservices-pattern
參考資料和部分圖片來源 https://www.vinsguru.com
往期推薦
「2020封箱」Spring Boot 2.4.1 發(fā)布
Spring Boot 2.4.0 正式發(fā)布,全面擁抱云原生
