前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >因RestTemplate超时引发的血案

因RestTemplate超时引发的血案

作者头像
业余草
发布2021-01-29 10:21:32
2.6K0
发布2021-01-29 10:21:32
举报
文章被收录于专栏:业余草业余草

最近线上出了一次故障,收银台系统所有服务全部假死。订单量瞬时下降,造成很大损失。

故障总结,导致问题的原因有两方面:

数据库慢查询

  • RestTemplate超时时间设置不生效。
  • spring-web不同版本设置RestTemplate方式不完全一样。

〓默认超时设置

默认情况下是没有超时设置的,此时超时依赖两方面

依赖TCP连接本身的超时时间(tcp空闲连接,超过一定时间,连接会被关闭)。

请求所经过的网络节点的超时时间。e.g. 中间经过nginx, nginx默认读取后端服务的超时时间是60s,所以超时时间在60s左右(日志显示稍微大一点,不会大很多)。

〓代码分析

例子

代码语言:javascript
复制
long start = System.currentTimeMillis();
try {
    RestTemplate restTemplate = new RestTemplate();
    Map responseObject = restTemplate.getForObject(url, Map.class);
    System.out.println(responseObject);
} catch (Exception e) {
    Assert.assertNotNull(e);
    System.out.println("timeout = " + (System.currentTimeMillis() - start));
}

原因:RestTemplate继承自 HttpAccessor, 默认使用的 ClientHttpRequestFactory是 SimpleClientHttpRequestFactory

代码语言:javascript
复制
public abstract class HttpAccessor {
    /**
     * Logger available to subclasses.
     */
    protected final Log logger = LogFactory.getLog(getClass());
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
}
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory {
    private static final int DEFAULT_CHUNK_SIZE = 4096;
    private Proxy proxy;
    private boolean bufferRequestBody = true;
    private int chunkSize = DEFAULT_CHUNK_SIZE;
  // 连接和读取超时都是 -1, 也就是没有超时设置。
    private int connectTimeout = -1;
    private int readTimeout = -1;
}

那么我们使用 RestTemplate该如何设置超时时间呢?

〓RestTemplate超时设置

由上面的代码我们了解到,超时设置其实应该通过内部的 ClientHttpRequestFactory 来设置的。

所以就可以通过给 RestTemplate设置一个我们自己创建的,设置了超时时间的 ClientHttpRequestFactory来实现。

代码语言:javascript
复制
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(1000);
clientHttpRequestFactory.setReadTimeout(50);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(clientHttpRequestFactory);

或者

代码语言:javascript
复制
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(1000);
clientHttpRequestFactory.setReadTimeout(50);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(clientHttpRequestFactory);

但是要注意的是: HttpComponentsClientHttpRequestFactory底层使用了apache的 HttpClient,超时时间的设置其实是针对它进行设置的。

〓HttpComponentsClientHttpRequestFactory

代码语言:javascript
复制
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
//默认读取超时 60s
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
private HttpClient httpClient;
/**
 * Set the connection timeout for the underlying HttpClient.
 * A timeout value of 0 specifies an infinite timeout.
 * @param timeout the timeout value in milliseconds
 */
public void setConnectTimeout(int timeout) {
    Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
    getHttpClient().getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
}

到此,如果就通过上面提到的方式设置超时时间,那么我们的应用就不用有超时问题,也不会发生故障了。

但问题就发生在,公司内部使用的组件,不是通过 HttpComponentsClientHttpRequestFactory 设置超时时间,而是通过设置 HttpComponentsClientHttpRequestFactory内部的 HttpClient设置的超时时间,并且设置了 HttpClient使用的 HttpClientConnectionManager,从而导致了问题的发生。

〓问题代码&测试

代码语言:javascript
复制
@Test
public void testRestTemplateWithRequestFactoryWithoutTimeOut() {
    long start = System.currentTimeMillis();
    try {
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        //2.设置超时时间, 设置/不设置ConnectionManager
        HttpClient httpClient = HttpClientBuilder.create()
                .setDefaultRequestConfig(getRequestConfig())
                .setDefaultSocketConfig(getSocketConfig())
                .setConnectionManager(new PoolingHttpClientConnectionManager(3, TimeUnit.MINUTES))
                .build();
        requestFactory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(requestFactory);
        Map responseObject = restTemplate.getForObject(QUERY_USER_RENEW_URL, Map.class);
        System.out.println(responseObject);
    } catch (Exception e) {
        Assert.assertNotNull(e);
        System.out.println("timeout = " + (System.currentTimeMillis() - start));
    }
}

〓结论

◆spring-web 版本 3.2.0
  • 默认超时 60s, 因为nginx默认的proxyreadtimeout 是60s
  • 设置了 HttpClient的超时时间, 不设置 ConnectionManager 超时生效
  • 设置了 HttpClient的超时时间, 设置 ConnectionManager 超时生效
◆spring-web 版本 4.0.9.RELEASE
  • 默认超时 60s, 因为nginx默认的proxyreadtimeout 是60s
  • 设置了 HttpClient的超时时间, 不设置 ConnectionManager 超时生效
  • 设置了 HttpClient的超时时间, 设置 ConnectionManager 超时不生效 (qiyue-store 就是这样问题)
◆spring-web 版本 4.3.0.RELEASE
  • 默认超时 60s, 因为nginx默认的proxyreadtimeout 是60s
  • 设置了 HttpClient的超时时间, 不设置 ConnectionManager 超时生效
  • 设置了 HttpClient的超时时间, 设置 ConnectionManager 超时生效
◆spring-web 版本 4.3.11.RELEASE
  • 默认超时 60s, 因为nginx默认的 proxyreadtimeout 是60s
  • 设置了 HttpClient的超时时间, 不设置 ConnectionManager 超时生效
  • 设置了 HttpClient的超时时间, 设置 ConnectionManager 超时生效 其实问题就在与不同的版本中 HttpComponentsClientHttpRequestFactory.createRequest 方法的实现逻辑不同。如何不同,自己查看。

〓总结

超时设置至关重要。外部依赖接口调用可以通过Hystrix进行包装。

任何参数的设置都需要验证是否可以正常工作,可以加入到测试环节中,方便在不同的依赖版本中进行验证。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-01-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 〓默认超时设置
  • 〓代码分析
  • 〓RestTemplate超时设置
  • 〓HttpComponentsClientHttpRequestFactory
  • 〓问题代码&测试
  • 〓结论
    • ◆spring-web 版本 3.2.0
      • ◆spring-web 版本 4.0.9.RELEASE
        • ◆spring-web 版本 4.3.0.RELEASE
          • ◆spring-web 版本 4.3.11.RELEASE
          • 〓总结
          相关产品与服务
          腾讯云代码分析
          腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档