自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Retry & Fallback 是利器還是魔咒?

開發(fā) 前端
Retry 就是在調(diào)用遠(yuǎn)程接口失敗時,Client 主動發(fā)起重試請求,以期待獲得最終結(jié)果,從而完成整個流程。

1. 概覽

在分布式場景中,Retry 和 Fallback 是最常見的容災(zāi)方案。

  • Retry 就是在調(diào)用遠(yuǎn)程接口失敗時,Client 主動發(fā)起重試請求,以期待獲得最終結(jié)果,從而完成整個流程
  • Fallback 是在調(diào)用遠(yuǎn)程接口失敗時,Client 不進(jìn)行重試而是調(diào)用一個特殊的 fallback 方法,從這個方法中獲取結(jié)果,使流程能夠繼續(xù)下去

那 Retry 和 Fallback 該怎么抉擇呢?

1.1. 背景

首先,先看下 Retry 和 Fallback 都是怎么幫助流程進(jìn)行自我恢復(fù)的。

1.1.1. Retry

現(xiàn)在有一個生產(chǎn)流程:

核心流程如下:

  • 從商品服務(wù)中獲取商品信息
  • 根據(jù)商品信息創(chuàng)建訂單
  • 將訂單保存到數(shù)據(jù)庫

如果發(fā)生網(wǎng)絡(luò)抖動,將導(dǎo)致生產(chǎn)失敗。

  • 在調(diào)用商品服務(wù)獲取商品時,由于網(wǎng)絡(luò)異常,接口調(diào)用失敗
  • 由于無法獲取商品信息,生產(chǎn)流程被異常中斷

由于上產(chǎn)流程太過重要,系統(tǒng)需盡最大努力保障用戶能夠完成下單操作,那針對網(wǎng)絡(luò)抖動這個問題,可以通過 Retry 進(jìn)行修復(fù)。

image

  • 在第一次獲取商品信息時,由于網(wǎng)絡(luò)問題導(dǎo)致獲取失敗
  • 系統(tǒng)不會直接拋出異常,而是在等待一段時間后,重新發(fā)起第二次請求,也就是 Retry 操作
  • 網(wǎng)絡(luò)恢復(fù),第二次請求成功獲取商品信息
  • 流程繼續(xù)運(yùn)行,最終完成用戶生產(chǎn)

Retry 機(jī)制非常適合服務(wù)短時間不可用,或某個服務(wù)節(jié)點(diǎn)異常 這類場景。

1.1.2. Fallback

一個生產(chǎn)驗(yàn)證接口,主流程如下:

  • 調(diào)用商品服務(wù)的接口獲取商品信息
  • 根據(jù)商品和用戶信息判斷用戶是否能夠購買該商品

同樣,假設(shè)在訪問商品服務(wù)時出現(xiàn)網(wǎng)絡(luò)異常:

由于無法獲取商品信息,從而導(dǎo)致整個驗(yàn)證流程被異常中斷,用戶操作被迫終止。

聰明的你估計會說那就使用 Retry 呀,是的:

如果是短時不可用,通過 Retry 機(jī)制便可以恢復(fù)流程。

但,如果是商品服務(wù)壓力過大,響應(yīng)時間過長呢?比如,商品服務(wù)流量激增,導(dǎo)致 DB CPU 飆升,出現(xiàn)大量的慢 SQL,這時觸發(fā)了系統(tǒng)的 Retry 會是怎樣?

image

  • 在獲取商品失敗后,系統(tǒng)自動觸發(fā) Retry 機(jī)制
  • 由于是商品服務(wù)本身出了問題,第二次請求仍舊失敗
  • 服務(wù)又觸發(fā)了第三次請求,仍未獲取結(jié)果
  • 達(dá)到最大重試次數(shù),仍舊無法獲取商品,只能通過異常中斷用戶請求

通過 Retry 機(jī)制未能將流程從異常中恢復(fù)過來,也給下游的 商品服務(wù) 造成了巨大傷害。

  • 商品服務(wù)壓力大,響應(yīng)時間長
  • 上游系統(tǒng)由于超時觸發(fā)自動重試
  • 自動重試增大了對商品服務(wù)的調(diào)用
  • 商品服務(wù)請求量更大,更難以從故障中恢復(fù)

