0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Retry和Fallback该怎么抉择呢?

jf_ro2CN3Fa 来源:geekhalo 2023-02-21 17:15 次阅读

1. 概览

在分布式场景中,Retry 和 Fallback 是最常见的容灾方案。

Retry 就是在调用远程接口失败时,Client 主动发起重试请求,以期待获得最终结果,从而完成整个流程

Fallback 是在调用远程接口失败时,Client 不进行重试而是调用一个特殊的 fallback 方法,从这个方法中获取结果,使流程能够继续下去

那 Retry 和 Fallback 该怎么抉择呢?

1.1. 背景

首先,先看下 Retry 和 Fallback 都是怎么帮助流程进行自我恢复的。

1.1.1. Retry

现在有一个生单流程:

e715a98e-af6d-11ed-bfe3-dac502259ad0.png

核心流程如下:

商品服务中获取商品信息

根据商品信息创建订单

将订单保存到数据库

如果发生网络抖动,将导致生单失败。

e7412a1e-af6d-11ed-bfe3-dac502259ad0.png

在调用商品服务获取商品时,由于网络异常,接口调用失败

由于无法获取商品信息,生单流程被异常中断

由于生单流程太过重要,系统需尽最大努力保障用户能够完成下单操作,那针对网络抖动这个问题,可以通过 Retry 进行修复。

e770aa32-af6d-11ed-bfe3-dac502259ad0.png

在第一次获取商品信息时,由于网络问题导致获取失败

系统不会直接抛出异常,而是在等待一段时间后,重新发起第二次请求,也就是 Retry 操作

网络恢复,第二次请求成功获取商品信息

流程继续运行,最终完成用户生单

Retry 机制非常适合服务短时间不可用,或某个服务节点异常 这类场景。

1.1.2. Fallback

一个生单验证接口,主流程如下:

e7990478-af6d-11ed-bfe3-dac502259ad0.png

调用商品服务的接口获取商品信息

根据商品和用户信息判断用户是否能够购买该商品

同样,假设在访问商品服务时出现网络异常:

e7bda256-af6d-11ed-bfe3-dac502259ad0.png

由于无法获取商品信息,从而导致整个验证流程被异常中断,用户操作被迫终止。

聪明的你估计会说那就使用 Retry 呀,是的:

e7e7c716-af6d-11ed-bfe3-dac502259ad0.png

如果是短时不可用,通过 Retry 机制便可以恢复流程。

但,如果是商品服务压力过大,响应时间过长呢?比如,商品服务流量激增,导致 DB CPU 飙升,出现大量的慢 SQL,这时触发了系统的 Retry 会是怎样?

e8136e84-af6d-11ed-bfe3-dac502259ad0.png

在获取商品失败后,系统自动触发 Retry 机制

由于是商品服务本身出了问题,第二次请求仍旧失败

服务又触发了第三次请求,仍未获取结果

达到最大重试次数,仍旧无法获取商品,只能通过异常中断用户请求

通过 Retry 机制未能将流程从异常中恢复过来,也给下游的 商品服务 造成了巨大伤害。

商品服务压力大,响应时间长

上游系统由于超时触发自动重试

自动重试增大了对商品服务的调用

商品服务请求量更大,更难以从故障中恢复

这就是常说的“读放大”,假设用户验证是否能够购买请求的请求量为 n,那极端情况下 商品服务的请求量为 3n (其中 2n 是由 Retry 机制造成)

此时,Retry 就不是一个好的方案。我们先退回业务场景进行思考,如果无法获取商品,验证接口是否可以直接放行,先让用户完成购买?

如果,这个业务假设能够接受的话,那就到了 Fallback 上场的时候了。

e838af8c-af6d-11ed-bfe3-dac502259ad0.png

调用商品服务获取商品信息失败

系统不会进行重试,而是触发 fallback 机制

fallback 会调用指定的一个方法,并将返回值作为远程接口的返回值

接下来的流程使用 fallback 方法的返回值完成业务逻辑

1.1.3. 场景思考

同样是对商品服务接口(同一个接口)的调用,在不同的场景需要使用不同的策略用以恢复业务流程,通常情况下:

Command 场景优先使用 Retry

这种流量即为重要,最好能保障流程的完整性

通常写流量比较小,小范围 Retry 不会对下游系统造成巨大影响

Query 场景优选使用 Fallabck

大多数展示场景,哪怕部分信息没有获取到对整体的影响也比较小

通常读场景流量较高,Retry 对下游系统的伤害不容忽视

那面对一个远程接口被多个场景使用,我们该怎么处理呢?

提供两组接口,一个具有 Retry 能力,一个具有 Fallback 能力,由使用方根据业务场景进行选择?

还是…

1.2. 目标

远程接口具备 Retry 和 Fallback 能力

能够根据上下文不同场景,在发生调用异常时动态选择 Retry 或 Fallback 进行流程恢复

2. 快速入门

2.1. 准备环境

项目主要依赖 spring retry 和 lego starter首先,引入 spring-retry 依赖


org.springframework.retry
spring-retry

此次,引入 lego-starter 依赖


com.geekhalo.lego
lego-starter
0.1.17

最后新建 RetryConfiguration 以开启 Retry 能力

@EnableRetry
@Configuration
publicclassRetryConfiguration{
}

2.2. 构建 ActionTypeProvider

在完成基本配置后,需要准备一个 ActionTypeProvider 用以提供上下文信息。ActionTypeProvider 接口定义如下:

publicinterfaceActionTypeProvider{
ActionTypeget();
}

publicenumActionType{
COMMAND,QUERY
}

通常情况下,我们会使用 ThreadLocal 组件将 ActionType 存储于线程上下文,在使用时从上下中获取相关信息。

publicclassActionContext{
privatestaticfinalThreadLocalACTION_TYPE_THREAD_LOCAL=newThreadLocal<>();

publicstaticvoidset(ActionTypeactionType){
ACTION_TYPE_THREAD_LOCAL.set(actionType);
}

publicstaticActionTypeget(){
returnACTION_TYPE_THREAD_LOCAL.get();
}

publicstaticvoidclear(){
ACTION_TYPE_THREAD_LOCAL.remove();
}
}

有了上下文之后,ActionBasedActionTypeProvider 直接从 Context 中获取 ActionType 具体如下

@Component
publicclassActionBasedActionTypeProviderimplementsActionTypeProvider{
@Override
publicActionTypeget(){
returnActionContext.get();
}
}

上下文中的 ActionType 又是怎么进行管理的呢,包括信息绑定和信息清理?最常用的方式便是:

提供一个注解,在方法上添加注解用于对 ActionType 的配置;

提供一个拦截器,对方法调用进行拦截。方法调用前,从注解中获取配置信息并绑定到上下文;方法调用后,主动清理上下文信息;

核心实现为:

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceAction{
ActionTypetype();
}


@Aspect
@Component
@Order(Integer.MIN_VALUE)
publicclassActionAspect{
@Pointcut("@annotation(com.geekhalo.lego.faultrecovery.smart.Action)")
publicvoidpointcut(){
}

@Around(value="pointcut()")
publicObjectaction(ProceedingJoinPointjoinPoint)throwsThrowable{
MethodSignaturemethodSignature=(MethodSignature)joinPoint.getSignature();
Actionannotation=methodSignature.getMethod().getAnnotation(Action.class);
ActionContext.set(annotation.type());
try{
returnjoinPoint.proceed();
}finally{
ActionContext.clear();
}
}
}

在这些组件的帮助下,我们只需在方法上基于 @Action 注解进行标记,便能够将 ActionType 绑定到上下文。

2.3. 使用 @SmartFault

在将 ActionType 绑定到上下文之后,接下来要做的便是对 远程接口 进行配置。远程接口的配置工作主要由 @SmartFault 来完成。其核心配置项包括:

配置项 含义 默认配置
recover fallback 方法名称
maxRetry 最大重试次数 3
include 触发重试的异常类型
exclude 不需要重新的异常类型

接下来,看一个 demo

@Service
@Slf4j
@Getter
publicclassRetryService3{
privateintcount=0;

privateintretryCount=0;
privateintfallbackCount=0;
privateintrecoverCount=0;

publicvoidclean(){
this.retryCount=0;
this.fallbackCount=0;
this.recoverCount=0;
}

/**
*Command请求,启动重试机制
*/
@Action(type=ActionType.COMMAND)
@SmartFault(recover="recover")
publicLongretry(Longinput)throwsThrowable{
this.retryCount++;
returndoSomething(input);
}

/**
*Query请求,启动Fallback机制
*/
@Action(type=ActionType.QUERY)
@SmartFault(recover="recover")
publicLongfallback(Longinput)throwsThrowable{
this.fallbackCount++;
returndoSomething(input);
}

@Recover
publicLongrecover(Throwablee,Longinput){
this.recoverCount++;
log.info("recover-{}",input);
returninput;
}

privateLongdoSomething(Longinput){
//偶数抛出异常
if(count++%2==0){
log.info("Error-{}",input);
thrownewRuntimeException();
}
log.info("Success-{}",input);
returninput;
}
}

