From e16a6fe119d8d2e3793946c9ff5a4c9ad32ceafa Mon Sep 17 00:00:00 2001 From: welsir <1824379011@qq.com> Date: Tue, 27 Jan 2026 01:09:28 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E8=AF=95-=E7=86=94=E6=96=AD=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../retry/backoff/BackoffStrategy.java | 7 + .../retry/backoff/ExponentialBackoff.java | 18 ++ .../retry/jitter/FullJitter.java | 14 ++ .../retry/jitter/JitterAlgorithm.java | 7 + .../retry/retryBudget/RetryBudget.java | 7 + tml-sdk-spring-boot-autoconfigure/pom.xml | 33 ++++ .../retry/RetryAutoConfiguration.java | 51 ++++++ .../autoconfigure/retry/RetryProperties.java | 168 ++++++++++++++++++ .../retry/annotation/BackoffType.java | 27 +++ .../retry/annotation/EnableRetry.java | 19 ++ .../retry/annotation/JitterType.java | 32 ++++ .../retry/annotation/Retryable.java | 72 ++++++++ .../retry/aop/RetryInterceptor.java | 125 +++++++++++++ .../retry/strategy/DelayCalculator.java | 112 ++++++++++++ 14 files changed, 692 insertions(+) create mode 100644 tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/BackoffStrategy.java create mode 100644 tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/ExponentialBackoff.java create mode 100644 tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/FullJitter.java create mode 100644 tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/JitterAlgorithm.java create mode 100644 tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/retryBudget/RetryBudget.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java create mode 100644 tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java diff --git a/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/BackoffStrategy.java b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/BackoffStrategy.java new file mode 100644 index 0000000..4e9db2a --- /dev/null +++ b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/BackoffStrategy.java @@ -0,0 +1,7 @@ +package io.github.timemachinelab.retry.backoff; + +public interface BackoffStrategy { + + long calculateDelay(int attempts); + +} diff --git a/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/ExponentialBackoff.java b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/ExponentialBackoff.java new file mode 100644 index 0000000..f1ff172 --- /dev/null +++ b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/backoff/ExponentialBackoff.java @@ -0,0 +1,18 @@ +package io.github.timemachinelab.retry.backoff; + +public class ExponentialBackoff implements BackoffStrategy { + + private final long baseDelay = 100; + + private long minDelay = 100; + private long maxDelay = 5000; + + @Override + public long calculateDelay(int attempts) { + + long delay = (long) (baseDelay * Math.pow(2,attempts-1)); + delay = Math.max(delay, baseDelay); + delay = Math.min(minDelay, delay); + return delay; + } +} diff --git a/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/FullJitter.java b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/FullJitter.java new file mode 100644 index 0000000..73fdf1c --- /dev/null +++ b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/FullJitter.java @@ -0,0 +1,14 @@ +package io.github.timemachinelab.retry.jitter; + +import java.util.concurrent.ThreadLocalRandom; + +public class FullJitter implements JitterAlgorithm { + + private ThreadLocalRandom random = ThreadLocalRandom.current(); + + @Override + public long calculateDelay(long baseDelay,long previousDelay) { + return random.nextLong(0,baseDelay+1); + + } +} diff --git a/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/JitterAlgorithm.java b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/JitterAlgorithm.java new file mode 100644 index 0000000..55453e2 --- /dev/null +++ b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/jitter/JitterAlgorithm.java @@ -0,0 +1,7 @@ +package io.github.timemachinelab.retry.jitter; + +public interface JitterAlgorithm { + + long calculateDelay(long baseDelay,long previousDelay); + +} diff --git a/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/retryBudget/RetryBudget.java b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/retryBudget/RetryBudget.java new file mode 100644 index 0000000..72090e0 --- /dev/null +++ b/tml-sdk-java-core/src/main/java/io/github/timemachinelab/retry/retryBudget/RetryBudget.java @@ -0,0 +1,7 @@ +package io.github.timemachinelab.retry.retryBudget; + +public interface RetryBudget { + + + +} diff --git a/tml-sdk-spring-boot-autoconfigure/pom.xml b/tml-sdk-spring-boot-autoconfigure/pom.xml index 99aa1b6..1dd591e 100644 --- a/tml-sdk-spring-boot-autoconfigure/pom.xml +++ b/tml-sdk-spring-boot-autoconfigure/pom.xml @@ -19,6 +19,39 @@ UTF-8 + + + + io.github.timemachinelab + tml-sdk-java-core + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.springframework + spring-aop + + + + org.aspectj + aspectjweaver + + + + org.springframework.boot + spring-boot-configuration-processor + + + + org.slf4j + slf4j-api + + + master https://github.com/Time-Machine-Lab/TmlFoundation diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java new file mode 100644 index 0000000..a867a0d --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java @@ -0,0 +1,51 @@ +package io.github.timemachinelab.autoconfigure.retry; + +import io.github.timemachinelab.autoconfigure.retry.aop.RetryInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 重试组件自动配置类 + * + * @author TimeMachineLab + */ +@Configuration +@ConditionalOnClass(name = "org.aspectj.lang.annotation.Aspect") +@ConditionalOnProperty(prefix = "tml.retry", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(RetryProperties.class) +@EnableAspectJAutoProxy +public class RetryAutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(RetryAutoConfiguration.class); + + public RetryAutoConfiguration() { + log.info("TML Retry Component Auto Configuration initialized"); + } + + /** + * 注册重试拦截器 + */ + @Bean + public RetryInterceptor retryInterceptor() { + log.info("Registering RetryInterceptor bean"); + return new RetryInterceptor(); + } + + // TODO: 当你实现了 core 模块后,可以在这里注册更多 Bean + // 例如: + // @Bean + // public RetryExecutor retryExecutor(RetryProperties properties) { + // return new RetryExecutor(...); + // } + // + // @Bean + // public RetryBudget retryBudget(RetryProperties properties) { + // return new TokenBucketRetryBudget(...); + // } +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java new file mode 100644 index 0000000..4b3d673 --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java @@ -0,0 +1,168 @@ +package io.github.timemachinelab.autoconfigure.retry; + +import io.github.timemachinelab.autoconfigure.retry.annotation.JitterType; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 重试组件配置属性 + * + * @author TimeMachineLab + */ +@ConfigurationProperties(prefix = "tml.retry") +public class RetryProperties { + + /** + * 是否启用重试功能 + */ + private boolean enabled = true; + + /** + * 默认配置 + */ + private DefaultConfig defaultConfig = new DefaultConfig(); + + /** + * 重试预算配置 + */ + private BudgetConfig budget = new BudgetConfig(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public DefaultConfig getDefaultConfig() { + return defaultConfig; + } + + public void setDefaultConfig(DefaultConfig defaultConfig) { + this.defaultConfig = defaultConfig; + } + + public BudgetConfig getBudget() { + return budget; + } + + public void setBudget(BudgetConfig budget) { + this.budget = budget; + } + + /** + * 默认重试配置 + */ + public static class DefaultConfig { + + /** + * 最大重试次数 + */ + private int maxAttempts = 3; + + /** + * 基础延迟时间(毫秒) + */ + private long baseDelay = 100; + + /** + * 最大延迟时间(毫秒) + */ + private long maxDelay = 60000; + + /** + * 抖动类型 + */ + private JitterType jitterType = JitterType.FULL; + + public int getMaxAttempts() { + return maxAttempts; + } + + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + public long getBaseDelay() { + return baseDelay; + } + + public void setBaseDelay(long baseDelay) { + this.baseDelay = baseDelay; + } + + public long getMaxDelay() { + return maxDelay; + } + + public void setMaxDelay(long maxDelay) { + this.maxDelay = maxDelay; + } + + public JitterType getJitterType() { + return jitterType; + } + + public void setJitterType(JitterType jitterType) { + this.jitterType = jitterType; + } + } + + /** + * 重试预算配置 + */ + public static class BudgetConfig { + + /** + * 是否启用重试预算 + */ + private boolean enabled = true; + + /** + * 滑动窗口大小(秒) + */ + private int windowSize = 10; + + /** + * 最大重试比例(0.0 - 1.0) + */ + private double maxRetryRatio = 0.1; + + /** + * 最小请求数阈值 + */ + private int minRequestsThreshold = 30; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getWindowSize() { + return windowSize; + } + + public void setWindowSize(int windowSize) { + this.windowSize = windowSize; + } + + public double getMaxRetryRatio() { + return maxRetryRatio; + } + + public void setMaxRetryRatio(double maxRetryRatio) { + this.maxRetryRatio = maxRetryRatio; + } + + public int getMinRequestsThreshold() { + return minRequestsThreshold; + } + + public void setMinRequestsThreshold(int minRequestsThreshold) { + this.minRequestsThreshold = minRequestsThreshold; + } + } +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java new file mode 100644 index 0000000..909f561 --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java @@ -0,0 +1,27 @@ +package io.github.timemachinelab.autoconfigure.retry.annotation; + +/** + * 退避策略类型枚举 + * + * @author TimeMachineLab + */ +public enum BackoffType { + + /** + * 固定延迟 + * delay = baseDelay + */ + FIXED, + + /** + * 线性退避 + * delay = baseDelay × attempt + */ + LINEAR, + + /** + * 指数退避(推荐) + * delay = baseDelay × 2^(attempt-1) + */ + EXPONENTIAL +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java new file mode 100644 index 0000000..13cd2fa --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java @@ -0,0 +1,19 @@ +package io.github.timemachinelab.autoconfigure.retry.annotation; + +import io.github.timemachinelab.autoconfigure.retry.RetryAutoConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * 启用重试功能的注解 + * 在 Spring Boot 配置类上使用 + * + * @author TimeMachineLab + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(RetryAutoConfiguration.class) +public @interface EnableRetry { +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java new file mode 100644 index 0000000..81e53b6 --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java @@ -0,0 +1,32 @@ +package io.github.timemachinelab.autoconfigure.retry.annotation; + +/** + * 抖动类型枚举 + * + * @author TimeMachineLab + */ +public enum JitterType { + + /** + * 全抖动:random(0, backoff) + * AWS 推荐,最大程度避免共振 + */ + FULL, + + /** + * 均等抖动:backoff/2 + random(0, backoff/2) + * 保证最小延迟,同时添加随机性 + */ + EQUAL, + + /** + * 去相关抖动:random(base, previous * 3) + * 考虑上次延迟,适合长时间重试场景 + */ + DECORRELATED, + + /** + * 无抖动:使用原始退避时间 + */ + NONE +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java new file mode 100644 index 0000000..142a5d0 --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java @@ -0,0 +1,72 @@ +package io.github.timemachinelab.autoconfigure.retry.annotation; + +import java.lang.annotation.*; + +/** + * 标记方法需要重试的注解 + * + * 使用示例: + *
+ * {@code
+ * @Retryable(
+ *     backoffType = BackoffType.EXPONENTIAL,
+ *     jitterType = JitterType.FULL,
+ *     maxAttempts = 3,
+ *     baseDelay = 100,
+ *     maxDelay = 60000
+ * )
+ * public User getUser(Long id) {
+ *     // 业务代码
+ * }
+ * }
+ * 
+ * + * @author TimeMachineLab + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Retryable { + + /** + * 退避策略类型 + * 默认 EXPONENTIAL(指数退避) + */ + BackoffType backoffType() default BackoffType.EXPONENTIAL; + + /** + * 抖动算法类型 + * 默认 FULL(全抖动) + */ + JitterType jitterType() default JitterType.FULL; + + /** + * 最大重试次数(包含首次调用) + * 默认 3 次 + */ + int maxAttempts() default 3; + + /** + * 基础延迟时间(毫秒) + * 默认 100ms + */ + long baseDelay() default 100; + + /** + * 最大延迟时间(毫秒) + * 默认 60000ms (60秒) + */ + long maxDelay() default 60000; + + /** + * 需要重试的异常类型(白名单) + * 默认为空数组,表示重试所有异常 + */ + Class[] include() default {}; + + /** + * 不需要重试的异常类型(黑名单) + * 优先级高于 include + */ + Class[] exclude() default {}; +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java new file mode 100644 index 0000000..1727ac9 --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java @@ -0,0 +1,125 @@ +package io.github.timemachinelab.autoconfigure.retry.aop; + +import io.github.timemachinelab.autoconfigure.retry.annotation.Retryable; +import io.github.timemachinelab.autoconfigure.retry.strategy.DelayCalculator; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; + +/** + * 重试拦截器 + * 拦截 @Retryable 注解的方法,应用重试逻辑 + * + * @author TimeMachineLab + */ +@Aspect +public class RetryInterceptor { + + private static final Logger log = LoggerFactory.getLogger(RetryInterceptor.class); + + /** + * 拦截所有标注了 @Retryable 的方法 + */ + @Around("@annotation(io.github.timemachinelab.autoconfigure.retry.annotation.Retryable)") + public Object aroundRetryable(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Retryable retryable = method.getAnnotation(Retryable.class); + + if (retryable == null) { + return joinPoint.proceed(); + } + + // 从注解中读取配置 + int maxAttempts = retryable.maxAttempts(); + Class[] includeExceptions = retryable.include(); + Class[] excludeExceptions = retryable.exclude(); + + // 创建延迟计算器(组合退避策略和抖动算法) + DelayCalculator delayCalculator = new DelayCalculator( + retryable.backoffType(), + retryable.jitterType(), + retryable.baseDelay(), + retryable.maxDelay() + ); + + // TODO: 这里需要调用你在 core 模块实现的 RetryExecutor 和 RetryBudget + // 目前先实现一个简单的重试逻辑作为占位符 + + Throwable lastException = null; + long previousDelay = retryable.baseDelay(); + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + log.debug("Executing method {} - attempt {}/{} (backoff={}, jitter={})", + method.getName(), attempt, maxAttempts, + retryable.backoffType(), retryable.jitterType()); + + return joinPoint.proceed(); + + } catch (Throwable ex) { + lastException = ex; + + // 检查是否应该重试此异常 + if (!shouldRetry(ex, includeExceptions, excludeExceptions)) { + log.debug("Exception {} is not retryable, throwing immediately", + ex.getClass().getName()); + throw ex; + } + + // 如果是最后一次尝试,直接抛出异常 + if (attempt >= maxAttempts) { + log.warn("Retry exhausted after {} attempts for method {}", + maxAttempts, method.getName()); + throw ex; + } + + // 使用延迟计算器计算延迟时间(组合退避 + 抖动) + long delay = delayCalculator.calculateDelay(attempt, previousDelay); + previousDelay = delay; + + log.debug("Method {} failed on attempt {}, retrying after {}ms (backoff={}, jitter={})", + method.getName(), attempt, delay, + retryable.backoffType(), retryable.jitterType()); + + Thread.sleep(delay); + } + } + + throw lastException; + } + + /** + * 判断异常是否应该重试 + */ + private boolean shouldRetry(Throwable ex, + Class[] includeExceptions, + Class[] excludeExceptions) { + + // 先检查黑名单(优先级更高) + for (Class excludeType : excludeExceptions) { + if (excludeType.isAssignableFrom(ex.getClass())) { + return false; + } + } + + // 如果没有配置白名单,默认重试所有异常 + if (includeExceptions.length == 0) { + return true; + } + + // 检查白名单 + for (Class includeType : includeExceptions) { + if (includeType.isAssignableFrom(ex.getClass())) { + return true; + } + } + + return false; + } +} diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java new file mode 100644 index 0000000..c05efe1 --- /dev/null +++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java @@ -0,0 +1,112 @@ +package io.github.timemachinelab.autoconfigure.retry.strategy; + +import io.github.timemachinelab.autoconfigure.retry.annotation.BackoffType; +import io.github.timemachinelab.autoconfigure.retry.annotation.JitterType; + +/** + * 延迟计算器 + * 组合退避策略和抖动算法,计算最终的重试延迟 + * + * @author TimeMachineLab + */ +public class DelayCalculator { + + private final BackoffType backoffType; + private final JitterType jitterType; + private final long baseDelay; + private final long maxDelay; + + public DelayCalculator(BackoffType backoffType, JitterType jitterType, + long baseDelay, long maxDelay) { + this.backoffType = backoffType; + this.jitterType = jitterType; + this.baseDelay = baseDelay; + this.maxDelay = maxDelay; + } + + /** + * 计算延迟时间 + * + * @param attempt 当前重试次数(从 1 开始) + * @param previousDelay 上一次的延迟(用于 Decorrelated Jitter) + * @return 最终延迟时间(毫秒) + */ + public long calculateDelay(int attempt, long previousDelay) { + // 1. 根据退避策略计算基准延迟 + long backoffDelay = calculateBackoff(attempt); + + // 2. 根据抖动算法添加随机性 + long finalDelay = applyJitter(backoffDelay, previousDelay); + + return finalDelay; + } + + /** + * 计算退避延迟(基准值) + */ + private long calculateBackoff(int attempt) { + long delay; + + switch (backoffType) { + case FIXED: + // 固定延迟 + delay = baseDelay; + break; + + case LINEAR: + // 线性退避:baseDelay × attempt + delay = baseDelay * attempt; + break; + + case EXPONENTIAL: + // 指数退避:baseDelay × 2^(attempt-1) + delay = baseDelay * (long) Math.pow(2, attempt - 1); + break; + + default: + delay = baseDelay; + } + + // 限制在 [baseDelay, maxDelay] 范围内 + delay = Math.max(delay, baseDelay); + delay = Math.min(delay, maxDelay); + + return delay; + } + + /** + * 应用抖动算法 + */ + private long applyJitter(long backoffDelay, long previousDelay) { + long delay; + + switch (jitterType) { + case FULL: + // 全抖动:random(0, backoff) + delay = (long) (Math.random() * backoffDelay); + break; + + case EQUAL: + // 均等抖动:backoff/2 + random(0, backoff/2) + delay = backoffDelay / 2 + (long) (Math.random() * (backoffDelay / 2)); + break; + + case DECORRELATED: + // 去相关抖动:random(baseDelay, previousDelay * 3) + long min = baseDelay; + long max = Math.min(previousDelay * 3, maxDelay); + delay = min + (long) (Math.random() * (max - min)); + break; + + case NONE: + // 无抖动:使用原始退避时间 + delay = backoffDelay; + break; + + default: + delay = backoffDelay; + } + + return delay; + } +}