這就是常說的“讀放大”,假設(shè)用戶驗(yàn)證是否能夠購買請求的請求量為 n,那極端情況下 商品服務(wù)的請求量為 3n (其中 2n 是由 Retry 機(jī)制造成)

此時,Retry 就不是一個好的方案。我們先退回業(yè)務(wù)場景進(jìn)行思考,如果無法獲取商品,驗(yàn)證接口是否可以直接放行,先讓用戶完成購買?

如果,這個業(yè)務(wù)假設(shè)能夠接受的話,那就到了 Fallback 上場的時候了。

  • 調(diào)用商品服務(wù)獲取商品信息失敗
  • 系統(tǒng)不會進(jìn)行重試,而是觸發(fā) fallback 機(jī)制
  • fallback 會調(diào)用指定的一個方法,并將返回值作為遠(yuǎn)程接口的返回值
  • 接下來的流程使用 fallback 方法的返回值完成業(yè)務(wù)邏輯

1.1.3. 場景思考

同樣是對商品服務(wù)接口(同一個接口)的調(diào)用,在不同的場景需要使用不同的策略用以恢復(fù)業(yè)務(wù)流程,通常情況下:

  1. Command 場景優(yōu)先使用 Retry
  2. 這種流量極為重要,最好能保障流程的完整性
  3. 通常寫流量比較小,小范圍 Retry 不會對下游系統(tǒng)造成巨大影響
  4. Query 場景優(yōu)選使用 Fallabck
  5. 大多數(shù)展示場景,哪怕部分信息沒有獲取到對整體的影響也比較小
  6. 通常讀場景流量較高,Retry 對下游系統(tǒng)的傷害不容忽視

那面對一個遠(yuǎn)程接口被多個場景使用,我們該怎么處理呢?

  1. 提供兩組接口,一個具有 Retry 能力,一個具有 Fallback 能力,由使用方根據(jù)業(yè)務(wù)場景進(jìn)行選擇?
  2. 還是…

1.2. 目標(biāo)

  1. 遠(yuǎn)程接口具備 Retry 和 Fallback 能力
  2. 能夠根據(jù)上下文不同場景,在發(fā)生調(diào)用異常時動態(tài)選擇 Retry 或 Fallback 進(jìn)行流程恢復(fù)

2. 快速入門

2.1. 準(zhǔn)備環(huán)境

項(xiàng)目主要依賴 spring retry 和 lego starter
首先,引入 spring-retry 依賴

<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>

此次,引入 lego-starter 依賴

<dependency>
<groupId>com.geekhalo.lego</groupId>
<artifactId>lego-starter</artifactId>
<version>0.1.17</version>
</dependency>

最后新建 RetryConfiguration 以開啟 Retry 能力

@EnableRetry
@Configuration
public class RetryConfiguration {
}

2.2. 構(gòu)建 ActionTypeProvider

在完成基本配置后,需要準(zhǔn)備一個 ActionTypeProvider 用以提供上下文信息。
ActionTypeProvider 接口定義如下:

public interface ActionTypeProvider {
ActionType get();
}
public enum ActionType {
COMMAND, QUERY
}

通常情況下,我們會使用 ThreadLocal 組件將 ActionType 存儲于線程上下文,在使用時從上下中獲取相關(guān)信息。

public class ActionContext {
private static final ThreadLocal<ActionType> ACTION_TYPE_THREAD_LOCAL = new ThreadLocal<>();
public static void set(ActionType actionType){
ACTION_TYPE_THREAD_LOCAL.set(actionType);
}
public static ActionType get(){
return ACTION_TYPE_THREAD_LOCAL.get();
}
public static void clear(){
ACTION_TYPE_THREAD_LOCAL.remove();
}
}

有了上下文之后,
ActionBasedActionTypeProvider 直接從 Context 中獲取 ActionType 具體如下

@Component
public class ActionBasedActionTypeProvider implements ActionTypeProvider {
@Override
public ActionType get(){
return ActionContext.get();
}
}

