zoukankan      html  css  js  c++  java
  • 【稳定性123】Http Retrying重试机制

    背景:

    项目UI与数据存储系统交互时遇到断连(例如:返回503 Service Unavailable),导致控件响应失灵。为解决处理503以及加强重试机制,引入Guava Retrying。

    参考:

    https://www.jianshu.com/p/2e3cfc509d56

    https://www.jianshu.com/p/a289dde63043

    https://www.jianshu.com/p/557eb67bb3d8

    重试机制适用场景

    对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。

    远程调用超时网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,比如dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败。

    比如外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性

    在很多业务场景中,为了排除系统中的各种不稳定因素,以及逻辑上的错误,并最大概率保证获得预期的结果,重试机制都是必不可少的。尤其是调用远程服务,在高并发场景下,很可能因为服务器响应延迟或者网络原因,造成我们得不到想要的结果,或者根本得不到响应。这个时候,一个优雅的重试调用机制,可以让我们更大概率保证得到预期的响应。


    “提高系统稳定性”
     
     
     

    Spring Retry 为 Spring 应用程序提供了声明性重试支持。 它用于Spring批处理、Spring集成、Apache Hadoop(等等)的Spring。

    在分布式系统中,为了保证数据分布式事务的强一致性,大家在调用RPC接口或者发送MQ时,针对可能会出现网络抖动请求超时情况采取一下重试操作。 大家用的最多的重试方式就是MQ了,但是如果你的项目中没有引入MQ,那就不方便了。

    但是Srping Retry必须是基于异常来进行控制。

    定义参数:
    • 重试条件:遇到 RuntimeException
    • 重试次数:3
    • 重试策略:重试的时候等待 5S, 后面时间依次变为原来的 2 倍数。
    • 熔断机制:全部重试失败,则调用 recover() 方法。
     

    guava-retrying 是Google Guava库的一个扩展包,可以为任意函数调用创建可配置的重试机制。该扩展包比较简单,大约包含了10个方法和类。

    guava-retrying 模块提供了一种通用方法, 可以使用Guava谓词匹配增强的特定停止、重试和异常处理功能来重试任意Java代码。

    Guava retryer有更优的策略定义,在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。

    Guava Retryer也是线程安全的,入口调用逻辑采用的是 java.util.concurrent.Callablecall() 方法。

    guava-retrying默认的阻塞策咯是通过Thread.sleep来实现的,也就是说通过让当前线程休眠来实现阻塞功能,这或许不是一种很好的选择;

    guava-retrying功能强大,基本能满足我们常用的操作;如果不满足当前各种已有的策咯,可以选择分别继承WaitStrategyStopStrategyBlockStrategy来自定义自己的实现;

    在实际使用过程种,我们可能会经常要调整重试次数、重试时间等策咯,所以我们可以将重试策咯的配置进行参数化保存,达到动态调节的目的;另外在使用的时候,也可以封装成util工具类供大家使用。
     
     
     
     1 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
     2         .retryIfException()
     3         .retryIfResult(aBoolean -> Objects.equals(aBoolean, false))
     4         .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(10, TimeUnit.SECONDS, Executors.newCachedThreadPool()))
     5         .withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
     6         .withStopStrategy(StopStrategies.stopAfterAttempt(5))
     7         .withRetryListener(new RetryListener() {
     8             @Override
     9             public <V> void onRetry(Attempt<V> attempt) {
    10                 System.out.print("retry time=" + attempt.getAttemptNumber());
    11             }
    12         }).build();
    13 try {
    14     retryer.call(() -> {
    15         // 逻辑处理
    16         return null;
    17     });
    18 } catch (Exception e) {
    19     System.out.println("exception:" + e);
    20 }

    具体接口及相应的策咯:

    • newBuilder:创建RetryerBuilder对象,通过该类进行构建各种重试策咯;
    • retryIfException:抛出异常时重试,但抛出error不会重试;另外该方法还包含一个重载的方法,可以自定义针对异常的实现;
    • retryIfRuntimeException:见名知义,抛出RuntimeException时重试;
    • retryIfExceptionOfType:抛出指定异常类型时重试;
    • retryIfResult:根据具体的返回值选择重试;
    • withRetryListener:在重试的时候进行事件监听,这中间我们可以记录下错误日志什么的;可以注册多个事件监听器,会按照注册顺序依次调用;
    • withWaitStrategy:重试等待策略,核心策咯之一;
    • withStopStrategy:重试停止策略,核心策咯之一;
    • withBlockStrategy:重试阻塞策略,也就是两次重试的时间间隔的实现方式;
    • withAttemptTimeLimiter:单次任务执行时长限制(如果单次任务执行超时,则终止执行当前任务);
    • build:通过newBuilder构建了各种重试策咯,构建完成,还需要通过build方法借助Retryer来执行;

    主要接口

    序号接口描述备注
    1 Attempt 一次执行任务  
    2 AttemptTimeLimiter 单次任务执行时间限制 如果单次任务执行超时,则终止执行当前任务。NoAttemptTimeLimit,FixedAttemptTimeLimit。
    3 BlockStrategies 任务阻塞策略 通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么),默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY
    4 RetryException 重试异常  
    5 RetryListener 自定义重试监听器 可以用于异步记录错误日志
    6 StopStrategy 停止重试策略 StopAfterDelayStrategy, NeverStopStrategy, StopAfterAttemptStrategy
    7 WaitStrategy 等待时长策略 (控制时间间隔),返回结果为下次执行时长。FixedWaitStrategy,RandomWaitStrategy,IncrementingWaitStrategy,ExponentialWaitStrategy,FibonacciWaitStrategy,ExceptionWaitStrategy,CompositeWaitStrategy。
    8 Attempt 一次执行任务
    9 Attempt 一次执行任务
     

    一个完备的重试实现,要很好地解决如下问题:

    1. 什么条件下重试
    2. 什么条件下停止
    3. 如何停止重试
    4. 停止重试等待多久
    5. 如何等待
    6. 请求时间限制
    7. 如何结束
    8. 如何监听整个重试过程

    并且,为了更好地封装性,重试的实现一般分为两步:

    1. 使用工厂模式构造重试器
    2. 执行重试方法并得到结果

    一个完整的重试流程可以简单示意为:

    graph LR
        A((Start)) -->|build| B(Retryer)
        B --> C{need call?}
        C -->|continue| D[call]
        D --> Z[call count++]
        Z --> C
        C -->|finished| E[result]
        E --> F((success))
        E --> G((failed ))
     
     
    1. 配置依赖
     
    Maven:
    <!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying -->
    <dependency>
        <groupId>com.github.rholder</groupId>
        <artifactId>guava-retrying</artifactId>
        <version>2.0.0</version>
    </dependency>
    Gradle:

    compile("com.github.rholder:guava-retrying:2.0.0") {
      exclude group: 'com.google.guava', module: 'guava'
    }

     
    2.实现Callable
    Callable的call方法中是你自己实际的业务调用。
    Callable<Boolean> callable = new Callable<Boolean>() {
        public Boolean call() throws Exception {
            return true; // do something useful here
        }
    };
     
    3.通过RetryerBuilder构造Retryer
     
    Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
            .retryIfResult(Predicates.<Boolean>isNull())
            .retryIfExceptionOfType(IOException.class)
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .build();
     
     
    4.使用重试器执行你的业务
     
    retryer.call(callable);
     

    下面是完整的参考实现。

     1 public Boolean test() throws Exception {
     2     //定义重试机制
     3     Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
     4             //retryIf 重试条件
     5             .retryIfException()
     6             .retryIfRuntimeException()
     7             .retryIfExceptionOfType(Exception.class)
     8             .retryIfException(Predicates.equalTo(new Exception()))
     9             .retryIfResult(Predicates.equalTo(false))
    10 
    11             //等待策略:每次请求间隔1s
    12             .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
    13 
    14             //停止策略 : 尝试请求6次
    15             .withStopStrategy(StopStrategies.stopAfterAttempt(6))
    16 
    17             //时间限制 : 某次请求不得超过2s , 类似: TimeLimiter timeLimiter = new SimpleTimeLimiter();
    18             .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))
    19 
    20             .build();
    21 
    22     //定义请求实现
    23     Callable<Boolean> callable = new Callable<Boolean>() {
    24         int times = 1;
    25 
    26         @Override
    27         public Boolean call() throws Exception {
    28             log.info("call times={}", times);
    29             times++;
    30 
    31             if (times == 2) {
    32                 throw new NullPointerException();
    33             } else if (times == 3) {
    34                 throw new Exception();
    35             } else if (times == 4) {
    36                 throw new RuntimeException();
    37             } else if (times == 5) {
    38                 return false;
    39             } else {
    40                 return true;
    41             }
    42 
    43         }
    44     };
    45     //利用重试器调用请求
    46    return  retryer.call(callable);
    47 }
     
     
     
    附加:
    HTTP 503
     

    一、什么是HTTP Error 503 ?

    web服务器不能处理HTTP请求,可能是临时超载或者是服务器进行维护。这意味着你需要忍耐一下,等待服务器的临时处理。在这种状态下,一些服务器可以简单的拒绝socket连接,否则会发生内容不一致的错误。

    二、503错误在HTTP周期的流程:

    1、从站点获得IP地址;

    2、通过IP地址打开socket连接;

    3、通过socket连接写入HTTP数据流;

    4、等待响应,返回的数据流。该数据流包含由HTTP协议决定的状态代码值。然后解析数据流状态代码和其它信息。

    三、出现 HTTP Error 503 的原因:

    1、站点遭到攻击,在超过限制时报503错误,待攻击停止就可以恢复了;

    2、站点规模较大,并发请求过多,这种建议修改优化程序或需要升级更高类型主机;

    3、程序有错误,在短时间内产生多次工作进程崩溃,会因IIS7的快速故障防护功能而关闭程序池;

    4、站点提供下载,当带宽超过限制时会报错,需停止下载功能,或者升级主机解决。

     
  • 相关阅读:
    C# FileStream 文件复制
    DataSnap Server 客户端调用 异常
    抛出异常 exception
    RAD Tokyo 10.2.2
    delphi ios info.plist
    sql 日期格式
    sql server 2014登录账号
    sql server导入excel数据
    dev16 cxgrid 在DLL里报0地址错
    sql server 查看所有表记录数
  • 原文地址:https://www.cnblogs.com/cathygx/p/13540415.html
Copyright © 2011-2022 走看看