「微服務設計之禪」重試模式
前言
微服務本質(zhì)上分布式架構(gòu),當我們使用分布式系統(tǒng)時任何不可預知的問題都會發(fā)生(例如網(wǎng)絡可用性問題、服務可用性問題、中間件可用性問題)。一個系統(tǒng)的問題可能會直接影響另外一個系統(tǒng)的使用或性能。所以在系統(tǒng)設計過程既要保證自身運行的彈性需求,也要避免對下游服務級聯(lián)故障。
重試模式
在微服務技術(shù)架構(gòu)中,當有多個服務(A,B,C ,D)時,一個服務(A)可能依賴于另一服務(B),而另一服務(B)又可能依賴于 C,依此類推。有時由于某些問題,服務 D 可能無法按預期響應。服務 D 可能引發(fā)了某些異常,例如 OutOfMemory Error 或 Internal Server Error。此類異常會影響下游服務,這可能導致用戶體驗較差,如下所示。

類似于網(wǎng)絡通信異常等,大部分情況通過刷新再次請求服務接口即可解決。在微服務架構(gòu)中,生產(chǎn)環(huán)境針對服務 D 部署了多個實例,并通過負載均衡等實現(xiàn)服務 D 高可用。如果其中一個實例網(wǎng)絡請求有問題,無法響應期望結(jié)果,我們可以通過重試該請求,通過負載均衡器將請求發(fā)送至運行狀態(tài)良好的實例獲取正確結(jié)果。因此通過“重試”模式,可以使得應用可用性得到保證。

示例程序
架構(gòu)圖

如上圖所示,簡單模擬電商下單邏輯
用戶登錄瀏覽商品 (商品庫存模塊) 扣減商品庫存 (商品庫存模塊) 創(chuàng)建商品訂單 (訂單模塊)
product-service 通過調(diào)用 order-service 服務下單
代碼實現(xiàn)
├──?retry-demo
???├──?order-service????????#訂單服務??(8070)
???└──?product-service??????#商品庫存服務??(8050)
依賴說明。由于 hystrix 年久失修,這里使用 resilience4j 斷路保護器做演示
<dependency>
????<groupId>io.github.resilience4jgroupId>
????<artifactId>resilience4j-spring-boot2artifactId>
????<version>1.6.1version>
dependency>
針對接口定義重試策略
resilience4j.retry:
??instances:
????ratingService:
??????maxRetryAttempts:?1?#重試策略
??????retryExceptions:???#針對哪些異常進行重試
????????-?org.springframework.web.client.HttpServerErrorException
消費方接口。product-service 8050
/**
?*?用戶點擊購買
?*/
@SneakyThrows
@GetMapping("/order")
public?String?buy()?{
????//?模擬調(diào)用?訂單服務下單
????orderService.createOrder().get();
????return?"success";
}
定義遠程調(diào)用類使用 Retry 包裝
/**
?*?創(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("");
}
服務提供方。order-service 8070
@RestController
public?class?PayController?{
????private?static?int?num?=?0;
????@SneakyThrows
????@GetMapping("/createOrder")
????public?String?createOrder()?{
????????log.info("開始請求創(chuàng)建訂單接口?{}",?++num);
????????//?請求次數(shù)奇數(shù)模擬創(chuàng)建訂單異常
????????if?(num?%?2?!=?0)?{
????????????throw?new?RuntimeException();
????????}
????????return?"創(chuàng)建訂單服務";
????}
}
開始測試
請求商品服務,返回成功
curl?http://localhost:8050/order
success?
訂單服務日志,由于是第一次請求觸發(fā)異常,然后服務調(diào)用方自動重試產(chǎn)生第二次調(diào)用。
2020-12-06?15:50:07.664??INFO?25846?---?[nio-8070-exec-1]?c.e.o.controller.OrderController?????????:?開始請求創(chuàng)建訂單接口?1
2020-12-06?15:50:07.686?ERROR?25846?---?[nio-8070-exec-1]?o.a.c.c.C.[.[.[/].[dispatcherServlet]????:?Servlet.service()?for?servlet?[dispatcherServlet]?in?context?with?path?[]?threw?exception?[Request?processing?failed;?nested?exception?is?java.lang.RuntimeException]?with?root?cause
...?異常日志?...
2020-12-06?15:50:08.271??INFO?25846?---?[nio-8070-exec-2]?c.e.o.controller.OrderController?????????:?開始請求創(chuàng)建訂單接口?2
總結(jié)
重試模式可以通過編碼的形式自動發(fā)起重試,避免終端用戶手動刷新體驗問題。但重試模式的缺點是會造成整體的響應時間,部分業(yè)務邏輯不能適用(比如冪等接口[1]等)
源碼:https://github.com/lltx/microservices-pattern[2]
參考資料和部分圖片來源 https://www.vinsguru.com[3]
參考資料
冪等接口: https://juejin.cn/post/6892966720017793037
[2]源碼:https://github.com/lltx/microservices-pattern: https://github.com/lltx/microservices-pattern
[3]參考資料和部分圖片來源 https://www.vinsguru.com: https://www.vinsguru.com
