干货:RestTemplate接口调用超时,罪魁祸首竟是它

前    言

突然!频繁收到一个老系统的告警通知,全是调用第三方接口超时的报错。排查后发现,竟是RestTemplate配置惹的祸!明明自定义了600秒的超时,实际却10秒就超时了。今天就把整个踩坑、排坑过程跟大家捋一捋,帮大家避避坑!

Rest Template with Spring Let's interact with services and

填坑过程

一开始我也懵了:明明在restTemplateSimple这个Bean里设置了600秒超时,10分钟的接口调用还超时?

图片

排查接口调用日志,发现调用第三方接口10秒后就超时了,跟restTemplateSimple配置的超时时间完全不符

图片

检查Bean注入情况,才发现Service里用@Autowired注入的是restTemplate,而不是我们配置的restTemplateSimpe

图片

这就尴尬了——Spring容器里同时存在两个RestTemplate实例,我们注错了对象!

为什么默认是10秒

你可能好奇,那10秒超时是哪来的?这里有个:如果你用SimpleClientHttpRequestFactory且没明确设置超时,或者通过RestTemplateBuilder配置但没指定超时,Spring Boot会有默认行为。在某些版本或配置下,这个默认值可能就是10秒。

图片

让我们使用以下代码,验证下默认超时时间,发现输出的超时时间确实是10秒。

   /**
     * 验证restTemplate的默认超时时间
     */
    @PostConstruct
    public void checkRestTemplateTimeout() {
        try {
            ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
            log.info("RestTemplate RequestFactory 类型: {}", factory.getClass().getName());

            // 如果是 InterceptingClientHttpRequestFactory,需要获取内部的真实 factory
            if (factory instanceof InterceptingClientHttpRequestFactory) {
                ClientHttpRequestFactory realFactory = getInternalFactory((InterceptingClientHttpRequestFactory) factory);
                log.info("内部真实 RequestFactory 类型: {}", realFactory.getClass().getName());

                if (realFactory instanceof SimpleClientHttpRequestFactory) {
                    SimpleClientHttpRequestFactory simpleFactory = (SimpleClientHttpRequestFactory) realFactory;
                    Object connectTimeout = getFieldValue(simpleFactory, "connectTimeout");
                    Object readTimeout = getFieldValue(simpleFactory, "readTimeout");
                    log.info("connectTimeout: {} ms", connectTimeout);
                    log.info("readTimeout: {} ms", readTimeout);
                }
            } else if (factory instanceof SimpleClientHttpRequestFactory) {
                SimpleClientHttpRequestFactory simpleFactory = (SimpleClientHttpRequestFactory) factory;
                Object connectTimeout = getFieldValue(simpleFactory, "connectTimeout");
                Object readTimeout = getFieldValue(simpleFactory, "readTimeout");
                log.info("connectTimeout: {} ms", connectTimeout);
                log.info("readTimeout: {} ms", readTimeout);
            }
        } catch (Exception e) {
            log.error("获取 RestTemplate 超时配置失败", e);
        }
    }

    /**
     * 从 InterceptingClientHttpRequestFactory 中获取内部的真实 ClientHttpRequestFactory
     */
    private ClientHttpRequestFactory getInternalFactory(InterceptingClientHttpRequestFactory interceptingFactory) {
        try {
            Field field = AbstractClientHttpRequestFactoryWrapper.class.getDeclaredField("requestFactory");
            field.setAccessible(true);
            return (ClientHttpRequestFactory) field.get(interceptingFactory);
        } catch (Exception e) {
            log.error("获取内部 factory 失败", e);
            return null;
        }
    }

    /**
     * 通过反射获取对象的私有字段值
     */
    private Object getFieldValue(Object obj, String fieldName) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            log.error("获取字段值失败: {}", fieldName, e);
            return null;
        }
    }

解决方案:使用 @Qualifier 注入指定的 Bean


@Autowired
@Qualifier("restTemplateSimple")  // 指定注入你配置的 Bean
private RestTemplate restTemplate;

经验总结与最佳实践

通过这次排查,我们总结出以下经验:

  1. 显式配置超时时间:永远不要依赖默认超时设置,因为不同环境下的默认行为可能不同

  2. Bean命名要规范:在Spring项目中,Bean的命名和注入应该保持一致,避免歧义

  3. 超时配置验证:一定要验证超时配置是否生效,可以编写单元测试进行验证

图片

你是否在项目中也遇到过类似的坑?欢迎在评论区分享你的经历和解决方案!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八零后琐话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值