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

Java远程调用失败?如何优雅地重试?

时间:2023-03-12 13:01:42 科技观察

在日常开发过程中,我们经常需要调用第三方组件或者数据库。有时候我们的查询可能会因为网络抖动或者下游服务抖动而失败。这个时候,我们往往会重试。如果多次重试还是失败,则向上抛异常失败。接下来阿粉就给大家展示一下通常是怎么做的,以及如何更优雅地重试。让我们看一下传统的方法。常规做法会先设置重试次数,然后通过while循环遍历。当循环次数没有达到重试次数时,会返回,直到得到正确的结果。如果retry测试仍然失败,它会休眠一段时间并再次尝试,直到正常返回或达到重试次数。包com.example.demo.service;导入org.springframework.retry.annotation.Backoff;导入org.springframework.retry.annotation.Retryable;导入org.springframework.stereotype.Service;导入java.util.Random;导入java。util.concurrent.TimeUnit;@ServicepublicclassHelloService{publicStringsayHello(Stringname){Stringresult="";int重试时间=3;while(retryTime>0){try{//结果=name+doSomething();返回结果;}catch(Exceptione){System.out.println("发送消息失败。在1中重试");重试时间--;尝试{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptionex){thrownewRuntimeException(ex);}}}返回结果;}privateintdoSomething(){Randomrandom=newRandom();inti=random.nextInt(3);System.out.println("我是"+i);返回10/i;}}这里为了模拟异常情况,阿芬在doSomething函数中生成并使用了随机数使用,当随机值为0时,会触发java.lang.ArithmeticException,因为0不能作为除数。接下来,我们将提供一个访问接口。代码如下:packagecom.example.demo.controller;导入com.example.demo.service.HelloService;导入org.springframework.beans.factory.annotation.Autowired;导入org.springframework.web.bind.annotation.GetMapping;导入org.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassHelloController{@AutowiredprivateHelloServicehelloService;@GetMapping(value="/hello")publicStringhello(@RequestParam("name")Stringname){returnhelloService.sayHello(name);}}正常启动后,我们通过浏览器访问。可以看到我们在第一种方法中成功的达到了想要的效果,随机数为0,1秒后重试结果正常。多试几次,会遇到3次都为0的情况,此时会抛出异常,说明服务确实有问题。可以看出上面的代码是有效的。虽然不是很好看,尤其是在一些其他逻辑的情况下,会显得臃肿,但是可以正常使用,所以有朋友会问,有没有优雅的做法呢?你不能在很多地方重复写这样的重试代码。NoteRetry我们要知道,我们普通人在日常开发的时候,遇到问题,别人肯定也遇到过。当我们遇到一个没有人遇到过的问题,说明我们很进步。所以小伙伴们可以想到的是有没有简单的重试方法。有些人已经为我们弄明白了。通过@Retryable注解也可以达到同样的效果。接下来阿粉就给大家介绍一下这个注解的使用方法。首先,我们需要在启动类中添加@EnableRetry注解,表示我们要开启重试功能。这个很好理解,就像我们需要添加@EnableScheduling注解来开启定时功能一样。Spring的@Enablexxx注解也很有意思,以后再说。添加注解后需要添加切面依赖,如下org.aspectjaspectjweaver1.9.2不要添加如下这个方面的依赖,在启动的时候,会在异常中添加如下注解和依赖。之后,我们需要修改HelloService中的sayHello()方法,简化如下,添加@Retryable注解,并设置相应的参数值。@Retryable(value=Exception.class,maxAttempts=3,backoff=@Backoff(delay=1000,multiplier=2))publicStringsayHello(Stringname){returnname+doSomething();}再次通过浏览器访问http://127.0.0.1:8080/hello?name=ziyou我们看到效果如下,和自己写的重试是一样的。@Retryable详解////IntelliJIDEA从.class文件重新创建的源代码//(由FernFlower反编译器提供支持)//packageorg.springframework.retry.annotation;importjava.lang.annotation.Documented;importjava.lang.annotation.ElementType;导入java.lang.annotation.Retention;导入java.lang.annotation.RetentionPolicy;导入java.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy。RUNTIME)@Documentedpublic@interfaceRetryable{Stringrecover()默认"";字符串拦截器()默认"";类[]value()默认{};类[]include()默认{};类[]exclude()默认{};Stringlabel()默认"";布尔状态()默认假;intmaxAttempts()默认3;字符串maxAttemptsExpression()默认"";退避backoff()默认@Backoff;StringexceptionExpression()default"";斯特林g[]listeners()default{};}点进这个注解,我们可以看到这个注解的代码如下,有几个参数,我们来解释一下recover:当前类中回滚方法的名称;interceptor:heavy重试的拦截器名称,重试时可以配置一个拦截器;value:需要重试的异常类型,与下面的include一致;include:重试包含的异常类型;exclude:不计入重试的异常类型;label:用于统计的唯一标识;stateful:该标志表示重试是有状态的,即异常被重抛,重试策略是否会以相同的策略应用于后续相同参数的调用,如果为false,则可重试的异常不会被重抛。maxAttempts:重试次数;退避:为重试此操作指定的属性;listeners:重试侦听器bean的名称;配合上面的一些属性的使用,我们可以实现简单的注解来实现方法调用异常自动重试,非常好用。我们可以在执行retry方法的时候设置一个自定义的重试拦截器,如下所示,自定义的重试拦截器需要实现MethodInterceptor接口并实现invoke方法,但是注意如果使用了拦截器,那么方法上的参数将会被覆盖。包com.example.demo.pid;导入org.aopalliance.intercept.MethodInterceptor;导入org.aopalliance.intercept.MethodInvocation;导入org.springframework.retry.interceptor.RetryInterceptorBuilder;导入org.springframework.retry.interceptor.RetryOperationsInterceptor;导入org.springframework.retry.policy.SimpleRetryPolicy;导入org.springframework.stereotype.Component;@ComponentpublicclassCustomRetryInterceptorimplementsMethodInterceptor{@OverridepublicObjectinvoke(MethodInvocationinvocation)throwsThrowable{RetryOperationsInterceptorbuild=RetryInterceptorBuilder.stateless().maxAttempts(2).backOffOptions(3000,2,1000).build();返回build.invoke(调用);}}自定义回滚方法,我们也可以写一个自定义的回滚方法,如果在多次重试方法后错误仍然存??在。@Retryable(value=Exception.class,recover="recover",maxAttempts=2,backoff=@Backoff(delay=1000,multiplier=2))publicStringsayHello(Stringname){returnname+doSomething();}@RecoverpublicStringrecover(Exceptione,Stringname){System.out.println("recover");返回“恢复”;注意:重试方法必须用@Recover注解;返回值必须与重试函数的返回值相同;除了第一个参数是触发的异常,后面的参数需要和重试函数的参数列表保持一致;上面代码中的@Backoff(delay=1000,multiplier=2)表示第一次Delay1000ms重试,以后每次重试的延迟时间加倍。小结今天阿粉给大家介绍了Spring中@Retryable注解的使用,并通过几个demo给大家展示了如何编写自己的重试拦截器和回滚方法,是不是觉得用起来会很爽?还等什么,赶快用起来吧。里面还有很多细节,只有真正用过才能体会到。