测试代码如下:

@SpringBootTest(classes=DemoApplication.class)
publicclassRetryService3Test{
@Autowired
privateRetryService3retryService;

@BeforeEach
publicvoidsetup(){
retryService.clean();
}

@Test
publicvoidretry()throwsThrowable{
for(inti=0;i< 100; i++){
            retryService.retry(i + 0L);
        }

        Assertions.assertTrue(retryService.getRetryCount() >0);
Assertions.assertTrue(retryService.getRecoverCount()==0);
Assertions.assertTrue(retryService.getFallbackCount()==0);
}

@Test
publicvoidfallback()throwsThrowable{
for(inti=0;i< 100; i++){
            retryService.fallback(i + 0L);
        }

        Assertions.assertTrue(retryService.getRetryCount() == 0);
        Assertions.assertTrue(retryService.getRecoverCount() >0);
Assertions.assertTrue(retryService.getFallbackCount()>0);
}
}

运行 retry 测试,日志如下:

[main]c.g.l.c.f.smart.SmartFaultExecutor:actiontypeisCOMMAND
[main]c.g.l.faultrecovery.smart.RetryService3:Error-0
[main]c.g.l.c.f.smart.SmartFaultExecutor:Retrymethodpublicjava.lang.Longcom.geekhalo.lego.faultrecovery.smart.RetryService3.retry(java.lang.Long)throwsjava.lang.Throwableuse[0]
[main]c.g.l.faultrecovery.smart.RetryService3:Success-0

可见,当 action type 为 COMMAND 时:

第一次调用时,触发异常,打印: Error-0

此时 SmartFaultExecutor 主动进行重试,打印: Retry method xxxx

方法重试成功,RetryService3 打印: Success-0

方法主动进行重试,流程从异常中恢复,处理过程和效果符合预期。

运行 fallback 测试,日志如下:

[main]c.g.l.c.f.smart.SmartFaultExecutor:actiontypeisQUERY
[main]c.g.l.faultrecovery.smart.RetryService3:Error-0
[main]c.g.l.c.f.smart.SmartFaultExecutor:recoverFromERRORformethodReflectiveMethodInvocation:publicjava.lang.Longcom.geekhalo.lego.faultrecovery.smart.RetryService3.fallback(java.lang.Long)throwsjava.lang.Throwable;targetisofclass[com.geekhalo.lego.faultrecovery.smart.RetryService3]
[main]c.g.l.faultrecovery.smart.RetryService3:recover-0

可见,当 action type 为 QUERY 时:

第一次调用时,触发异常,打印: Error-0

SmartFaultExecutor 执行 Fallback 策略,打印:recover From ERROR for method xxxx

调用RetryService3的 recover 方法,获取最终返回值。RetryService3 打印:recover-0

异常后自动执行 fallback,将流程从异常中恢复过来,处理过程和效果符合预期。

3. 设计&扩展

3.1 核心设计

e85d76fa-af6d-11ed-bfe3-dac502259ad0.png

整体流程如下:

ActionAspect 从 @Action 中读取配置信息,将请求类型绑定到线程上下文

然后执行正常业务逻辑

当调用 @SmartFault 注解的方法时,会被 SmartFaultMethodInterceptor 拦截器拦截

拦截器通过 ActionTypeProvider 获取当前的 ActionType

根据 ActionType 对请求进行路由

如果是 COMMAND 操作,将使用 RetryTemplate 执行请求,在发生异常时,通过重试配置进行请求重发,从而最大限度的获得远程结果

如果是 QUERY 操作,将使用 FallbackTemplate(重试次数为0的 RetryTemplate)执行请求,当发生异常时,调用 fallback 方法,执行配置的 recover 方法,直接使用返回结果

获取远程结果后,执行后续的业务逻辑

最后,ActionAspect 将 ActionType 从线程上下文中移除。








审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10873

    浏览量

    212024
  • SQL
    SQL
    +关注

    关注

    1

    文章

    766

    浏览量

    44164
  • 数据库
    +关注

    关注

    7

    文章

    3816

    浏览量

    64459

