当前位置: 首页 > 科技观察

Retry&Fallback是武器还是诅咒?

时间:2023-03-15 23:00:49 科技观察

1。概述在分布式场景中,Retry和Fallback是最常见的容灾方案。重试是指当调用远程接口失败时,Client主动发起重试请求,期待最终的结果,从而完成整个过程。Fallback是指当调用远程接口失败时,Client不重试而是调用一个特殊的fallback方法,从这个方法中获取结果,以便流程继续进行。Retry和Fallback如何选择?1.1.背景首先,让我们看看Retry和Fallback如何帮助进程自我恢复。1.1.1.Retry现在有一个生产流程:核心流程如下:从产品服务中获取产品信息根据产品信息创建订单,并将订单保存到数据库中如果网络抖动,生产就会失败。调用产品服务获取产品时,由于网络异常,导致无法获取产品信息,接口调用失败,生产流程异常中断。因为生产环节太重要了,系统需要尽最大努力保证用户能够完成订单操作。抖动的问题可以通过重试来解决。图片第一次获取商品信息时,系统不会因为网络问题直接抛出异常,而是等待一段时间后重新发起第二次请求,即Retry操作网络恢复,第二次请求成功获取商品信息的过程继续运行,最终完成用户生产。Retry机制非常适合服务短时间不可用,或者某个服务节点异常的场景。1.1.2.Fallback是生产验证接口,主要流程如下:调用产品服务接口获取产品信息根据产品和用户信息判断用户是否可以购买该产品同理,假设访问产品服务时出现网络异常:因为无法获取商品信息,导致整个验证过程异常中断,用户操作被强制终止。如果你聪明一点,你可能会说应该使用Retry,是的:如果短时间不可用,可以通过Retry机制来恢复进程。但是,如果商品和服务压力过大,响应时间过长怎么办?比如商品和服务的流量激增,导致DBCPU飙升,出现大量慢SQL,此时触发系统的Retry会怎样?image获取商品失败后,系统自动触发Retry机制。因为产品服务本身有问题,所以第二次请求还是失败了。服务触发第三次请求,还没得到结果。已达到最大重试次数,仍无法获取商品,用户请求只能被异常打断,无法通过Retry机制从异常中恢复进程,这也对下游的商品服务造成了极大的损害。商品服务压力大,响应时间长。上游系统超时触发自动重试。自动重试增加了对商品服务的调用。商品服务请求量更大,故障恢复难度更大。这通常称为“读取放大”。假设用户验证是否可以购买请求的请求次数为n,那么在极端情况下,商品和服务的请求次数为3n(其中2n是Retry机制造成的)。这时候Retry并不是一个很好的解决方案。我们先回到业务场景来思考一下。如果无法获取商品,是否可以直接放出验证接口,让用户先完成购买?如果这个业务假设是可以接受的,那么Fallback就该上场了。调用产品服务获取产品信息时系统不会重试。相反,回退机制将被触发。fallback会调用一个指定的方法,并将返回值作为远程接口的返回值。下一个流程使用回退方法的返回值来完成业务逻辑。1.1.3.场景思维也是商品服务接口的调用(同一个接口)。在不同的场景下,需要采用不同的策略来恢复业务流程。通常,在Command场景中首先使用Retry流量非常重要。最好确保过程的完整性。通常写流量比较小,小规模的Retry不会对下游系统造成太大的影响。Fallabck的大多数显示场景都优先使用查询场景。即使没有得到一些信息,整体影响也比较小。通常read场景流量大,Retry对下游系统的伤害不容忽视。我们应该如何处理一个远程接口被多个场景使用?提供两套接口,一套有Retry能力,一套有Fallback能力,用户可以根据业务场景选择?仍然...1.2。目标远程接口具有重试和回退功能。根据上下文的不同场景,调用异常时可以动态选择Retry或Fallback进行流程恢复。2.快速启动2.1。准备环境项目主要依赖springretry和legostarter。spring-retry依赖org.springframework.retryspring-retry这次引入lego-starter依赖com.geekhalo.legolego-starter0.1.17最后新建一个RetryConfiguration,开启Retry能力@EnableRetry@ConfigurationpublicclassRetryConfiguration{}2.2。构建ActionTypeProvider完成基本配置后,需要准备一个ActionTypeProvider来提供上下文信息。ActionTypeProvider接口定义如下:publicinterfaceActionTypeProvider{ActionTypeget();}publicenumActionType{COMMAND,QUERY}通常我们会使用ThreadLocal组件将ActionType存储在线程上下文中,并从上下文中获取相关信息使用时。公共类ActionContext{privatestaticfinalThreadLocalACTION_TYPE_THREAD_LOCAL=newThreadLocal<>();publicstaticvoidset(ActionTypeactionType){ACTION_TYPE_THREAD_LOCAL.set(actionType);}publicstaticActionTypeget(){return_ACTH_READ_T;}publicstaticvoidclear(){ACTION_TYPE_THREAD_LOCAL.remove();}}ActionBasedActionTypeProvider有了context后直接从Context中获取ActionType如下}}context中的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();动作注解=methodSignature.getMethod().getAnnotation(Action.class);ActionContext.set(annotation.type());尝试{返回joinPoint.proceed();}最后{ActionContext.clear();}}}在这些组件的帮助下,我们只需要将@Action基于方法Annotation即可将ActionType绑定到上下文。2.3.使用@SmartFault将ActionType绑定到上下文之后,接下来要做的就是配置远程接口。远程接口的配置主要由@SmartFault完成。其核心配置项包括:配置项含义默认配置recoverfallback方法名maxRetry最大重试次数3包括触发重试的异常类型排除不需要重试的异常类型接下来看一个demo@Service@Slf4j@GetterpublicclassRetryService3{私人整数计数=0;私有intretryCount=0;私人intfallbackCount=0;私人intrecoverCount=0;publicvoidclean(){this.retryCount=0;这个.fallbackCount=0;这个.recoverCount=0;}/***命令请求,启动重试机制*/@Action(type=ActionType.COMMAND)@SmartFault(recover="recover")publicLongretry(Longinput)throwsThrowable{this.retryCount++;返回做某事(输入);}/***查询请求,启动Fallback机制*/@Action(type=ActionType.QUERY)@SmartFault(recover="recover")publicLongfallback(Longinput)throwsThrowable{this.fallbackCount++;返回doSomething(输入);}@RecoverpublicLongrecover(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{@AutowiredprivateRetryService3retryService;@BeforeEachpublicvoidsetup(){retryService.clean();}@Testpublicvoidretry()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);}@Testpublicvoidfallback()throwsThrowable{for(inti=0;i<100;i++){重试服务.fallback(i+0L);}Assertions.assertTrue(retryService.getRetryCount()==0);Assertions.assertTrue(retryService.getRecoverCount()>0);Assertions.assertTrue(retryService.getFallbackCount()>0);}}运行重试测试,日志如下:[main]c.g.l.c.f.smart.SmartFaultExecutor:actiontypeisCOMMAND[main]c.g.l.faultrecovery.smart.RetryService3:Error-0com.geekhalo.lego.faultrecovery.smart.RetryService3。retry(java.lang.Long)throwsjava.lang.Throwableuse[0][main]c.g.l.faultrecovery.smart.RetryService3:Success-0isvisiblewhentheactiontypeisCOMMANDwhen:第一次调用时,异常被触发,打印:Error-0此时SmartFaultExecutor主动重试,打印:Retrymethodxxxxmethodretryissuccessful,RetryService3打印:Success-0method主动重试,流程从异常开始恢复,处理过程和效果符合预期运行回退测试,日志如下:[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.javalang.Longcom.geekhalo.lego.faultrecovery.smart.RetryService3.fallback(java.lang.Long)抛出java.lang.Throwable;targetisofclass[com.geekhalo.lego.faultrecovery.smart.RetryService3][main]c.g.l.faultrecovery.smart.RetryService3:recover-0可见,当actiontype为QUERY时:第一次调用时,异常为triggered,print:Error-0SmartFaultExecutor执行Fallback策略,print:recoverFromERRORformethodxxxx调用RetryService3的recover方法,得到最终的返回值。RetryService3打印:recover-0异常后自动执行fallback,进程从异常中恢复。处理过程和效果符合预期。3.Design&Extension3.1核心设计图的整体流程如下:ActionAspect从@Action中读取配置信息,将请求类型绑定到线程上下文,然后执行正常的业务逻辑。当调用@SmartFault注解的方法时,会被SmartFaultMethodInterceptor拦截,拦截器通过ActionTypeProvider获取当前ActionType,并根据ActionType路由请求。如果是COMMAND操作,它将使用RetryTemplate来执行请求。当异常发生时,会通过重试配置重新发送请求,以最大化远程结果。如果是QUERY操作,将使用FallbackTemplate(RetryTemplatewith0retries)来执行请求。当异常发生时,调用fallback方法,执行配置的recover方法。直接使用返回结果获取远程结果后,执行后续的业务逻辑。最后,ActionAspect将ActionType从线程上下文中移除4.项目信息项目仓库地址:https://gitee.com/litao851025/lego项目文档地址:https://gitee.com/litao851025/lego/wikis/support/智能故障