阿里梅指南。
容错编程是一个重要的编程思想,可以提高应用程序的可靠性和稳定性,同时提高应用程序的鲁棒性。 本文总结了部分作者在面对服务故障(如AOP、CGLIB)时如何进行优雅重试,并对重试工具组件的源代码和注意事项进行总结分析。 容错编程是一种旨在确保应用程序可靠性和稳定性的编程思想,它采取以下措施:1异常处理:通过捕获和处理异常来避免应用程序崩溃。
2.错误处理:通过检查错误**并采取适当的操作(如重试或回滚)来处理错误。
3.重试机制:如果出现错误,请尝试重新执行 ** 块,直到它成功或达到最大尝试次数。
4.备份机制:如果主系统发生故障,请切换到备用系统以保持应用程序正常运行。
5.日志记录:记录错误和异常信息,以便后续进行故障排除。 容错编程是一个重要的编程思想,可以提高应用程序的可靠性和稳定性,同时提高应用程序的鲁棒性。 1. 为什么需要重试?
在做业务技术时,设计一个可复用、可扩展、可编排的系统架构非常重要,这直接决定了业务需求迭代的效率。 但同时,业务技术人员也应该有悲观的心态:在分布式环境中,HSF服务因单点问题而出现瞬时抖动的情况并不少见,比如系统瞬时抖动、单点故障、服务超时、服务异常、中间件抖动、网络超时、配置错误等软硬件问题。 忽略这些异常会降低服务的健壮性,影响用户体验,导致用户投诉,甚至系统故障。 因此,在设计和实施解决方案时,需要充分考虑各种故障场景,有针对性地进行防御性编程。 当我们调用第三方 API 时,我们经常会遇到失败的情况,对于这些情况,我们通常会处理失败重试和失败丢弃逻辑。 但是,重试并非适用于所有场景,例如参数验证无效、读写操作是否适合重试、数据是否幂等。 如果远程呼叫超时或网络突然中断,您可以重试。 我们可以设置多次重试,以增加调用成功的概率。 为了方便后续故障排除和故障率统计,我们还可以在数据库中记录故障次数和是否成功,以便重试统计和定时任务。 训练研发团队在大本营、训练业务、本地e-station等多个业务中充分演练了各种故障场景,并对部分核心流程进行了多种形式的故障重试处理,如骑手测试提交、数据同步到洞察平台、获取量子平台圈标签等。 本文总结了面对服务失败时执行优雅重试的一些方法,例如 AOP、CGLIB,并总结和分析了重试工具组件的源代码和注意事项。
2. 如何重试。
2.1 简单的重试方法
测试演示
2.2 动态模式版本@test
public integer sampleretry(int code) catch (exception e)
system.out.println("sampleretry,返回! ");
return null;
在某些情况下,当一个对象不适合或可以直接引用另一个对象时,我们可以使用一个对象作为客户端和目标对象之间通信的中介。 使用该对象的优点是它是兼容的,并且可以通过每个重试方法调用。
如何使用:
测试演示public class dynamicproxytest implements invocationhandler
获取更新
Param RealSubject 对象
public static object getproxy(object realsubject)
@override
public object invoke(object proxy, method method, object args) throws throwable catch (exception e)
return null;
2.3 字节码技术生成 ** 重试次数@test
public integer v2retry(int code)
CGLIB 是一个构建库,用于扩展 jA 类并在运行时实现接口。 它功能强大、性能高、质量高。 使用 CGLIB,您可以生成子类来定位对象,在不更改原始类的情况下扩展和增强它们。 这种技术广泛用于 AOP 框架、ORM 框架、缓存框架和许多其他 J**A 应用程序。 CGLIB通过生成字节码来创建**类,具有很高的性能。
如何使用:
public class cglibproxytest implements methodinterceptor catch (exception e)
return null;
获取课程
* Param Clazz 类信息
返回类结果
public object getproxy(class clazz)
测试演示
2.4 HSF 调用超时重试@test
public integer cglibretry(int code)
在我们的日常开发中,在调用第三方HSF服务时,出现瞬态抖动是很常见的。 为了减少调用超时对业务的影响,我们可以根据服务和下游服务的特点,使用HSF同步重试。 如果使用的框架没有专门设置,则默认情况下不会自动重试 HSF 接口超时。 在注释@hsfconsumer中,有一个参数重试,它允许您设置失败的重试次数。 默认情况下,此参数的值默认为 0。
HSFCer超时重试原理服务调用流程示意图:@hsfconsumer(serviceversion = "1.0.0", servicegroup = "hsf",clienttimeout = 2000, methodspecials = )
private xxxhsfservice xxxhsfserviceconsumer;
当 asynctosyncinvocationhandler invoketype(.) 时,会发生 HSF 超时重试。如果配置的重试参数大于 0,则使用 retry() 方法重试,并且仅在重试时重试TimeOutException箱。 源代码分析。
从上面的段落中可以看出,重试只发生在同步调用中。 使用者方法的元数据执行次数大于 1 (consumermethodmodel.)。getexecutetimes() 1) 尝试重试:private rpcresult invoketype(invocation invocation, invocationhandler invocationhandler) throws throwable else if (consumermethodmodel.getexecutetimes() 1) else
} else
hsfresponse hsfresponse = new hsfresponse();
hsfresponse.setappresponse(appresponse);
rpcresult rpcresult = new rpcresult();
rpcresult.sethsfresponse(hsfresponse);
return rpcresult;
hsfconsumer 超时重试原则利用了一个简单的 while 循环 + try-catch 缺陷: 1. 只有在同步调用该方法时才会重试。private rpcresult retry(invocation invocation, invocationhandler invocationhandler,
listenablefuture future, int executetimes) throws throwable
int timeout = -1;
try catch (executionexception e) catch (timeoutexception e) else
} catch (throwable e)
2. 只有当 HSF 接口出现 timeoutexception 时,才会调用重试方法。
3. 如果在 HSFCeuser 中为某个方法设置了 retries 参数,则该方法返回时出现超时异常,HSF SDK 会自动重试。 重试的实现方式是 while+ try-catch 循环。 因此,如果自动重试接口变慢,重试次数设置过高,RT会变长,极端情况下,HSF线程池会已满。 因此,HSF的自动重试功能是一个基础且简单的功能,不建议大规模使用。
2.5 spring retry
Spring Retry 是 Spring 系列中的一个子项目,它提供声明性重试支持,以帮助我们以标准化的方式处理任何特定操作的重试。 该框架非常适合需要重试的业务场景,例如网络请求和数据库访问。 使用 Spring Retry,我们可以使用注释来设置重试策略,而无需编写冗长的 **。 所有配置都是基于注释的,这使得使用 Spring Retry 非常简单直观。 POM 依赖关系。
启用 @retryable 后,引入了 spring-retry jar 包,并在 Spring Boot 的启动类中添加了 @enableretry 注解。org.springframework.retrygroupid>
spring-retryartifactid>
dependency>
org.springframeworkgroupid>
spring-aspectsartifactid>
dependency>
服务实现类@retryable注解@enableretry
@springbootapplication(scanbasepackages = ,excludename = )
@importresource()
public class application
可以看到,在**中,实现方法被注解@retryable,@retryable有以下参数可以配置:@override
@retryable(value = bizexception.class, maxattempts = 6)
public integer retryabletest(integer code)
baseresponse objectbaseresponse = responsehandler.servicefailure(responseerrorenum.update_comment_failure);
system.out.println("retryabletest,正确! ");
return 200;
@recover
public integer recover(bizexception e) ;
spring-retry 还为@retryable重试失败处理方法提供了@recover注释。 如果不需要 ** 方法,可以直接不写 ** 方法,那么效果就是重试次数结束后,如果还是不成功,不符合业务判断,就会抛出异常。 可以看到 pass 参数说 bizexception e,它被用作 ** 的连接器代码(重试次数用完了,或者失败,我们抛出这个 bizexception e 通知来触发这个 ** 方法)。
注意:@recover注解开启重试失败后调用的方法,注解的方法参数必须是@retryable抛出的异常,否则无法识别。 @recover标记的方法的返回值必须与@retryable标记的方法相同。 此方法编写在与重试方法相同的实现类中。 由于它基于 AOP 实现,因此它不支持类中的自调用方法。 不能在方法中使用 try catch,只能向外抛出异常,并且异常必须是 throwable 类型。 原理 spring-retyr 调用序列图:
Spring Retry 的基本原理是通过@enableretry注解引入 AOP 功能。 当 Spring 容器启动时,将扫描所有用 @retryable 和 @circuitbreaker(fuses)注释的方法,并生成切入点和建议。 当发生方法调用时,Spring 委托 RetryOperationsInterceptor 进行调用,并在内部实现故障回复重试和降级恢复方法。 这种设计模式使得重试逻辑的实现非常简单易懂,并充分利用了Spring框架提供的AOP能力,实现了高效优雅的重试机制。 缺陷尽管 Spring Retry 工具能够优雅地实现重试,但它仍然有两个不太友好的设计:首先,重试实体限定为 Throwable 子类,这意味着重试是针对可捕获的功能异常,但实际上我们可能希望依赖数据对象实体作为重试实体, 但 Spring Retry 框架必须强制将其转换为 Throwable 子类。其次,重试根断言对象使用了 dowithretry 的异常实例,这不符合正常内部断言的返回设计。 Spring Retry 建议使用注解来重试方法,重试逻辑同步执行。 重试的“失败”是可抛出的异常,如果想通过返回值的某个状态来判断是否需要重试,可能需要自己判断返回值,手动抛出异常。
2.6 gu**a retrying
Gu**A Retrying 是一个基于 Google 核心类库 Gu**A 的重试机制的库,它提供了一种通用的方法,使用GU**A Predicate Matching增强的特定停止、重试和异常处理能力,支持多种重试策略,例如指定重试次数、指定重试间隔、 等等。此外,它还支持谓词匹配,以确定重试时是否以及应该执行哪些操作。 gu**a 重试的最大特点是它可以灵活地与其他 gu**a 库集成,这使得它非常易于使用。 POM 依赖关系。
测试演示com.github.rholdergroupid>
gu**a-retryingartifactid>
2.0.0version>
dependency>
当重试发生时,如果我们需要做一些额外的操作,比如发送告警邮件,我们可以使用 retrylistener。 每次重试后,gu**a-retry 会自动 ** 我们注册的侦听器。 可以注册多个 retrylistener,并按照注册顺序调用它们。public static void main(string args)
retryer retryer = retryerbuilder.newbuilder()
retryif 重试条件
.retryifexception()
.retryifruntimeexception()
.retryifexceptionoftype(exception.class)
.retryifexception(predicates.equalto(new exception())
.retryifresult(predicates.equalto(false))
等待策略:每个请求之间间隔 1 秒
.withwaitstrategy(waitstrategies.fixedwait(1, timeunit.seconds))
停止策略:尝试请求 6 次
.withstopstrategy(stopstrategies.stopafterattempt(6))
时间限制:请求不得超过 2 秒
.withattempttimelimiter(
attempttimelimiters.fixedtimelimit(2, timeunit.seconds))
注册自定义 *** 可以在失败后实现回退方法)。
.withretrylistener(new myretrylistener())build();
try catch (exception ee)
RetryerBuilder 是一个工厂创建者,可以自定义重试源并支持多个重试源,配置重试次数或重试超时时间,并配置等待间隔以创建重试器实例。public class myretrylistener implements retrylistener else
system.out.println();
RetryerBuilder 的重试源支持异常异常对象和自定义断言对象,retryifexception 和 retryifresult 设置支持多个兼容对象。 RetryifException,在引发运行时异常或选中异常时重试,但在引发错误时不会重试。
仅当引发运行时异常时,才会重试 RetryifRuntimeException,并且不会重试选中的异常和错误。
RetryifExceptionOfType 允许我们仅在发生特定异常(如 NullPointerException 和 IllegalStateException)时重试,这两者都是运行时异常以及自定义错误。
RetryIfResult 指定可调用方法将在返回值时重试。
stopstrategy:停止重试策略的提供方式如下:
重试器工具的优点类似于 spring 重试,因为它通过定义重试器角色来包装正常的逻辑重试。 但是,gu**a retryer 在策略定义方面更胜一筹。 它不仅允许您设置重试次数和频率来控制它,而且还通过与多个异常或自定义实体对象重试源定义兼容来提供更大的灵活性。 这使得 gu**a retryer 可以应用于更广泛的业务场景,例如网络请求、数据库访问等。 此外,Gu**A Retryer 具有高度的可扩展性,可以很容易地与其他 Gu**A 库集成。 3.优雅的重试共性和原则。
Spring Retry 和 Gu**A Retryer 都是线程安全重试工具,支持并发业务场景下的重试逻辑,保证重试的正确性。 这些工具可以设置重试间隔、差异化的重试策略和重试超时,进一步提高重试的有效性和过程的稳定性。 同时,Spring Retry 和 Gu**A Retryer 都使用命令设计模式,通过对重试对象进行去分化来完成相应的逻辑操作,并在内部实现重试逻辑的封装。 这种设计模式使得扩展和修改重试逻辑变得非常容易,同时还增强了可重用性。 第四,总结。
在一些功能逻辑中,存在不稳定依赖的场景,然后我们需要使用重试机制来获得预期的结果,或者尝试重新执行逻辑而不立即结束。 例如,在远程接口访问、数据加载访问、数据上传校验等场景中,可能需要使用重试机制。 不同的异常场景需要以不同的方式进行不同的重试,我们应该尽可能地将正常逻辑与重试逻辑解耦。 在设置重试策略时,我们需要根据实际情况考虑一些问题。 例如,何时适合重试? 我应该尝试同步、阻塞、重试还是异步延迟? 你有办法一键快速失败吗? 此外,我们还需要考虑不重试的失败是否会严重影响用户体验。 在设置超时时间、重试策略、重试场景和重试次数时,我们还需要仔细考虑上述问题。 本文只对重试机制的一小部分进行了说明,在实际应用中,我们应该根据实际情况,采用合适的失败重试方案。 参考文档:gu**a-retrying实现重试机制: 编程失败重试: 组件@recover失败问题解决: 引导 注意,优雅的重试机制实现: 并行毛: 本文带你探讨HSF的实现原理: 自动重试: HSF spring-retry resilience4j 自研小工具: