介绍功能和需求背景
介绍代码上下文,思路,方案。
讲解代码,评委穿插提问。参会的其他同学有问题也可以提。
评委对代码给建议和反馈
你有一个苹果,我有一个苹果,交换一下我们还是只有一个苹果;你有一本书,我有一本书,交换一下我们就有两本书了。每个程序员都有自己的工具集和代码私库,如果大家一起贡献出来,我们就会有一套较完整的工具集,相信可以解决大部分问题。
作为一个程序员,一个工科生,现实中的理想就是有一套趁手的完整的工具套件,家里有点什么事情,只要拿出我的工具套件就能自己轻松搞定。跟现实中一样,我们中间件团队也在默默的打造自己的软件工具套件,也希望更多的同学一起来打造这一套称心的软件工具套件。
流程执行完后会将流程实例调用http接口上报到星云服务端,若星云服务端不可用,不会发起重试,导致流程虽然执行完成,但是在控制台查看不到对应流程实例信息。
- 改造成上报失败按(指数退避算法)策略,在一段时间后重试,确保流程实例能够上传到服务端
- 若上传失败,在尝试若干次后记录日志并发送告警给对应业务负责人
网关应用原先依赖MetaQ,改造升级后内部的重试策略也是继承一致的重试策略,按固定级别(1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h)退避。
发送消息时设置重试策略,目前支持两种重试策略
1、【普通模式】:默认模式,强烈建议用该模式,该模式间隔会逐步递增(5s,10s,30s,60s... 逐渐递增,总周期为7天,总共20次)。
2、【表达式模式】:
距离上次重试的时间间隔 = 首次重试间隔 + 已重试次数 * 重试时间间隔步长 * 首次重试间隔(秒)
在表达式模式的策略下,最大重试、首次重试间隔和步长才会生效。注意普通模式下,以下参数不生效。
调度限流重试策略
主动限流使用随机数,5~15分钟
jobDetail.setRedoIntervalMode(RedoIntervalModeConstant.EXPRESSION);
// 最大重试次数,默认99次
jobDetail.setFailRedoCountMax(10);
// 首次重试间隔(秒)
jobDetail.setFailFirstRedoInterval(5);
// 重试时间间隔步长,如设置成0,则每次重试间隔相同,如设置成1,则间隔是5、10、15...
jobDetail.setFailRedoIntervalStep(0);
根据策略获取下一次重试间隔
根据策略获取指定次数的重试间隔
多种退避算法,如指数退避、固定步长、等差数列、自定义固定级别退避,以及不退避。
可以设置最大重试间隔,如最大重试间隔不能超过1小时
可以设置最大重试次数,如最大重试20次
可以获取当前重试次数,即已经执行了几次
可以获取从开始到当前流逝的全部时间
可以获取从开始到指定次数流逝的全部时间
标准化重试策略和接口,方便延迟调度平台使用
com.github.rholder
guava-retrying
1.0.6
详见: guava-retrying by rholder
org.springframework
spring-core
4.3.17.RELEASE
public interface BackOff {
/**
* Start a new back off execution.
* @return a fresh {@link BackOffExecution} ready to be used
*/
BackOffExecution start();
}
package org.springframework.util.backoff;
/**
* Represent a particular back-off execution.
*
* Implementations do not need to be thread safe.
*
* @author Stephane Nicoll
* @since 4.1
* @see BackOff
*/
public interface BackOffExecution {
/**
* Return value of {@link #nextBackOff()} that indicates that the operation
* should not be retried.
*/
long STOP = -1;
/**
* Return the number of milliseconds to wait before retrying the operation
* or {@link #STOP} ({@value #STOP}) to indicate that no further attempt
* should be made for the operation.
*/
long nextBackOff();
}
package com.xxx.commons.retry.backoff;
/**
* @created 2021-12-20 4:26 PM
* @description:
*/
public interface Backoff {
/**
* 开始
*
* @return
*/
BackoffExecution start();
}
package com.xxx.commons.retry.backoff;
import java.util.concurrent.TimeUnit;
/**
* @created 2021-12-20 4:26 PM
* @description:
*/
public interface BackoffExecution {
/**
* 不再重试时返回的值
*/
long STOP = -1;
/**
* The default time unit
*/
TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
/**
* 下一次退避时间
*
* @return nextBackoff 下一次重试延迟时间(毫秒),不再重试时返回{@link #STOP} ({@value #STOP})
*/
long nextBackoff();
/**
* 根据重试次数获取指定次数的退避时间
*
* @param attempt
* @return
*/
long getBackoff(int attempt);
/**
* 从开始到现在一共经过的时间
*
* @return
*/
long getElapsedTime();
/**
* 从开始到指定次数一共经过的时间
*
* @param attempt
* @return
*/
long getElapsedTime(int attempt);
/**
* 当前尝试次数
*
* @return currentAttempt
*/
int currentAttempt();
}
接口与类 |
功能说明 |
备注说明 |
com.xxx.commons.retry.RetryService |
重试服务工具类 |
|
com.xxx.commons.retry.backoff.Backoff |
退避算法定义接口 |
|
com.xxx.commons.retry.backoff.BackoffExecution |
退避算法执行接口 |
|
com.xxx.commons.retry.backoff.BackoffType |
退避算法类型接口 |
|
com.xxx.commons.retry.backoff.BackoffTypeEnum |
退避算法类型枚举类 |
|
com.xxx.commons.retry.backoff.CustomBackoff |
自定义序列退避算法实现 |
|
com.xxx.commons.retry.backoff.ExponentialBackoff |
指数退避算法实现 |
|
com.xxx.commons.retry.backoff.FixedBackoff |
固定步长退避算法实现 |
|
com.xxx.commons.retry.backoff.GradualBackoff |
等差数列退避算法实现 |
|
com.xxx.commons.retry.backoff.NoneBackoff |
不退避算法实现 |
|
com.xxx.commons.retry.backoff.FibonacciBackoff |
裴波那契退避算法实现 |
暂未实现 |
package com.xxx.commons.retry.backoff;
/**
* @created 2021-12-31 3:24 PM
* @description:
*/
public interface BackoffType {
/**
* 类型编码
*
* @return
*/
int getCode();
/**
* 类型名字
*
* @return
*/
String getName();
/**
* 类型说明
*
* @return
*/
String getDesc();
}
package com.xxx.commons.retry.backoff;
/**
* 重试补偿步长类型
*
* @created 2021-12-20 4:29 PM
* @description:
*/
public enum BackoffTypeEnum implements BackoffType {
/**
* 无补偿
**/
NONE(0, "none", "无补偿"),
/**
* 固定步长
**/
FIXED(1, "fixed", "固定步长"),
/**
* 等差步长
*/
GRADUAL(2, "gradual", "等差步长"),
/**
* 指数退避
**/
EXPONENTIAL(3, "exponential", "指数退避"),
/**
* 自定义步长
*/
CUSTOM(4, "custom", "自定义步长"),
;
private int code;
private String name;
private String desc;
BackoffTypeEnum(int code, String name, String desc) {
this.code = code;
this.name = name;
this.desc = desc;
}
public static BackoffTypeEnum typeOf(int code) {
for (BackoffTypeEnum type : BackoffTypeEnum.values()) {
if (type.code == code) {
return type;
}
}
return null;
}
@Override
public int getCode() {
return code;
}
@Override
public String getName() {
return name;
}
@Override
public String getDesc() {
return desc;
}
}
package com.xxx.commons.retry;
/**
* @created 2021-12-31 3:21 PM
* @description:
*/
import com.xxx.commons.retry.backoff.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
public class RetryService {
static {
Runtime.getRuntime().addShutdownHook(RetryServiceShutdownHook.instance());
}
/**
* 重试线程池大小
*/
public static final int RETRY_CORE_POOL_SIZE = Integer.getInteger(
RetryConstants.DEFAULT_RETRY_SERVICE_SCHEDULED_THREAD_POOL_SIZE_KEY,
RetryConstants.DEFAULT_RETRY_SERVICE_SCHEDULED_THREAD_POOL_DEFAULT_SIZE);
/**
* 重试服务线程池
*/
final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(RETRY_CORE_POOL_SIZE);
private static final RetryService instance = new RetryService();
public static RetryService getInstance() {
return instance;
}
private RetryService() {
RetryServiceShutdownHook.instance().addExecutor(executor);
}
public ScheduledFuture> retry(Callable callable, long delay) {
return executor.schedule(callable, delay, BackoffExecution.DEFAULT_TIME_UNIT);
}
public ScheduledFuture> retry(Runnable runnable, long delay) {
return executor.schedule(runnable, delay, BackoffExecution.DEFAULT_TIME_UNIT);
}
public ScheduledFuture> retry(Callable callable, long delay, TimeUnit unit) {
return executor.schedule(callable, delay, unit);
}
public ScheduledFuture> retry(Runnable runnable, long delay, TimeUnit unit) {
return executor.schedule(runnable, delay, unit);
}
public interface RetryConstants {
String DEFAULT_RETRY_SERVICE_SCHEDULED_THREAD_POOL_SIZE_KEY = "default.retry.service.scheduled.thread.pool.size";
int DEFAULT_RETRY_SERVICE_SCHEDULED_THREAD_POOL_DEFAULT_SIZE = 8;
}
static class RetryServiceShutdownHook extends Thread {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private List executors = Collections.synchronizedList(new ArrayList());
private final static RetryServiceShutdownHook instance = new RetryServiceShutdownHook();
public static RetryServiceShutdownHook instance() {
return instance;
}
private RetryServiceShutdownHook() {
}
public RetryServiceShutdownHook addExecutor(ExecutorService executor) {
executors.add(executor);
return this;
}
@Override
public void run() {
logger.info("retry service shutdown");
for (int i = 0; i < executors.size(); i++) {
ExecutorService executorService = executors.get(i);
try {
if (!executorService.isShutdown()) {
executorService.shutdown();
}
} catch (Exception e) {
logger.error("retry service shutdown executor service error", e.getMessage());
}
}
}
}
}
package com.xxx.commons.retry.backoff;
import com.xxx.commons.data.constant.CommonConstants;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @created 2021-12-27 9:47 AM
* @description: 自定义序列退避算法实现
*/
public class CustomBackoff implements Backoff, Serializable {
private static final long serialVersionUID = 5677908306014056187L;
public static final String DEFAULT_SEPARATOR = CommonConstants.SPACE_SEPARATOR;
public static final String LEFT_BRACKET = "{";
public static final String RIGHT_BRACKET = "}";
/**
* Constant value indicating an unlimited number of attempt.
*/
public static final int UNLIMITED_ATTEMPTS = Integer.MAX_VALUE;
/**
* 默认退避时间序列
* 示例1:1 2 3 10 10 10 30 60 120 180 600 1800 3600 3600 3600 3600 3600 3600
* 示例2:1s 2s 3s 10s 10s 10s 30s 1m 2m 3m 10m 30m 1h 1h 1h 1h 1h 1h
* 示例3:1 2 3 10{3} 30 60 120 180 600 1800 3600{6}
* 示例4:1s 2s 3s 10s{3} 30s 1m 2m 3m 10m 30m 1h{6}
*/
public static final String DEFAULT_SEQUENCE = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h{64}";
/**
* 自定义退避序列
*/
private String sequence = DEFAULT_SEQUENCE;
/**
* 最大尝试次数
*/
private int maxAttempt = UNLIMITED_ATTEMPTS;
/**
* 自定义序列分隔符
*/
private String separator = DEFAULT_SEPARATOR;
/**
* Create an instance with an sequence with DEFAULT.
*/
public CustomBackoff() {
}
/**
* Create an instance.
*
* @param sequence the origin sequence
*/
public CustomBackoff(String sequence) {
this.sequence = sequence;
}
public CustomBackoff(String sequence, String separator) {
this.sequence = sequence;
this.separator = separator;
}
public CustomBackoff(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
public CustomBackoff(String sequence, int maxAttempt) {
this.sequence = sequence;
this.maxAttempt = maxAttempt;
}
/**
* @param sequence the origin sequence
* @param maxAttempt the maximum number of attempt
* @param separator the sequence's separator
*/
public CustomBackoff(String sequence, int maxAttempt, String separator) {
this.sequence = sequence;
this.maxAttempt = maxAttempt;
this.separator = separator;
}
/**
* Set the maximum number of attempt in milliseconds.
*/
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Return the maximum number of attempt in milliseconds.
*/
public int getMaxAttempt() {
return maxAttempt;
}
public String getSeparator() {
return separator;
}
public void setSeparator(String separator) {
this.separator = separator;
}
@Override
public BackoffExecution start() {
return new CustomBackoffExecution(this.sequence, this.maxAttempt, this.separator);
}
public static class CustomBackoffExecution implements BackoffExecution, Serializable {
private static final long serialVersionUID = 2991013977130023823L;
/**
* 自定义退避序列
*/
private String sequence;
/**
* 最大尝试次数
*/
private int maxAttempt;
/**
* 自定义序列分隔符
*/
private String separator;
/**
* 解析后的退避时间间隔列表
*/
private List intervals;
private long currentElapsedTime = 0;
private int currentAttempt = 0;
public CustomBackoffExecution() {
this(DEFAULT_SEQUENCE, UNLIMITED_ATTEMPTS, DEFAULT_SEPARATOR);
}
public CustomBackoffExecution(String sequence, int maxAttempt, String separator) {
this.sequence = sequence;
this.maxAttempt = maxAttempt;
this.separator = separator;
this.parse();
}
private void parse() {
intervals = new ArrayList<>();
HashMap timeUnitTable = new HashMap();
timeUnitTable.put("s", 1000L);
timeUnitTable.put("m", 1000L * 60);
timeUnitTable.put("h", 1000L * 60 * 60);
timeUnitTable.put("d", 1000L * 60 * 60 * 24);
String[] fields = sequence.split(separator);
boolean completed = false;
for (String field : fields) {
field = field.trim();
int count = 1;
long multiple = 1;
int leftBracket = field.indexOf(CustomBackoff.LEFT_BRACKET);
int rightBracket = field.indexOf(CustomBackoff.RIGHT_BRACKET);
if (leftBracket > 0 && rightBracket > 0) {
count = Integer.parseInt(field.substring(leftBracket + 1, rightBracket));
field = field.substring(0, leftBracket);
}
Long unit = timeUnitTable.get(String.valueOf(field.charAt(field.length() - 1)));
if (unit != null) {
multiple = unit.longValue();
field = field.substring(0, field.length() - 1);
}
long timeInMills = Long.parseLong(field) * multiple;
for (int i = 0; i < count; i++) {
if (this.intervals.size() < this.maxAttempt) {
this.intervals.add(timeInMills);
} else {
completed = true;
}
}
if (completed) {
break;
}
}
// if maxAttempt greater than intervals's size, then set intervals's size as maxAttempt
if (this.intervals.size() < this.maxAttempt) {
this.maxAttempt = this.intervals.size();
}
}
@Override
public int currentAttempt() {
return currentAttempt;
}
@Override
public long getElapsedTime() {
return this.currentElapsedTime;
}
@Override
public long nextBackoff() {
if (currentAttempt >= maxAttempt) {
return STOP;
}
if (currentAttempt >= this.intervals.size()) {
return STOP;
}
long nextInterval = this.intervals.get(this.currentAttempt);
this.currentElapsedTime += nextInterval;
this.currentAttempt += 1;
return nextInterval;
}
@Override
public void updateCurrentAttempt(int attempt) {
if (attempt < 0) {
this.currentAttempt = 0;
} else if (attempt >= maxAttempt) {
this.currentAttempt = maxAttempt;
} else {
this.currentAttempt = attempt;
}
this.currentElapsedTime = this.getElapsedTime(this.currentAttempt);
}
@Override
public long getBackoff(int attempt) {
if (attempt <= 0) {
return 0L;
}
if (attempt - 1 >= maxAttempt) {
return STOP;
}
if (attempt > this.intervals.size()) {
return STOP;
}
return this.intervals.get(attempt - 1);
}
@Override
public long getElapsedTime(int attempt) {
if (attempt >= maxAttempt) {
attempt = maxAttempt;
}
if (attempt >= this.intervals.size()) {
attempt = this.intervals.size();
}
long elapsedTime = 0;
for (int i = 0; i < attempt; i++) {
elapsedTime += this.intervals.get(i);
}
return elapsedTime;
}
public String getSequence() {
return sequence;
}
public void setSequence(String sequence) {
this.sequence = sequence;
}
@Override
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
public String getSeparator() {
return separator;
}
public void setSeparator(String separator) {
this.separator = separator;
}
public List getIntervals() {
return intervals;
}
public void setIntervals(List intervals) {
this.intervals = intervals;
}
public long getCurrentElapsedTime() {
return currentElapsedTime;
}
public void setCurrentElapsedTime(long currentElapsedTime) {
this.currentElapsedTime = currentElapsedTime;
}
public int getCurrentAttempt() {
return currentAttempt;
}
public void setCurrentAttempt(int currentAttempt) {
this.currentAttempt = currentAttempt;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("CustomBackoff{");
sb.append("intervals=").append(intervals);
String attemptValue = (maxAttempt == Integer.MAX_VALUE ?
"unlimited" : String.valueOf(maxAttempt));
sb.append(", maxAttempt=").append(attemptValue);
sb.append(", currentElapsedTime=").append(currentElapsedTime);
sb.append(", currentAttempt=").append(this.currentAttempt);
sb.append('}');
return sb.toString();
}
}
public static void main(String[] args) {
BackoffExecution backoffExecution = new CustomBackoff().start();
System.out.println(backoffExecution.getBackoff(1));
System.out.println(backoffExecution.getBackoff(2));
System.out.println(backoffExecution.getBackoff(3));
System.out.println(backoffExecution.getElapsedTime(1));
System.out.println(backoffExecution.getElapsedTime(2));
System.out.println(backoffExecution.getElapsedTime(3));
}
}
package com.xxx.commons.retry.backoff;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
/**
* @created 2021-12-20 4:31 PM
* @description:
*/
public class ExponentialBackoff implements Backoff, Serializable {
private static final long serialVersionUID = -3955367506662250971L;
/**
* The default initial interval.
*/
public static final long DEFAULT_INITIAL_INTERVAL = 2000L;
/**
* The default multiplier (increases the interval by 50%).
*/
public static final double DEFAULT_MULTIPLIER = 2;
/**
* The default maximum back off time.
*/
public static final long DEFAULT_MAX_INTERVAL = 10 * 60 * 1000L;
/**
* Constant value indicating an unlimited number of attempt.
*/
public static final int UNLIMITED_ATTEMPTS = Integer.MAX_VALUE;
private long initialInterval = DEFAULT_INITIAL_INTERVAL;
private double multiplier = DEFAULT_MULTIPLIER;
private long maxInterval = DEFAULT_MAX_INTERVAL;
private int maxAttempt = UNLIMITED_ATTEMPTS;
/**
* Create an instance with the default settings.
*
* @see #DEFAULT_INITIAL_INTERVAL
* @see #DEFAULT_MULTIPLIER
* @see #DEFAULT_MAX_INTERVAL
*/
public ExponentialBackoff() {
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
*/
public ExponentialBackoff(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Create an instance with the supplied settings.
*
* @param initialInterval the initial interval in milliseconds
*/
public ExponentialBackoff(long initialInterval) {
this.initialInterval = initialInterval;
this.checkInterval(this.initialInterval, this.maxInterval);
}
/**
* Create an instance with the supplied settings.
*
* @param initialInterval the initial interval
* @param timeUnit the initial interval's time unit
* @param multiplier the multiplier (should be greater than or equal to 1)
*/
public ExponentialBackoff(long initialInterval, TimeUnit timeUnit, double multiplier) {
checkMultiplier(multiplier);
this.multiplier = multiplier;
this.initialInterval = BackoffExecution.DEFAULT_TIME_UNIT.convert(initialInterval, timeUnit);
this.checkInterval(this.initialInterval, this.maxInterval);
}
public ExponentialBackoff(long initialInterval, long maxInterval, TimeUnit timeUnit, double multiplier) {
checkMultiplier(multiplier);
this.multiplier = multiplier;
this.initialInterval = BackoffExecution.DEFAULT_TIME_UNIT.convert(initialInterval, timeUnit);
this.maxInterval = BackoffExecution.DEFAULT_TIME_UNIT.convert(maxInterval, timeUnit);
this.checkInterval(this.initialInterval, this.maxInterval);
}
/**
* The initial interval in milliseconds.
*/
public void setInitialInterval(long initialInterval) {
this.initialInterval = initialInterval;
this.checkInterval(this.initialInterval, this.maxInterval);
}
/**
* Return the initial interval in milliseconds.
*/
public long getInitialInterval() {
return initialInterval;
}
/**
* The value to multiply the current interval by for each retry attempt.
*/
public void setMultiplier(double multiplier) {
checkMultiplier(multiplier);
this.multiplier = multiplier;
}
/**
* Return the value to multiply the current interval by for each retry attempt.
*/
public double getMultiplier() {
return multiplier;
}
/**
* The maximum back off time.
*/
public void setMaxInterval(long maxInterval) {
this.maxInterval = maxInterval;
}
/**
* Return the maximum back off time.
*/
public long getMaxInterval() {
return maxInterval;
}
/**
* Set the maximum number of attempt in milliseconds.
*/
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Return the maximum number of attempt in milliseconds.
*/
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public BackoffExecution start() {
return new ExponentialBackoffExecution(initialInterval, maxInterval, multiplier, maxAttempt);
}
private void checkMultiplier(double multiplier) {
if (multiplier < 1) {
throw new IllegalArgumentException("Invalid multiplier '" + multiplier + "'. Should be equal" +
"or higher than 1. A multiplier of 1 is equivalent to a fixed interval");
}
}
private void checkInterval(long initialInterval, long maxInterval) {
if (initialInterval <= 0) {
throw new IllegalArgumentException("Invalid initialInterval '" + initialInterval + "'. Should be higher" +
" than 0. A initialInterval of 0 is equivalent to no interval");
}
if (maxInterval <= initialInterval * getMultiplier()) {
throw new IllegalArgumentException("Invalid maxInterval '" + maxInterval + "'. Should be higher" +
" than initialInterval * multiplier. A maxInterval equal or less than initialInterval * multiplier " +
"is equivalent to a fixed interval");
}
}
public static class ExponentialBackoffExecution implements BackoffExecution, Serializable {
private static final long serialVersionUID = 672748521748221970L;
private long initialInterval;
private double multiplier;
private long maxInterval;
private int maxAttempt;
private long currentInterval = -1;
private long currentElapsedTime = 0;
private int currentAttempt = 0;
public ExponentialBackoffExecution() {
this(DEFAULT_INITIAL_INTERVAL, DEFAULT_MAX_INTERVAL, DEFAULT_MULTIPLIER, UNLIMITED_ATTEMPTS);
}
public ExponentialBackoffExecution(long initialInterval, long maxInterval, double multiplier, int maxAttempt) {
this.initialInterval = initialInterval;
this.maxInterval = maxInterval;
this.multiplier = multiplier;
this.maxAttempt = maxAttempt;
}
@Override
public int currentAttempt() {
return currentAttempt;
}
@Override
public long getElapsedTime() {
return this.currentElapsedTime;
}
@Override
public long nextBackoff() {
if (currentAttempt >= maxAttempt) {
return STOP;
}
long nextInterval = computeNextInterval();
this.currentElapsedTime += nextInterval;
this.currentAttempt += 1;
return nextInterval;
}
private long computeNextInterval() {
long maxInterval = this.maxInterval;
if (this.currentInterval >= maxInterval) {
return maxInterval;
} else if (this.currentInterval < 0) {
this.currentInterval = this.initialInterval;
} else {
this.currentInterval = multiplyInterval(this.currentInterval, maxInterval);
}
return this.currentInterval;
}
private long multiplyInterval(long currentInterval, long maxInterval) {
long interval = currentInterval;
interval *= this.multiplier;
return (interval > maxInterval ? maxInterval : interval);
}
@Override
public void updateCurrentAttempt(int attempt) {
if (attempt < 0) {
this.currentAttempt = 0;
} else if (attempt >= maxAttempt) {
this.currentAttempt = maxAttempt;
} else {
this.currentAttempt = attempt;
}
this.currentElapsedTime = this.getElapsedTime(this.currentAttempt);
}
@Override
public long getBackoff(int attempt) {
if (attempt <= 0) {
return 0L;
}
if (attempt - 1 >= maxAttempt) {
return STOP;
}
long interval = this.initialInterval;
long maxInterval = this.maxInterval;
for (long i = 1; i < attempt; i++) {
interval = multiplyInterval(interval, maxInterval);
if (interval >= maxInterval) {
break;
}
}
return interval;
}
@Override
public long getElapsedTime(int attempt) {
if (attempt >= maxAttempt) {
attempt = maxAttempt;
}
long interval = this.initialInterval;
long maxInterval = this.maxInterval;
long elapsedTime = 0;
if (attempt <= 0) {
return elapsedTime;
} else {
elapsedTime = interval;
}
for (long i = 1; i < attempt; i++) {
if (interval < maxInterval) {
interval = multiplyInterval(interval, maxInterval);
}
elapsedTime += interval;
}
return elapsedTime;
}
public long getInitialInterval() {
return initialInterval;
}
public void setInitialInterval(long initialInterval) {
this.initialInterval = initialInterval;
}
public double getMultiplier() {
return multiplier;
}
public void setMultiplier(double multiplier) {
this.multiplier = multiplier;
}
public long getMaxInterval() {
return maxInterval;
}
public void setMaxInterval(long maxInterval) {
this.maxInterval = maxInterval;
}
@Override
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
public long getCurrentInterval() {
return currentInterval;
}
public void setCurrentInterval(long currentInterval) {
this.currentInterval = currentInterval;
}
public long getCurrentElapsedTime() {
return currentElapsedTime;
}
public void setCurrentElapsedTime(long currentElapsedTime) {
this.currentElapsedTime = currentElapsedTime;
}
public int getCurrentAttempt() {
return currentAttempt;
}
public void setCurrentAttempt(int currentAttempt) {
this.currentAttempt = currentAttempt;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ExponentialBackOff{");
sb.append("currentInterval=").append(this.currentInterval < 0 ? "n/a" : this.currentInterval + "ms");
sb.append(", multiplier=").append(this.multiplier);
sb.append('}');
return sb.toString();
}
}
}
package com.xxx.commons.retry.backoff;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
/**
* @created 2021-12-20 4:32 PM
* @description:
*/
public class FixedBackoff implements Backoff, Serializable {
private static final long serialVersionUID = 2511140811757045231L;
/**
* The default recovery interval: 5000 ms = 5 seconds.
*/
public static final long DEFAULT_INTERVAL = 5000L;
/**
* Constant value indicating an unlimited number of attempt.
*/
public static final int UNLIMITED_ATTEMPTS = Integer.MAX_VALUE;
private long interval = DEFAULT_INTERVAL;
private int maxAttempt = UNLIMITED_ATTEMPTS;
/**
* Create an instance with an interval of {@value #DEFAULT_INTERVAL}
* ms and an unlimited number of attempt.
*/
public FixedBackoff() {
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
*/
public FixedBackoff(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Create an instance.
*
* @param interval the interval between two attempt
* @param timeUnit the time unit of interval
*/
public FixedBackoff(long interval, TimeUnit timeUnit) {
this.interval = BackoffExecution.DEFAULT_TIME_UNIT.convert(interval, timeUnit);
this.maxAttempt = maxAttempt;
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
* @param interval the interval between two attempt
*/
public FixedBackoff(int maxAttempt, long interval) {
this.interval = interval;
this.maxAttempt = maxAttempt;
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
* @param interval the interval between two attempt
* @param timeUnit the time unit of interval
*/
public FixedBackoff(int maxAttempt, long interval, TimeUnit timeUnit) {
this.interval = BackoffExecution.DEFAULT_TIME_UNIT.convert(interval, timeUnit);
this.maxAttempt = maxAttempt;
}
/**
* Set the interval between two attempt in milliseconds.
*/
public void setInterval(long interval) {
this.interval = interval;
}
/**
* Return the interval between two attempt in milliseconds.
*/
public long getInterval() {
return interval;
}
/**
* Set the maximum number of attempt in milliseconds.
*/
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Return the maximum number of attempt in milliseconds.
*/
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public BackoffExecution start() {
return new FixedBackoffExecution(interval, maxAttempt);
}
public static class FixedBackoffExecution implements BackoffExecution, Serializable {
private static final long serialVersionUID = 1656175761226107038L;
private long interval;
private int maxAttempt;
private int currentAttempt = 0;
private long currentElapsedTime = 0;
public FixedBackoffExecution() {
this(DEFAULT_INTERVAL, UNLIMITED_ATTEMPTS);
}
public FixedBackoffExecution(long interval, int maxAttempt) {
this.interval = interval;
this.maxAttempt = maxAttempt;
}
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
@Override
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
public int getCurrentAttempt() {
return currentAttempt;
}
public void setCurrentAttempt(int currentAttempt) {
this.currentAttempt = currentAttempt;
}
public long getCurrentElapsedTime() {
return currentElapsedTime;
}
public void setCurrentElapsedTime(long currentElapsedTime) {
this.currentElapsedTime = currentElapsedTime;
}
@Override
public int currentAttempt() {
return currentAttempt;
}
@Override
public long getElapsedTime() {
return this.currentElapsedTime;
}
@Override
public long nextBackoff() {
if (this.currentAttempt < this.maxAttempt) {
long nextInterval = this.interval;
this.currentElapsedTime += nextInterval;
this.currentAttempt++;
return nextInterval;
} else {
return STOP;
}
}
@Override
public void updateCurrentAttempt(int attempt) {
if (attempt < 0) {
this.currentAttempt = 0;
} else if (attempt >= maxAttempt) {
this.currentAttempt = maxAttempt;
} else {
this.currentAttempt = attempt;
}
this.currentElapsedTime = this.getElapsedTime(this.currentAttempt);
}
@Override
public long getBackoff(int attempt) {
if (attempt <= 0) {
return 0L;
}
if (attempt - 1 >= maxAttempt) {
return STOP;
}
return this.interval;
}
@Override
public long getElapsedTime(int attempt) {
return this.interval * attempt;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("FixedBackoff{");
sb.append("interval=").append(this.interval);
String attemptValue = (this.maxAttempt == Integer.MAX_VALUE ?
"unlimited" : String.valueOf(this.maxAttempt));
sb.append(", maxAttempt=").append(attemptValue);
sb.append(", currentAttempt=").append(this.currentAttempt);
sb.append('}');
return sb.toString();
}
}
}
package com.xxx.commons.retry.backoff;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import static com.xxx.commons.retry.backoff.BackoffExecution.DEFAULT_TIME_UNIT;
/**
* @created 2021-12-20 4:33 PM
* @description:
*/
public class GradualBackoff implements Backoff, Serializable {
private static final long serialVersionUID = -4082336395861849848L;
/**
* The default initial interval.
*/
public static final long DEFAULT_INITIAL_INTERVAL = 2000L;
/**
* The default recovery interval: 3000 ms = 3 seconds.
*/
public static final long DEFAULT_INTERVAL = 3000L;
/**
* Constant value indicating an unlimited number of attempt.
*/
public static final int UNLIMITED_ATTEMPTS = Integer.MAX_VALUE;
private long initialInterval = DEFAULT_INITIAL_INTERVAL;
private long interval = DEFAULT_INTERVAL;
private int maxAttempt = UNLIMITED_ATTEMPTS;
/**
* Create an instance with an interval of {@value #DEFAULT_INTERVAL}
* ms and an unlimited number of attempt.
*/
public GradualBackoff() {
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
*/
public GradualBackoff(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Create an instance.
*
* @param interval the interval between two attempt
* @param timeUnit the time unit of interval
*/
public GradualBackoff(long initialInterval, long interval, TimeUnit timeUnit) {
this.initialInterval = DEFAULT_TIME_UNIT.convert(initialInterval, timeUnit);
this.interval = DEFAULT_TIME_UNIT.convert(interval, timeUnit);
this.maxAttempt = maxAttempt;
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
* @param interval the interval between two attempt
*/
public GradualBackoff(int maxAttempt, long initialInterval, long interval) {
this.initialInterval = initialInterval;
this.interval = interval;
this.maxAttempt = maxAttempt;
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
* @param interval the interval between two attempt
* @param timeUnit the time unit of interval
*/
public GradualBackoff(int maxAttempt, long initialInterval, long interval, TimeUnit timeUnit) {
this.initialInterval = DEFAULT_TIME_UNIT.convert(initialInterval, timeUnit);
this.interval = DEFAULT_TIME_UNIT.convert(interval, timeUnit);
this.maxAttempt = maxAttempt;
}
/**
* Return the interval of first attempt in milliseconds.
*
* @return
*/
public long getInitialInterval() {
return initialInterval;
}
/**
* Set the interval of first attempt in milliseconds.
*/
public void setInitialInterval(long initialInterval) {
this.initialInterval = initialInterval;
}
/**
* Set the interval between two attempt in milliseconds.
*/
public void setInterval(long interval) {
this.interval = interval;
}
/**
* Return the interval between two attempt in milliseconds.
*/
public long getInterval() {
return interval;
}
/**
* Set the maximum number of attempt in milliseconds.
*/
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Return the maximum number of attempt in milliseconds.
*/
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public BackoffExecution start() {
return new GradualBackoffExecution(initialInterval, interval, maxAttempt);
}
public static class GradualBackoffExecution implements BackoffExecution, Serializable {
private static final long serialVersionUID = 6691539566098442985L;
private long initialInterval;
private long interval;
private int maxAttempt;
private int currentAttempt = 0;
private long currentElapsedTime = 0;
public GradualBackoffExecution() {
this(DEFAULT_INITIAL_INTERVAL, DEFAULT_INTERVAL, UNLIMITED_ATTEMPTS);
}
public GradualBackoffExecution(long initialInterval, long interval, int maxAttempt) {
this.initialInterval = initialInterval;
this.interval = interval;
this.maxAttempt = maxAttempt;
}
@Override
public int currentAttempt() {
return currentAttempt;
}
@Override
public long getElapsedTime() {
return this.currentElapsedTime;
}
@Override
public long nextBackoff() {
if (this.currentAttempt < this.maxAttempt) {
long nextInterval = this.currentAttempt == 0 ? this.initialInterval : this.interval;
this.currentElapsedTime += nextInterval;
this.currentAttempt++;
return nextInterval;
} else {
return STOP;
}
}
@Override
public void updateCurrentAttempt(int attempt) {
if (attempt < 0) {
this.currentAttempt = 0;
} else if (attempt >= maxAttempt) {
this.currentAttempt = maxAttempt;
} else {
this.currentAttempt = attempt;
}
this.currentElapsedTime = this.getElapsedTime(this.currentAttempt);
}
@Override
public long getBackoff(int attempt) {
if (attempt <= 0) {
return 0L;
}
if (attempt - 1 >= maxAttempt) {
return STOP;
}
return attempt == 1 ? this.initialInterval : this.interval;
}
@Override
public long getElapsedTime(int attempt) {
return attempt == 1 ? this.initialInterval : this.initialInterval + this.interval * (attempt - 1);
}
public long getInitialInterval() {
return initialInterval;
}
public void setInitialInterval(long initialInterval) {
this.initialInterval = initialInterval;
}
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
@Override
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
public int getCurrentAttempt() {
return currentAttempt;
}
public void setCurrentAttempt(int currentAttempt) {
this.currentAttempt = currentAttempt;
}
public long getCurrentElapsedTime() {
return currentElapsedTime;
}
public void setCurrentElapsedTime(long currentElapsedTime) {
this.currentElapsedTime = currentElapsedTime;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("GradualBackoff{");
sb.append("initialInterval=").append(this.initialInterval);
sb.append("interval=").append(this.interval);
String attemptValue = (this.maxAttempt == Integer.MAX_VALUE ?
"unlimited" : String.valueOf(this.maxAttempt));
sb.append(", maxAttempt=").append(attemptValue);
sb.append(", currentAttempt=").append(this.currentAttempt);
sb.append('}');
return sb.toString();
}
}
}
package com.xxx.commons.retry.backoff;
import java.io.Serializable;
/**
* @created 2021-12-20 4:34 PM
* @description:
*/
public class NoneBackoff implements Backoff, Serializable {
private static final long serialVersionUID = 8227959838869099892L;
/**
* The default recovery interval: 0 ms = 0 seconds.
*/
public static final long DEFAULT_INTERVAL = 0L;
/**
* Constant value indicating an unlimited number of attempt.
*/
public static final int UNLIMITED_ATTEMPTS = Integer.MAX_VALUE;
private long interval = DEFAULT_INTERVAL;
private int maxAttempt = UNLIMITED_ATTEMPTS;
/**
* Create an instance with an interval of {@value #DEFAULT_INTERVAL}
* ms and an unlimited number of attempt.
*/
public NoneBackoff() {
}
/**
* Create an instance.
*
* @param maxAttempt the maximum number of attempt
*/
public NoneBackoff(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
/**
* Return the interval between two attempt in milliseconds.
*/
public long getInterval() {
return interval;
}
/**
* Return the maximum number of attempt in milliseconds.
*/
public int getMaxAttempt() {
return maxAttempt;
}
/**
* Set the maximum number of attempt in milliseconds.
*/
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
@Override
public BackoffExecution start() {
return new NoneBackoffExecution(interval, maxAttempt);
}
public static class NoneBackoffExecution implements BackoffExecution, Serializable {
private static final long serialVersionUID = 2453613900451888865L;
private long interval;
private int maxAttempt;
private int currentAttempt = 0;
private long currentElapsedTime = 0;
public NoneBackoffExecution() {
this(DEFAULT_INTERVAL, UNLIMITED_ATTEMPTS);
}
public NoneBackoffExecution(long interval, int maxAttempt) {
this.interval = interval;
this.maxAttempt = maxAttempt;
}
@Override
public int currentAttempt() {
return currentAttempt;
}
@Override
public long getElapsedTime() {
return this.currentElapsedTime;
}
@Override
public long nextBackoff() {
if (this.currentAttempt < this.maxAttempt) {
long nextInterval = this.interval;
this.currentElapsedTime += nextInterval;
this.currentAttempt++;
return nextInterval;
} else {
return STOP;
}
}
@Override
public void updateCurrentAttempt(int attempt) {
if (attempt < 0) {
this.currentAttempt = 0;
} else if (attempt >= maxAttempt) {
this.currentAttempt = maxAttempt;
} else {
this.currentAttempt = attempt;
}
this.currentElapsedTime = this.getElapsedTime(this.currentAttempt);
}
@Override
public long getBackoff(int attempt) {
if (attempt <= 0) {
return 0L;
}
if (attempt - 1 >= maxAttempt) {
return STOP;
}
return this.interval;
}
@Override
public long getElapsedTime(int attempt) {
return this.interval * attempt;
}
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
@Override
public int getMaxAttempt() {
return maxAttempt;
}
@Override
public void setMaxAttempt(int maxAttempt) {
this.maxAttempt = maxAttempt;
}
public int getCurrentAttempt() {
return currentAttempt;
}
public void setCurrentAttempt(int currentAttempt) {
this.currentAttempt = currentAttempt;
}
public long getCurrentElapsedTime() {
return currentElapsedTime;
}
public void setCurrentElapsedTime(long currentElapsedTime) {
this.currentElapsedTime = currentElapsedTime;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("NoneBackoff{");
sb.append("interval=").append(this.interval);
sb.append(", maxAttempt=").append(this.maxAttempt);
sb.append('}');
return sb.toString();
}
}
}
网关使用示例:
添加依赖
com.xxx.arch
xxx-commons-recipes
1.0.8
初始化
@Configuration
@AutoConfigureAfter(OnsConfig.class)
public class LinkBean {
public static final String MESSAGE_DELAY_LEVEL = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
@Bean
public BackoffExecution backoffExecution() {
return new CustomBackoff(MESSAGE_DELAY_LEVEL, MAX_RETRY_COUNT).start();
}
...
}
具体使用
@Slf4j
public class RetryFilterFactory implements FilterFactory {
private Producer producer;
private BackoffExecution backoffExecution;
public RetryFilterFactory(Producer producer, BackoffExecution backoffExecution) {
this.producer = producer;
this.backoffExecution = backoffExecution;
}
@Override
public String name() {
return "retry";
}
@Override
public Filter apply(Properties properties) {
return (context, chain) -> {
int retry = context.getRetryCount();
retry++;
long retryTime = context.getAttributeOrDefault(LinkContext.FAILOVER_RETRY_TIME_ATTRIBUTE, 0L);
context.getAttributes().put(LinkContext.FAILOVER_RETRY_TIME_ATTRIBUTE,
retryTime + backoffExecution.getBackoff(retry));
try {
producer.send(context);
} catch (Exception exp) {
log.error("master send context: {}, error:", context, exp);
throw new LinkIOException("send master mq error");
}
chain.filter(context);
};
}
}
希望大家一起来共建xxx-commons组件,也希望xxx-commons组件能够成为内部基础组件被广泛使用,提升系统和业务开发效率,并提升开发质量。