重试机制

说明

新增重试机制 及 流控策略(基于流控策略的优雅退避机制)。

核心库 aliyun-java-sdk-core 从 4.6.0 版本开始支持重试机制,以及提供基于流控策略的优雅退避方案(基于流控策略的优雅退避机制)。Maven 依赖如下:

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.6.0</version>
</dependency>

重试机制

关闭重试

默认不开启重试,您也可以主动配置重试策略为 None,有以下两种方式:

// Client 级别配置,关闭重试
client.setSysRetryPolicy(RetryPolicy.none());

// Request 级别配置重试策略,优先级大于 Client 级别
request.setSysRetryPolicy(RetryPolicy.none());

重试策略

重试策略提供三种:

  1. 指定异常(Exception)

  2. 指定状态码(Http StatusCode)

  3. 指定服务端返回头(Http Response Headers)

三种方式互不影响,即三种之间是或关系,触发任何一种 State 则进行重试或者不重试。也就是说,策略可以是触发重试的策略也可以是限制重试的策略。

触发重试和限制重试参考以下配置方式:

  • 创建重试策略集合:

Set<RetryCondition> retryConditions = new HashSet<RetryCondition>();

// Condition 举例
// 触发重试的 statusCode 配置,例:当状态码返回 500 或 501 时进行重试
Set<Integer> statusCodes = new HashSet<Integer>();
statusCodes.add(500); // http statusCode
statusCodes.add(501); // http statusCode
// 加入到触发重试策略中
retryConditions.add(StatusCodeCondition.create(statusCodes));

// 触发重试的 exception 配置,例:当遇到 SocketTimeoutException 或 IOException 时进行重试
Set<Class<? extends Exception>> exceptions = new HashSet<Class<? extends Exception>>();
exceptions.add(SocketTimeoutException.class); // exception
exceptions.add(IOException.class); // exception
// 加入到触发重试策略中
retryConditions.add(ExceptionsCondition.create(exceptions));
  • 创建限制重试集合:

Set<RetryCondition> throttlingConditions = new HashSet<RetryCondition>();

// Condition 举例
// 限制重试的 statusCode 配置,例:当状态码返回 429 时禁止进行重试
Set<Integer> code = new HashSet<Integer>();
code.add(429); // http statusCode,限制策略,此处表示遇到 429 则限制重试
// 加入到限制重试策略中
throttlingConditions.add(StatusCodeCondition.create(code));
  • 最后将 Conditions 配置到 RetryPolicy 中:

RetryPolicy retryPolicy = RetryPolicy.builder()
                .maxNumberOfRetries(3) // 最大重试次数
                .maxDelayTimeMillis(20 * 1000) // 最大重试间隔时间,单位为 ms,超过这个时间则不再重试
                .retryConditions(retryConditions) // 触发重试策略
                .throttlingConditions(throttlingConditions) // 限制重试策略
                .build();

其中每次重试的间隔时间,则是根据指数退避算法计算出,也就是使用的EqualJitter算法计算出下次重试等待时间。

重试 Conditions 高阶设置

