1

curator中如果连接失败或获取数据失败是可以重试的,其中重试有从次数、时间维度重试,这里重点提一下指数补偿重试。相关类:

public class ExponentialBackoffRetry extends SleepingRetry{
    private static final int MAX_RETRY_LIMIT = 29;
    private static final int DEFAULT_MAX_SLEEP_MS = Integer.MAX_VALUE;
    private final Random random = new Random();
    private final  int baseSleepTimeMs;
    private final  int maxSleepMs;
    public ExponentialBackoffRetry(int baseSleepTimeMs,int maxRetries){
        this(baseSleepTimeMs,maxRetries,DEFAULT_MAX_SLEEP_MS);
    }
    public ExponentialBackoffRetry(int baseSleepTimeMs,int maxRetries,int maxSleepMs){
        super(validateMaxRetries(maxRetries));
        this.baseSleepTimeMs = baseSleepTimeMs;
        this.maxSleepMs = maxSleepMs;
    }
    public long getBaseSleepTimeMs(){return baseSleepTimeMs;}


    private static int validateMaxRetries(int maxRetries){
        if(maxRetries > MAX_RETRY_LIMIT){
            maxRetries = MAX_RETRY_LIMIT;
        }
        return maxRetries;
    }
    @Override
    protected long getSleepTimeMs(int retryCount, long elapsedTimeMs) {
        //这里使用了指数补偿或指数退避的思想
        //使用随机,是为了避免出现惊群效应,防止相同时间执行大量操作
        //1 << n -> 2的n次方; n << 1 -> 2n
        long sleepMs = baseSleepTimeMs * Math.max(1,random.nextInt(1 << (retryCount + 1)));
        if(sleepMs > maxSleepMs){
            sleepMs = maxSleepMs;
        }
        return sleepMs;
    }
}

重点在获取休眠时间的方法getSleepTimeMs里,接着看看方法里的retryCount是如何传入的,这里涉及到另一个类:RetryLoopImpl,代码如下:

/**
 * 对RetryLoop的实现,里面有RetryPolicy、RetrySleeper两个重要的属性
 * RetryPolicy类里有通过对异常、重试次数来判断是否再进行重试
 * RetrySleeper线程休眠工具
 */
public class RetryLoopImpl extends RetryLoop {

    private boolean isDone = false;
    private int retryCount = 0;
    private final long startTimeMs = System.currentTimeMillis();
    private final RetryPolicy retryPolicy;
    private static final RetrySleeper sleeper = (time, unit) -> unit.sleep(time);

    RetryLoopImpl(RetryPolicy retryPolicy){
        this.retryPolicy = retryPolicy;
    }

    static RetrySleeper getRetrySleeper(){
        return sleeper;
    }

    @Override
    public boolean shouldContinue() {
        return !isDone;
    }

    @Override
    public void markComplete() {
        isDone = true;
    }

    /**
     * 异常抛出后流程会中断
     * @param exception
     * @throws Exception
     */
    @Override
    public void takeException(Exception exception) throws Exception {
        boolean rethrow =true;
        if(retryPolicy.allowRetry(exception)){//有的异常发生后是可以重试的
            //在sleep一定时间内如果未被中断也可以重试
            if(retryPolicy.allowRetry(retryCount++,System.currentTimeMillis() - startTimeMs,sleeper)){
                rethrow = false;
            }
        }
        //如果上面两个条件都不满足,则抛出异常结束流程
        if(rethrow){
            throw exception;
        }
    }
}

可以看到retryCount在takeException方法中会自增,所以在ExponentialBackoffRetry的getSleepTimeMs方法中通过左移来达到相对指数增长的效果,这里说相对是因为还使用了random来做一个随机,为了避免出现惊群效应。

那重试的入口又在哪里呢?在RetryLoop这个抽象类里,代码如下:
/**
 *有任务需要执行时,并不是直接去执行而是使用RetryLoop里的callWithRetry方法封装后再执行,
 * 这样,如果在执行的过程中发生异常,那么就可以根据异常的种类判断是继续重试还是抛出异常
 */
public abstract class RetryLoop {

    public static <T> T callWithRetry(RetryLoop retryLoop, Callable<T> proc)throws Exception{
        T result = null;
        while (retryLoop.shouldContinue()){
            try {
                result = proc.call();
                retryLoop.markComplete();
            }catch (Exception e){
                retryLoop.takeException(e);
            }
        }
        return result;
    }

    /**
     * 返回true需要继续执行
     * @return
     */
    public abstract boolean shouldContinue();

    /**
     * 任务执行成功后调用这个方法
     */
    public abstract void markComplete();

    /**
     * pass any caught exception here,
     * 对异常进行过滤判断,从而进行重试或抛出异常中断流程
     * @param exception
     */
    public abstract void takeException(Exception exception) throws Exception;

}

我们需要关注callWithRetry方法,方法接受一个重试策略和一个线程体,while条件判断是否继续重试,如果方法执行成功则标记不用重试而结束,如果在重试的过程中发生了异常,我们传入的也是指数补偿重试策略那获取的休眠时间也就是类指数增长了


步履不停
38 声望13 粉丝

好走的都是下坡路