关于Ribbon重试机制的坑

一、问题

在一次管理后台数据导入接口中,发现在大数量导入的情况下,数据会出现重复写入的问题。后经调试发现导入接口实际上被调用了两次。初步猜测可能是Feign或Ribbon的重试机制导致的。也就是管理后台服务调用业务服务,由于业务服务数据导入执行耗时较长导致超时,从而后台服务进行了重试导致。

后台服务Ribbon配置如下:

#Ribbon配置
#Ribbon更新服务注册列表的频率
ribbon.ServerListRefreshInterval=2000
#请求连接的超时时间
ribbon.ConnectTimeout=3000
#请求处理的超时时间
ribbon.ReadTimeout=10000

二、分析

跟踪源码,在FeignLoadBalancer中配置了重试相关的策略,如果ribbon.OkToRetryOnAllOperations配置为true,则任何请求方法都进行重试,ribbon.OkToRetryOnAllOperations配置为false时,GET请求方式也会进行重试,非GET方法只有在连接异常时才会进行重试。

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler (
        RibbonRequest request, IClientConfig requestConfig){
    // 如果OkToRetryOnAllOperations配置为true,则任何请求方法/任何异常的情况都进行重试
    if (this.ribbon.isOkToRetryOnAllOperations()) {
        return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                requestConfig);
    }
    // OkToRetryOnAllOperations配置为false时(默认为false)
    // 非GET请求,只有连接异常时才进行重试
    if (!request.toRequest().method().equals("GET")) {
        return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
                requestConfig);
        // GET请求任何情况/任何异常都重试
    } else {
        return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                requestConfig);
    }
}

通过上面的分析,我们可以知道并不是配置了ribbon.OkToRetryOnAllOperations=false就不会进行重试,对于GET请求Ribbon还是会进行重试的,而在我们的系统中并没有对Ribbon的重试机制做特殊的配置,也就是用的默认值。Ribbon重试机制默认配置如下:

#同一实例最大重试次数,不包括首次调用。默认值为0
ribbon.MaxAutoRetries = 0
#同一个服务其他实例的最大重试次数,不包括第一次调用的实例。默认值为1
ribbon.MaxAutoRetriesNextServer = 1
#是否所有操作都允许重试。默认值为false
ribbon.OkToRetryOnAllOperations = false

由于MaxAutoRetriesNextServer配置默认值为1,而我们的导入接口恰巧又是GET请求,在业务服务接口数据处理超时的情况下,所以Ribbon会自动重试一次。

三、解决方案

首先GET请求是用于数据查询类接口的请求方式,像涉及到数据插入/更新/删除等操作接口不应该用GET请求方式,在我们的数据导入接口中使用的是GET请求方式,所以此处是存在问题的。

像在一般的系统中,建议关闭Ribbon的重试机制,如果非得开启重试,那么系统的各个接口一定要保证接口的幂等性,否则可能会导致接口逻辑被执行多次的情况,在一些重要数据的场景带来的影响将是灾难性的。

要关闭Ribbon的重试将上面的MaxAutoRetriesNextServer配置为0即可,后调整Ribbon的完整配置如下:

#Ribbon配置
#Ribbon更新服务注册列表的频率
ribbon.ServerListRefreshInterval=2000
#请求连接的超时时间
ribbon.ConnectTimeout=3000
#请求处理的超时时间
ribbon.ReadTimeout=10000
#同一实例最大重试次数,不包括首次调用。默认值为0
ribbon.MaxAutoRetries = 0
#同一个服务其他实例的最大重试次数,不包括第一次调用的实例。默认值为1
ribbon.MaxAutoRetriesNextServer = 0
#是否所有操作都允许重试。默认值为false
ribbon.OkToRetryOnAllOperations = false


如果文章对你有帮助的话,给文章点个赞吧。

如果有写得不正确的地方,欢迎指出。

文章首发公众号:会跳舞的机器人,欢迎扫码关注。