BackOff重试算法

可以使用斐波那契数列计算重试时间,也可以使用指数补偿计算重试时间。

定义一个接口

@FunctionalInterface
public interface ExponentialBackOffFunction <T> {
	T execute();
}

定义重试工具类

import static java.util.Arrays.asList;

import java.net.SocketTimeoutException;
import java.util.List;

import javax.net.ssl.SSLHandshakeException;

import lombok.extern.log4j.Log4j;

@Log4j
public final class ExponentialBackOff {
	
	private static final int[] FIBONACCI = new int[] { 1, 1, 2, 3, 5, 8, 13 };
	private static final List<Class<? extends Exception>> EXPECTED_COMMUNICATION_ERRORS = asList(
			SSLHandshakeException.class, SocketTimeoutException.class);

	private ExponentialBackOff() {
	}

	// E(c) = Math.pow(2, c) - 1
	// 考虑到补偿时间的均匀分布,补偿时间的数学期望是所有可能性的平均值。
	// 也就是说,在c次冲突之后,补偿时隙数量在 [0,1,...,N] 中,其中 [公式] E(c) = Math.pow(2, c) - 1
	// 则补偿时间的数学期望 E(c) = (Math.pow(2, c) - 1)/2
	public static long getWaitTimeExponential(int attempt) {
		double waitTime = (Math.pow(2, attempt) - 1) / 2;
		return Math.round(waitTime * 100);
	}
	
	// 这个地方使用斐波那契数去增加重试时间
	public static long getWaitTimeDefault(int attempt) {
		return FIBONACCI[attempt] * 100;
	}

	public static <T> T execute(ExponentialBackOffFunction<T> fn) {
		for (int attempt = 0; attempt < FIBONACCI.length; attempt++) {
			try {
				return fn.execute();
			} catch (Exception e) {
				handleFailure(attempt, e);
			}
		}
		throw new RuntimeException("Failed to communicate.");
	}

	private static void handleFailure(int attempt, Exception e) {
		if (e.getCause() != null && !EXPECTED_COMMUNICATION_ERRORS.contains(e.getCause().getClass()))
			throw new RuntimeException(e);
		doWait(attempt);
	}

	private static void doWait(int attempt) {
		try {
			long sleepTime = getWaitTimeExponential(attempt);
			System.out.println("attempt " + attempt + " sleep " + sleepTime);
			Thread.sleep(sleepTime);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}
}

测试案例

public class Test {
	public static void main(String[] args) {
		ExponentialBackOff.execute(new Work());
	}
}

class Work implements ExponentialBackOffFunction<String> {

	@Override
	public String execute() {
		int a = 5 / 0;
		return a + "";
	}
}

运行结果

使用指数补偿:
attempt 0 sleep 0
attempt 1 sleep 50
attempt 2 sleep 150
attempt 3 sleep 350
attempt 4 sleep 750
attempt 5 sleep 1550
attempt 6 sleep 3150
使用斐波那契数列
attempt 0 sleep 100
attempt 1 sleep 100
attempt 2 sleep 200
attempt 3 sleep 300
attempt 4 sleep 500
attempt 5 sleep 800
attempt 6 sleep 1300