网络请求之“重试设计”:退避

重试的场景

重试的定义:我们认为这个故障是暂时的,而不是永久的,所以我们会去重试。

要重试:调用超时,被调用端返回了某种可以重试的错误(如繁忙中,流程中,维护中,资源不足等)

不要重试:业务级别的错误(如没有权限,或者是非法数据等),技术上的错误(如http的500等),这类型的错误重试下去没有意义。

重试的策略:

总原则:有个重试次数的最大值,经过一段时间不断的重试之后,就没有必要再重试了,应该报故障了,这样可以避免因为重试过多而导致网络上的负担更重

  1. No BackOff 无退避算法策略,即当重试时是立即重试
  2. Fixed BackOff 固定时间的退避策略,需设置参数

    1
    2
    3. UniformRandom BackOff 随机时间退避策略,需设置```sleeper```、```minBackOffPeriod```和```maxBackOffPeriod```。该策略在[```minBackOffPeriod```,```maxBackOffPeriod```]之间取一个随机休眠时间,```minBackOffPeriod```默认为500毫秒,```maxBackOffPeriod```默认为1500毫秒
    4. Exponential Backoff 指数级退避策略,每一次重试所需要的休息时间都会翻倍增加。让被调用方能够有更多的时间来从容处理我们的请求,和TCP的拥塞机制有点像。需设置参数```sleeper```、```initialInterval```、```maxInterval```和```multiplier```。```initialInterval```指定初始休眠时间,默认为100毫秒。```maxInterval```指定最大休眠时间,默认为30秒。```multiplier```指定乘数,即下一次休眠时间为当前休眠时间 * ```multiplier

  3. ExponentialRandom BackOff 随机指数退避策略,引入随机乘数,固定乘数可能会引起很多服务同时重试导致DDos,使用随机休眠时间来避免这种情况。

example:

  1. 指数级退避策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //最大延迟时间间隔,单位是毫秒
    private static int MAX_WAIT_INTERVAL = 100*1000;
    //最大重试次数
    private static int MAX_RETRIES = 3;
    //指数退避算法
    public static void doOperationAndWaitForResult(){
    try{
    int retries = 0;
    boolean retry = false;
    do {
    long waitTime = Math.min(getWaitTimeExp(retries), MAX_WAIT_INTERVAL);
    System.out.print("等待时间:" + waitTime + " ms \n");
    //Wait for the result
    Thread.sleep(waitTime);
    //Get the result of the asynchronous operation
    Results results = getAsyncOperationResult();
    if (Results.SUCCESS == results){
    retry = false;
    }else if (Results.NOT_READY == results || Results.THROTTLED == results
    || Results.SERVER_ERROR == results){
    retry = true;
    }else {
    retry = false;
    }
    }while (retry && (retries ++ < MAX_RETRIES));
    }catch (Exception e){
    }
    }
    /**
    * 根据重试的次数,返回2的指数的等待时间
    * @param retryCount
    * @return
    */
    public static long getWaitTimeExp(int retryCount){
    long waitTime = ((long) Math.pow(2, retryCount) * 100L);
    return waitTime;
    }
  2. 随机时间退避策略:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private void avoidTime(int retryTime) {
    int tryTime = RETRY_TIME - retryTime;
    Random random = new Random(System.currentTimeMillis());
    int time = 0;
    switch (tryTime) {
    case 1:
    case 2:
    time = random.nextInt(150) + 50;
    break;
    case 3:
    time = random.nextInt(300) + 200;
    break;
    default:
    time = random.nextInt(150);
    break;
    }
    try {
    Thread.sleep(time);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }