博主自主知识产权《Spring Boot深入浅出系列课程》(16章97节文档) 已经上线,请关注

精讲响应式WebClient第6篇-请求失败自动重试机制

spring 字母哥 0评论

精讲响应式WebClient第6篇-请求失败自动重试机制

本文是精讲响应式WebClient第6篇,前篇的blog访问地址如下:

在上一篇我们为大家介绍了WebClient的异常处理方法,我们可以对指定的异常进行处理,也可以分类处理400-499、500-599状态码的HTTP异常。
我们本节为大家介绍的实际上是另外一种异常处理机制:请求失败之后自动重试。当WebClient发起请求,没有得到正常的响应结果,它就会每隔一段时间再次发送请求,可以发送n次,这个n是我们自定义的。n次请求都失败了,最后再将异常抛出,可以通过我们上一节交给大家的方法进行异常处理。也就是针对连接超时异常、读写超时异常等,或者是HTTP响应结果为非正常状态码(不是200状态码段),都在自动重试机制的范畴内。

一、请求异常重试

下面的代码是请求"http://jsonplaceholder.typicode.com" 网站的服务,该网站是一个免费提供HTTP请求测试的服务端网站,我们可以用它测试WebClient。需要注意的是:正常的GET方法请求地址是"/posts/1",我特意的把它写错成为"/postss/1",这样可以触发404资源无法找到的异常。

public class ReTryTest {

  @Test
  public void testRetry() {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://jsonplaceholder.typicode.com")
            .build();

    Mono<String> mono = webClient
            .get()  //GET 请求
            .uri("/postss/1")  // 请求路径,注意为了制造异常,这里是错的
            .retrieve()  //获取请求结果
            .bodyToMono(String.class)  //用Mono接收单个非集合对象数据
            .doOnError(Exception.class, err -> {  //处理异常
              System.out.println(LocalDateTime.now() +  "---发生错误:" +err.getMessage() );
            })
            .retry(3);

    System.out.println("=====" + mono.block());
  }

}
  • doOnError异常处理是我们在上一节文章中为大家介绍的异常处理函数,我们在这里打印日志,观察重试次数
  • retry(3)就是重点了,表示请求失败之后重试3次请求。也可以使用retry()无参方法,不设置次数,可以无限重试。这样显然不好,我们一般不用。

下面是doOnError中打印的控制台输出内容,一共打印了4次。(一次失败 + 三次重试失败)

二、重试时间间隔设置

上面的请求重试方法,请求失败之后立即重试,在很短的时间内就完成了3次重试。如果这是在生产环境下,可能你的服务端因为资源紧张造成请求响应超时等异常,这种重试机制无疑会让本就不堪重负的服务端雪上加霜。我们下面交给大家一种为重试设置时间间隔的方法:

.retryBackoff(3, Duration.ofSeconds(5));
  • 第一个参数仍然表示重试3次
  • 第二个参数表示按指数增长的时间间隔重试,第一次重试间隔5秒,第二次间隔10秒(5 x2),第三次间隔20秒(5x2x2)

源码如下:

三、retryWhen方法

上面的retryBackoff方法虽然已经一定程度上缓解了请求重试导致的服务端的压力,但是它还是不分场景的不断重试。

  • 在实际的开发中,可以请求重试的场景应该是:网络异常、请求超时异常、服务端突然面临高并发导致的临时处理能力不足导致的超时等这种由于外部原因导致的异常场景。
  • 对于那些由于程序员编写的bug、资源访问权限不足、资源找不到、HTTP版本不受支持等造成的异常,重试一万次也不会成功,反而可能因为你不断的重试造成服务器崩溃。

所以说Webclient已经在源码中,将retryBackoff()标记为废弃,建议使用retryWhen()代替它。retryWhen()可以指定针对某些异常进行重试,其他异常不做重试。

为了使用retryWhen(),需要引入下面的包

<dependency>
   <groupId>io.projectreactor.addons</groupId>
   <artifactId>reactor-extra</artifactId>
</dependency>

3.1.人为制造超时异常-用于测试

为了能够制造请求超时的异常场景,我们给连接超时设置为5毫秒,即:让所有请求一定会超时。(没有任何请求能在5毫秒内完成网络连接)

//认为设置请求超时时间为5毫秒,也就是请求一定会超时,一定会抛出ConnectTimeoutException
TcpClient tcpClient = TcpClient
        .create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5); //5毫秒

WebClient webClient = WebClient.builder()
        .baseUrl("http://jsonplaceholder.typicode.com")
        .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
        .build();

3.2.测试retryWhen

用Retry对象定义请求重试的条件,也就是retryWhen的when

Retry<?> retry = Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
        .retryMax(3) // 重试3次
        .backoff(Backoff.exponential(Duration.ofSeconds(5),Duration.ofSeconds(60),2,true));

Mono<String> mono = webClient
        .get()    //GET 请求
        .uri("/posts/1")  // 请求路径,这里的请求路径是正确的
        .retrieve()
        .bodyToMono(String.class)
        .retryWhen(retry);   //满足Retry条件进行重试

System.out.println("=====" + mono.block());
  • Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException) 表示只有针对ConnectTimeoutException连接超市异常才进行请求重试,这里使用了java8的Predicate语法
  • Backoff.exponential表示按指数增长的时间间隔进行重试,可以自己指定指数重试因子,即指数的计数。这里我们仍然使用2作为指数重试因子,第一次重试间隔5秒,第二次间隔10秒(5 x2),第三次间隔20秒(5x2x2)
  • 为防止间隔时间指数级无限延长,Backoff.exponential最长的重试间隔不能超过60秒,第二个参数。
  • retryWhen(retry) 满足retry条件进行重试

3.3.retryWhen的其他方法

  • onlyIf()表示捕获到指定的某个异常,进行请求重试
  • allBut()表示除了某个异常之外,其他的异常被捕获则进行请求重试
  • any() 表示针对所有异常,进行请求重试
  • anyOf()表示指定某些异常类型,进行请求重试


backOff表示重试的时间间隔

  • exponential()指数级增长的时间间隔
  • fix()表示固定的时间间隔
喜欢 (0)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址