原文标题:容灾方案:Retry 和 Fallback 该怎么抉择?

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    usb fallback这个是主要什么作用?

    usb fallback 这个是主要什么作用
    发表于 07-04 07:21

    FPGA,如何抉择

    方面的) 对C语言熟悉,但VHDL又和C语言半毛钱关系没有。 现在都不知道是否要继续下去了,现在的困惑是要不要继续下去呢 。现在想是应该重新去找个用得着C语言的和硬件又有关联的(比如嵌入式)的工作,还是把这FPGA继续学下去? 搞FPGA有前途吗? 望论坛里的前辈能给点建议吗? 先谢谢各位了!
    发表于 07-06 21:29

    为什么activate_retry_interval的值不等于3?

    各位高手,小弟正在基于庆科micokit-3165开发板和MICO操作系统开发应用。正在调试micokit_enjoy工程。运行完成“uint32_t activate_retry
    发表于 10-09 00:07

    Uboot更新固件报错ARP retry count exceeded如何解决?

    Uboot 更新固件报错ARP retry count exceeded,请问这个怎么解决?
    发表于 09-12 08:26

    本本发热导致“发烧” 那怎么办

    本本发热导致“发烧” 那怎么办? 焦点:怎样让我们可爱的本本保持正常的“体温”?   你是否曾经听说过,瑞典的
    发表于 01-26 11:40 1584次阅读

    荣耀9、小米6对比评测:这对难兄难弟,外观、价格也都差不多如何抉择

    要说,今年的手机新品让我都心动,但是总不能都买吧!虽然我用的是魅族,但是pro7真的让我提不起兴趣,所以在此问问各位网友们?在荣耀9和小米6之间,我应该怎么抉择?望各位上车的朋友们分析分析,让我在这个周把手机这个难题解决了?
    发表于 10-11 09:42 770次阅读

    华为P20Pro和iPhone 8Plus如何抉择

    采用了双玻璃设计,也算创新了一回,不过大多数人认为其是在向乔布斯的iPhone 4致敬。所以如何抉择,首先要看你对外观上的要求怎样,是喜欢经典还是喜欢时尚主流
    发表于 11-07 18:34 1510次阅读

    地铁二维码改造是如何实现的?传统地铁闸机厂商又该如何抉择

    地铁全面进入刷码乘车“新时代”!那么,地铁二维码改造是如何实现的?传统地铁闸机厂商又该如何抉择
    的头像 发表于 06-01 16:24 1.3w次阅读

    中国移动携手华为打通了首个5G EPS Fallback语音视频通话

    在5G网络语音业务中,EPS fallback方案允许5G终端驻留在5G网络,但在4G网络上提供语音业务。当终端发起语音呼叫时,网络通过EPS Fallback流程将终端切换到4G网络上,通过
    发表于 06-14 10:19 1232次阅读

    四案例EPS Fallback问题解决资料下载

    电子发烧友网为你提供四案例EPS Fallback问题解决资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 03-27 08:44 13次下载
    四案例EPS <b class='flag-5'>Fallback</b>问题解决资料下载

    SA EPS FallBack重要信令节点资料下载

    电子发烧友网为你提供SA EPS FallBack重要信令节点资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 04-14 08:55 4次下载
    SA EPS <b class='flag-5'>FallBack</b>重要信令节点资料下载

    MOS管如何抉择

    MOS管是电子制造的基本元件,但面对不同封装、不同特性、不同品牌的MOS管时,如何抉择?有没有省心、省力的遴选方法?
    的头像 发表于 08-25 08:56 1732次阅读

    缩短MultiBoot流程中的回跳 (Fallback)时间

    MultiBoot 是 FPGA 远程更新配置文件时一种非常普遍的应用--为了确保安全,我们通常需要安排一个 Golden Image,升级失败后 FPGA 能回跳 (Fallback) 到此配置,从而使 FPGA 始终处于可被检测的工作状态。
    的头像 发表于 12-09 09:50 1347次阅读

    5G EPS Fallback语音方案流程总结

    选择EPS Fallback作为5G SA的语音方案,是因为考虑到目前5G SA建网初期,5G信号覆盖还处于初期阶段,没有大规模的覆盖,而4G的覆盖已经进入成熟期。
    的头像 发表于 04-20 11:11 2.1w次阅读

    如何抉择PLC和DCS系统

    在可编程逻辑控制器(PLC)和分散式控制系统(DCS)之间如何抉择,要具体情况具体分析,因为应用场合不同,对控制系统的要求也各不相同。
    发表于 06-25 10:40 453次阅读
    如何<b class='flag-5'>抉择</b>PLC和DCS系统