上下文中的 ActionType 又是怎么進(jìn)行管理的呢,包括信息綁定和信息清理?
最常用的方式便是:

  • 提供一個注解,在方法上添加注解用于對 ActionType 的配置;
  • 提供一個攔截器,對方法調(diào)用進(jìn)行攔截。方法調(diào)用前,從注解中獲取配置信息并綁定到上下文;方法調(diào)用后,主動清理上下文信息;

核心實(shí)現(xiàn)為:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
ActionType type();
}
@Aspect
@Component
@Order(Integer.MIN_VALUE)
public class ActionAspect {
@Pointcut("@annotation(com.geekhalo.lego.faultrecovery.smart.Action)")
public void pointcut() {
}
@Around(value = "pointcut()")
public Object action(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Action annotation = methodSignature.getMethod().getAnnotation(Action.class);
ActionContext.set(annotation.type());
try {
return joinPoint.proceed();
}finally {
ActionContext.clear();
}
}
}

在這些組件的幫助下,我們只需在方法上基于 @Action 注解進(jìn)行標(biāo)記,便能夠?qū)?ActionType 綁定到上下文。

2.3. 使用 @SmartFault

在將 ActionType 綁定到上下文之后,接下來要做的便是對 遠(yuǎn)程接口 進(jìn)行配置。遠(yuǎn)程接口的配置工作主要由 @SmartFault 來完成。
其核心配置項(xiàng)包括:

配置項(xiàng)

含義

默認(rèn)配置

recover

fallback 方法名稱


maxRetry

最大重試次數(shù)

3

include

觸發(fā)重試的異常類型


exclude

不需要重新的異常類型


接下來,看一個 demo

@Service
@Slf4j
@Getter
public class RetryService3 {
private int count = 0;
private int retryCount = 0;
private int fallbackCount = 0;
private int recoverCount = 0;
public void clean(){
this.retryCount = 0;
this.fallbackCount = 0;
this.recoverCount = 0;
}
/**
* Command 請求,啟動重試機(jī)制
*/
@Action(type = ActionType.COMMAND)
@SmartFault(recover = "recover")
public Long retry(Long input) throws Throwable{
this.retryCount ++;
return doSomething(input);
}
/**
* Query 請求,啟動Fallback機(jī)制
*/
@Action(type = ActionType.QUERY)
@SmartFault(recover = "recover")
public Long fallback(Long input) throws Throwable{
this.fallbackCount ++;
return doSomething(input);
}
@Recover
public Long recover(Throwable e, Long input){
this.recoverCount ++;
log.info("recover-{}", input);
return input;
}
private Long doSomething(Long input) {
// 偶數(shù)拋出異常
if (count ++ % 2 == 0){
log.info("Error-{}", input);
throw new RuntimeException();
}
log.info("Success-{}", input);
return input;
}
}

測試代碼如下:

@SpringBootTest(classes = DemoApplication.class)
public class RetryService3Test {
@Autowired
private RetryService3 retryService;
@BeforeEach
public void setup(){
retryService.clean();
}
@Test
public void retry() throws Throwable{
for (int i = 0; i < 100; i++){
retryService.retry(i + 0L);
}
Assertions.assertTrue(retryService.getRetryCount() > 0);
Assertions.assertTrue(retryService.getRecoverCount() == 0);
Assertions.assertTrue(retryService.getFallbackCount() == 0);
}
@Test
public void fallback() throws Throwable{
for (int i = 0; i < 100; i++){
retryService.fallback(i + 0L);
}
Assertions.assertTrue(retryService.getRetryCount() == 0);
Assertions.assertTrue(retryService.getRecoverCount() > 0);
Assertions.assertTrue(retryService.getFallbackCount() > 0);
}
}

運(yùn)行 retry 測試,日志如下:

[main] c.g.l.c.f.smart.SmartFaultExecutor       : action type is COMMAND
[main] c.g.l.faultrecovery.smart.RetryService3 : Error-0
[main] c.g.l.c.f.smart.SmartFaultExecutor : Retry method public java.lang.Long com.geekhalo.lego.faultrecovery.smart.RetryService3.retry(java.lang.Long) throws java.lang.Throwable use [0]
[main] c.g.l.faultrecovery.smart.RetryService3 : Success-0