官方提供了三种 Condition,这里做更加详细的介绍。

  1. StatusCodeCondition

    1. 存放的是整数集合,根据集合中的 Http 状态码,与此次调用的返回的真实的状态码做比较,然后判断是否进行重试或者限制重试。

  2. ExceptionsCondition

    1. 存放的是 Exception 集合,根据集合中的 Exception 类型,与此次调用的返回的抛出的异常做比较,然后判断是否进行重试或者限制重试。

  3. HeadersCondition

    1. 存放的是个 Map,该结构稍微复杂,Map 的 key 值匹配的是返回头 Headers 的 key 值,Map 的 value 需要实现 Pattern(com.aliyuncs.policy.retry.pattern.Pattern) 接口,表示对 Headers 相应 key 对应的 value 做表达式匹配,例如:包含某个字符串、等于某个数值等。

    2. 官方实现了两个 Pattern,一个是 AliyunThrottlingPattern(基于阿里云的流控策略,基于流控策略的优雅退避机制),另一个是 SimplePattern(直接比较 value 字符串是否相等)

    3. 可自定义实现 Pattern 接口,其中三个函数需要实现:

      1. meetState(),表示匹配上的原则;

      2. escapeTime(),表示逃脱时间,仅用于限制重试策略上,若该值不等于 -1,则表示在该时间内不进行重试,但也不会直接限制重试,而是等待到可以重试时,再进行重试。若该值大于最大重试间隔时间,则直接返回不再等待。

  4. 自定义 Condition

    1. 用户可实现自己的 Condition,只需要实现 RetryCondition 接口,需要实现两个接口函数:

      1. meetState(RetryPolicyContext var1),根据上下文判断是否符合匹配状态;

      2. escapeTime(RetryPolicyContext var1),计算逃脱时间,仅用于限制重试策略上,触发重试策略则默认 -1。

完整代码示例

代码示例:

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.exceptions.ThrottlingException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.policy.retry.RetryPolicy;
import com.aliyuncs.policy.retry.conditions.ExceptionsCondition;
import com.aliyuncs.policy.retry.conditions.RetryCondition;
import com.aliyuncs.policy.retry.conditions.StatusCodeCondition;
import com.aliyuncs.profile.DefaultProfile;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.HashSet;
import java.util.Set;

public class TestV {
    public static void main(String[] args) {
        // 创建DefaultAcsClient实例并初始化
        DefaultProfile profile = DefaultProfile.getProfile(
                "cn-hangzhou",            // 地域ID
                "xxx",   // RAM账号的AccessKey ID
                "xxx");             // RAM账号Access Key Secret
        IAcsClient client = new DefaultAcsClient(profile);
        // Client 级别配置重试策略
        client.setSysRetryPolicy(RetryPolicy.none());
        CommonRequest or APIRequest request = new xxxRequest();
        // Request 级别配置重试策略,优先级大于 Client 级别
        request.setSysRetryPolicy(RetryPolicy.defaultRetryPolicy(true));

        // 触发重试的 statusCode 配置
        Set<RetryCondition> retryConditions = new HashSet<RetryCondition>();
        Set<Integer> statusCodes = new HashSet<Integer>();
        statusCodes.add(500); // http statusCode
        statusCodes.add(501); // http statusCode
        retryConditions.add(StatusCodeCondition.create(statusCodes));
        
        // 触发重试的 exception 配置
        Set<Class<? extends Exception>> exceptions = new HashSet<Class<? extends Exception>>();
        exceptions.add(SocketTimeoutException.class); // exception
        exceptions.add(IOException.class); // exception
        retryConditions.add(ExceptionsCondition.create(exceptions));
        
        // 限制重试的 statusCode 配置
        Set<RetryCondition> throttlingConditions = new HashSet<RetryCondition>();
        Set<Integer> code = new HashSet<Integer>();
        code.add(429); // http statusCode,限制策略,此处表示遇到 429 则限制重试
        throttlingConditions.add(StatusCodeCondition.create(code));
        
        RetryPolicy retryPolicy = RetryPolicy.builder()
                .maxNumberOfRetries(3) // 最大重试次数
                .maxDelayTimeMillis(20 * 1000) // 最大重试间隔时间,超过这个时间则不再重试
                .retryConditions(retryConditions) // 重试触发策略
                .enableAliyunThrottlingControl(true) // 使用 阿里云 流控策略 进行控制
                .throttlingConditions(throttlingConditions) // 也可以自己写限制策略
                .build();

        try {
            xxxResponse response = client.xxx(request);
            System.out.println(response.getData());
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
            if (ThrottlingException.class.isAssignableFrom(e.getCause().getClass())) {
                // 流控异常包在 ClientException 中
            }
        }
    }
}

阿里云首页 阿里云SDK 相关技术圈