最近在项目上遇到一个奇怪的问题:

我们有两个服务,服务A调用服务B发送邮件,但结果是重复发送了4封邮件,内容还是一样的。一开始以为是邮件服务器出问题,但通过本地调试发现发送邮件的方法是被重复调用了。

至于为什么会重复调用,这得先了解一下Spring Cloud 的重试机制。

Spring Cloud 整合了 Spring Retry实现重试逻辑。

可以通过Ribbon、Feign、Zuul中进行配置。

服务A中有如下Ribbon配置:

1
2
3
4
5
6
7
8
9
10
11
ribbon:
# 客户端读取超时时间,超时时间要小于Hystrix的超时时间,否则重试机制就无意义了
ReadTimeout: ${RIBBON_READ_TIMEOUT:30000}
# 客户端连接超时时间
ConnectTimeout: ${RIBBON_CONNECT_TIMEOUT:3000}
# 访问实例失败(超时),允许自动重试,设置重试次数,失败后会更换实例访问,请一定确保接口的幂等性,否则重试可能导致数据异常。
OkToRetryOnAllOperations: true
# 最大重试次数(不包含首次调用)
MaxAutoRetries: 1
# 切换实例后最大重试次数
MaxAutoRetriesNextServer: 1

可以看到 OkToRetryOnAllOperations 属性控制了超时时是否自动重试所有请求,当为true时会重试所有请求,包含POST请求,可能会对服务器资源产生影响,如我遇到的重复发送多封邮件的问题。

因此,我将服务A 的 ReadTimeOut更改为 40000,重新部署测试重复调用的情况消失。

ribbon.retryableStatusCodes 属性列出希望客户端重试的响应代码。

Feign的重试功能被默认配置为永不启用

Zuul可以通过zuul.retryable 来控制是否开启重试。可以通过zuul.routes.routename.retryable来为逐个路由禁用重试功能