RestTemplate连续读取两个不同文件时报错Read timed out

发布时间 2023-12-01 11:36:02作者: duguyan

在项目上负责对接一些三方接口,鉴于之前的经验,选择使用RestTemplate来实现各种http请求,以及文件的读取。

首先写了RestTemplate的配置类来配置基础信息,代码如下:

@Configuration
@ConditionalOnClass(value = {RestTemplate.class, HttpClient.class})
public class RestTemplateConfig {

    @Value("${remote.maxTotalConnect:1000}")
    private int maxTotalConnect; //连接池的最大连接数1000
    @Value("${remote.maxConnectPerRoute:100}")
    private int maxConnectPerRoute; //单个主机的最大连接数200
    @Value("${remote.connectTimeout:2000}")
    private int connectTimeout; //连接超时默认2s
    @Value("${remote.readTimeout:30000}")
    private int readTimeout; //读取超时默认30s
    @Value("${remote.readTimeout:10000}")
    private int connectionRequestTimeout;   // 从连接池获取连接的超时时间默认10s

    //创建HTTP客户端工厂
    private ClientHttpRequestFactory createFactory() {
        if (this.maxTotalConnect <= 0) {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(this.connectTimeout);
            factory.setReadTimeout(this.readTimeout);
            return factory;
        }
        HttpClient httpClient = HttpClientBuilder.create().setMaxConnTotal(this.maxTotalConnect)
                .setMaxConnPerRoute(this.maxConnectPerRoute).setConnectionTimeToLive(30, TimeUnit.MINUTES).build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
                httpClient);
        factory.setConnectTimeout(this.connectTimeout);
        factory.setReadTimeout(this.readTimeout);
        factory.setConnectionRequestTimeout(connectionRequestTimeout);
        return factory;
    }

    //初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate getRestTemplate() {
        RestTemplate restTemplate = new RestTemplate(this.createFactory());
        //换上fastjson
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
        while (iterator.hasNext()) {
            HttpMessageConverter<?> converter = iterator.next();
            //原有的String是ISO-8859-1编码 去掉
            if (converter instanceof StringHttpMessageConverter) {
                iterator.remove();
            }
            //由于系统中默认有jackson 在转换json时自动会启用  但是我们不想使用它 可以直接移除
            if (converter instanceof GsonHttpMessageConverter || converter instanceof MappingJackson2HttpMessageConverter) {
                iterator.remove();
            }
        }
        messageConverters.add(new StringHttpMessageConverter(Charset.forName("utf-8")));
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        /** 设置支持的MediaType**/
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON);
        mediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        mediaTypes.add(MediaType.APPLICATION_PDF);
        mediaTypes.add(MediaType.APPLICATION_RSS_XML);
        mediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        mediaTypes.add(MediaType.APPLICATION_XML);
        mediaTypes.add(MediaType.IMAGE_GIF);
        mediaTypes.add(MediaType.IMAGE_JPEG);
        mediaTypes.add(MediaType.IMAGE_PNG);
        mediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        mediaTypes.add(MediaType.TEXT_HTML);
        mediaTypes.add(MediaType.TEXT_MARKDOWN);
        mediaTypes.add(MediaType.TEXT_PLAIN);
        mediaTypes.add(MediaType.TEXT_XML);
        converter.setSupportedMediaTypes(mediaTypes);
//      设置默认的字符集
        converter.setDefaultCharset(Charset.forName("UTF-8"));
//      设置默认的时间格式
        JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat);
//       设置序列化方式
        fastJsonConfig.setSerializerFeatures(
                //List字段如果为null,输出为[],而非null
                SerializerFeature.WriteNullListAsEmpty,
                //是否输出值为null的字段
                SerializerFeature.WriteMapNullValue,
                //字符类型字段如果为null,输出为”“,而非null
                SerializerFeature.WriteNullStringAsEmpty,
                //消除对同一对象循环引用的问题
                SerializerFeature.DisableCircularReferenceDetect);
        converter.setFastJsonConfig(fastJsonConfig);
        messageConverters.add(converter);
        return restTemplate;
    }

}

  然后在项目中用@Resource  RestTemplate 使用就可以了;代码完成后打包启用一切正常。在测试环境运行一周后突然开始报错,测试反馈系统一直timed out,整个业务流程被阻断了

 查看日志,确如测试同事所说,当天调用该方法的请求都出现了这个问题。这就尴尬了,之前也这么写的,为啥现在报错了,而且前一天还是正常的。一直没想通,改用httpClient方式,发现还是timed out。再看代码,是系统要连续读取两张照片,第一张读取时是没问题的,在读取第二张时,系统明显感觉发送完请求就假死了,一直到连接超时。

 鉴于图片用两种读取方式都能读到第一张,所以想着用每种方式读取一张,看能不能完成本次需求,代码修改后,再次测试,系统正常跑完流程,未再报错。但是为什么用RestTemplate连续读取两次报错,一直不理解。直到查资料RestTemplate还有个connectionRequestTimeout时间设置。源码如下:

 用翻译软件翻译结果如下:

        设置请求连接时使用的超时(以毫秒为单位) 使用底层的{@link RequestConfig}从连接管理器获取。 超时值0指定无限超时。 其他属性可以通过指定 {@link RequestConfig}在自定义{@link HttpClient}上的实例。 请求连接的超时值,以毫秒为单位。

原来是RestTemplate,连接池关闭获取不到连接就报readtimeout异常,随后在RestTemplateConfiguration设置上connectionRequestTimeout时间10s,再次测试OK。与通过两种不同方式读取结果一致,都能完成业务正常流水。至此问题解决!