「微服務(wù)設(shè)計之禪」超時模式
前言
微服務(wù)本質(zhì)上分布式架構(gòu),當(dāng)我們使用分布式系統(tǒng)時任何不可預(yù)知的問題都會發(fā)生(例如網(wǎng)絡(luò)可用性問題、服務(wù)可用性問題、中間件可用性問題)。一個系統(tǒng)的問題可能會直接影響另外一個系統(tǒng)的使用或性能。所以在系統(tǒng)設(shè)計過程既要保證自身運行的彈性需求,也要避免對下游服務(wù)級聯(lián)故障。
超時模式
如下圖在微服務(wù)架構(gòu)中,當(dāng)存在多個服務(wù)(A,B,C,D),服務(wù) A 依賴于服務(wù) B,而服務(wù) B 依賴于服務(wù) C,依次類推。由于網(wǎng)絡(luò)可用性問題,導(dǎo)致終點服務(wù)(服務(wù) D)不能即使返回,最終導(dǎo)致服務(wù) A 作為調(diào)用方 線程一直處于阻塞狀態(tài)。

所以為了避免上圖所述的線程阻塞的問題,我們建議在依賴服務(wù)之間通過設(shè)置調(diào)用超時來避免服務(wù)緩慢或者不可用的問題。
| 超時設(shè)置的好處 |
|---|
| 即使被調(diào)用方服務(wù)不可用,也能保證消費方服務(wù)始終正在運行 |
| 避免消費方服務(wù)無限期等待 |
| 避免阻塞當(dāng)前線程 |
示例程序
架構(gòu)說明

如上圖所示,簡單模擬電商支付下單邏輯
用戶登錄瀏覽商品 (商品庫存模塊) 扣減商品庫存 (商品庫存模塊) 創(chuàng)建商品訂單 (訂單模塊) 調(diào)用支付模塊支付 (支付模塊)
代碼實現(xiàn)
├──?timeout-demo
???├──?order-service????????#訂單服務(wù)??(8070)
???├──?pay-service??????????#支付服務(wù)??(8060)
???└──?product-service??????#商品庫存服務(wù)??(8050)
依賴說明。由于 hystrix 年久失修,這里使用 resilience4j 斷路保護器做演示
<dependency>
????<groupId>io.github.resilience4jgroupId>
????<artifactId>resilience4j-spring-boot2artifactId>
????<version>1.6.1version>
dependency>
消費方定義超時策略
#?超時參數(shù)配置
resilience4j:
??timelimiter:
????instances:
??????createOrder:?#?接口名稱
????????timeoutDuration:?5s?#超時時間
??????pay:?#?接口名稱
????????timeoutDuration:?3s?#超時時間
消費方接口。product-service 8050
@GetMapping("/buy")
public?String?buy()?{
??log.info("-->?開始調(diào)用?");
??//?模擬調(diào)用?訂單服務(wù)下單
??orderService.createOrder()
??????????//?模擬調(diào)用?支付服務(wù)支付
??????????.thenApply(orderNo?->?payService.pay()).get()
??????????.get();
??log.info("-->?結(jié)束調(diào)用?");
??return?"success";
}
/**
?*?創(chuàng)建訂單
?*?name:?指定接口超時配置名稱
?*?fallbackMethod:?超時后降級方法
?*/
@TimeLimiter(name?=?"createOrder",?fallbackMethod?=?"getError")
public?CompletableFuture?createOrder()? {
????return?CompletableFuture.supplyAsync(()?->?restTemplate.getForEntity("http://localhost:8070/createOrder"
????????????,?String.class).getBody());
}
/**
?*?支付
?*/
@TimeLimiter(name?=?"pay",?fallbackMethod?=?"getError")
public?CompletableFuture?pay()? {
????return?CompletableFuture.supplyAsync(()?->?restTemplate.getForEntity("http://localhost:8060/pay"
????????????,?String.class).getBody());
}
/**
?*?超時后執(zhí)行降級方法
?*/
public?CompletableFuture?getError(Throwable?error)? {
????log.warn("失敗?{}",?error.getMessage());
????return?CompletableFuture.completedFuture("");
}
服務(wù)提供方。order-service 8070 /pay-service 8060
@RestController
public?class?PayController?{
????@SneakyThrows
????@GetMapping("/pay")
????public?String?pay(){
????????//?模擬調(diào)用支付渠道耗時?10s
????????Thread.sleep(10000);
????????return?"支付成功";
????}
}
@RestController
public?class?OrderController?{
????@SneakyThrows
????@GetMapping("/createOrder")
????public?String?createOrder(){
????????//?模擬創(chuàng)建訂單耗時
????????Thread.sleep(10000);
????????return?"創(chuàng)建訂單服務(wù)";
????}
}
使用示例
通過以上代碼,我們實現(xiàn)了一個簡單的下單邏輯,通過調(diào)用商品服務(wù)接口,會自動調(diào)用支付、訂單服務(wù)接口。訂單、支付服務(wù)提供方 處理的耗時分別為 10S ,但服務(wù)消費方(商品服務(wù)) 針對 createOrder、pay 接口的最大超時時間為 5S、3S。所以當(dāng)用戶調(diào)用商品服務(wù)的 BUY 會在 8S 內(nèi)獲得返回。
測試如下
curl?http://localhost:8050/buy
日志輸出如下:8 秒超時按照降級方法返回。
2020-12-05?14:09:34.605??ProductController???????:?-->?開始調(diào)用
2020-12-05?14:09:39.626??OrderService???:?創(chuàng)建訂單失敗了?TimeLimiter?'createOrder'?recorded?a?timeout?exception.
2020-12-05?14:09:42.644??PayService???:?支付訂單失敗?TimeLimiter?'pay'?recorded?a?timeout?exception.
2020-12-05?14:09:42.645ProductController???????:?-->?結(jié)束調(diào)用
總結(jié)
通過引入 resilience4j 包裝接口,實現(xiàn)對指定調(diào)用超時設(shè)置,保證線程不會被阻塞
核心服務(wù)不會因為下游服務(wù)超時而被影響造成性能問題
保證應(yīng)用返回時間在固定時間窗內(nèi)

問題分析:
下游服務(wù)不可用的狀態(tài)下,線程仍需要阻塞,當(dāng)并發(fā)請求很多時,也會造成性能瓶頸

如上問題可以通過隔離模式來實現(xiàn)影響最小化,下篇再來講解。
源碼:https://github.com/lltx/microservices-pattern[1]
參考資料和部分圖片來源 https://www.vinsguru.com[2]
參考資料
源碼:: https://github.com/lltx/microservices-pattern
[2]參考資料和部分圖片來源: https://www.vinsguru.com
往期推薦