可見,當(dāng) action type 為 COMMAND 時:

  • 第一次調(diào)用時,觸發(fā)異常,打?。?Error-0
  • 此時 SmartFaultExecutor 主動進(jìn)行重試,打印: Retry method xxxx
  • 方法重試成功,RetryService3 打印: Success-0

方法主動進(jìn)行重試,流程從異常中恢復(fù),處理過程和效果符合預(yù)期。

運(yùn)行 fallback 測試,日志如下:

[main] c.g.l.c.f.smart.SmartFaultExecutor       : action type is QUERY
[main] c.g.l.faultrecovery.smart.RetryService3 : Error-0
[main] c.g.l.c.f.smart.SmartFaultExecutor : recover From ERROR for method ReflectiveMethodInvocation: public java.lang.Long com.geekhalo.lego.faultrecovery.smart.RetryService3.fallback(java.lang.Long) throws java.lang.Throwable; target is of class [com.geekhalo.lego.faultrecovery.smart.RetryService3]
[main] c.g.l.faultrecovery.smart.RetryService3 : recover-0

可見,當(dāng) action type 為 QUERY 時:

  • 第一次調(diào)用時,觸發(fā)異常,打印: Error-0
  • SmartFaultExecutor 執(zhí)行 Fallback 策略,打?。簉ecover From ERROR for method xxxx
  • 調(diào)用RetryService3的 recover 方法,獲取最終返回值。RetryService3 打印:recover-0

異常后自動執(zhí)行 fallback,將流程從異常中恢復(fù)過來,處理過程和效果符合預(yù)期。

3. 設(shè)計&擴(kuò)展

3.1 核心設(shè)計

image

整體流程如下:

  • ActionAspect 從 @Action 中讀取配置信息,將請求類型綁定到線程上下文
  • 然后執(zhí)行正常業(yè)務(wù)邏輯
  • 當(dāng)調(diào)用 @SmartFault 注解的方法時,會被 SmartFaultMethodInterceptor 攔截器攔截
  1. 攔截器通過 ActionTypeProvider 獲取當(dāng)前的 ActionType
  2. 根據(jù) ActionType 對請求進(jìn)行路由
  3. 如果是 COMMAND 操作,將使用 RetryTemplate 執(zhí)行請求,在發(fā)生異常時,通過重試配置進(jìn)行請求重發(fā),從而最大限度的獲得遠(yuǎn)程結(jié)果
  4. 如果是 QUERY 操作,將使用 FallbackTemplate(重試次數(shù)為0的 RetryTemplate)執(zhí)行請求,當(dāng)發(fā)生異常時,調(diào)用 fallback 方法,執(zhí)行配置的 recover 方法,直接使用返回結(jié)果
  • 獲取遠(yuǎn)程結(jié)果后,執(zhí)行后續(xù)的業(yè)務(wù)邏輯
  • 最后,ActionAspect 將 ActionType 從線程上下文中移除

4. 項(xiàng)目信息

項(xiàng)目倉庫地址:https://gitee.com/litao851025/lego

項(xiàng)目文檔地址:https://gitee.com/litao851025/lego/wikis/support/smart-fault

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2012-10-09 09:14:47

2013-03-06 16:06:31

2021-07-14 15:27:27

論文神器數(shù)據(jù)

2020-09-21 06:53:41

NoSQL高并發(fā)面試

2009-01-15 09:43:00

網(wǎng)絡(luò)病毒斷網(wǎng)

2012-09-24 15:26:56

云計算XTools

2012-05-22 11:20:07

虛擬化VDI

2024-02-04 08:05:48

DataX阿里云開源

2012-06-20 09:22:14

云存儲

2012-06-18 15:12:58

云存儲

2020-06-16 09:45:59

CIO上系統(tǒng)IT管理

2011-08-12 09:35:27

Java 7

2011-07-29 14:19:12

2024-09-30 11:32:06

2018-11-15 15:03:59

安全運(yùn)營中心SOC數(shù)據(jù)泄露

2020-03-21 16:10:00

物聯(lián)網(wǎng)IoT風(fēng)險

2013-08-22 10:10:31

2011-07-01 12:16:41

移動ERP

2013-07-15 13:42:34

手機(jī)游戲生命周期

2015-04-21 09:20:40

SwfitObject—C
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號