restTemplate做长连接

restTemplate是springboot自带的http请求客户端,里面包装了HttpClient,是在SpringBoot项目中进行http请求常用的方式,本篇文章主要是讲解怎么进行restTemplate的长连接

一、普通情况下的RestTemplate配置

    @Bean
    public RestTemplate AchiementTemplateLongConnection(RestTemplateBuilder builder) {
         return builder.build();
    }

二、长连接的配置

下面说下怎么进行RestTemplate的配置

    @Bean
    public RestTemplate AchiementTemplateLongConnection(RestTemplateBuilder builder) {
         return builder.requestFactory(()->httpComponentsClientHttpRequestFactory())
                .build();
    }

    @Bean
    public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient(poolingHttpClientConnectionManager(),requestConfig()));
        return factory;
    }

    @Bean
    public RequestConfig requestConfig() {
        RequestConfig result = RequestConfig.custom()
                .setConnectionRequestTimeout(0)
                .setConnectTimeout(CONNECTION_TIMEOUT)
                .setSocketTimeout(CONNECTION_TIMEOUT)
                .build();

        return result;
    }

    @Bean
    public CloseableHttpClient httpClient(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager, RequestConfig requestConfig) {
        CloseableHttpClient result = HttpClientBuilder
                .create()
                .setConnectionManager(poolingHttpClientConnectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(new AchivmentGetHttpRequestRetryHandler(1,true))
                .build();

        return result;
    }

    @Bean
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setValidateAfterInactivity(2000);
        return connectionManager;
    }

下面对几个方法返回的类进行解释下:

  • httpClient:http请求的客户端
  • RequestConfig:httpClient请求的配置包括超时时间等
  • PoolingHttpClientConnectionManager:设置连接池,节省了创建连接的开销

三、长连接中需要注意的问题

试想一个问题,长连接是有时间的,不管是8小时还是2小时,当连接断开了,怎么重连是需要处理的,有两种方式可以进行处理:

  1. 连接之前先校验下连接是否通着,mysql的长连接就是通过ping命令去校验的
  2. 做重试,RestTemplate就是这么做的,捕获IOException,然后进行重试,因为之前断开的连接在第一次失败后,被销毁了,后面再一次重试即可成功了。

==注:==这里要特别说明下,httpComponents包里面并没有那么傻,自己断开的连接自己肯定可以感知的,但是当你是客户端,而服务端单方面断开连接的时候,就感知不到了。

最开始我是在检查,我代码中已经设置的检验,为何还是检验不出来呢,我设置检验的代码如下:

connectionManager.setValidateAfterInactivity(2000);

为何检验的代码没有生效呢?原因在这里:BHttpConnectionBase类的方法


    @Override
    public boolean isStale() {
        if (!isOpen()) {
            return true;
        }
        try {
            final int bytesRead = fillInputBuffer(1);
            return bytesRead < 0;
        } catch (final SocketTimeoutException ex) {
        //如果是SocketTimeoutException
            return false;
        } catch (final IOException ex) {
            return true;
        }
    }

我们看下当服务端关闭连接的时候,客户端报什么异常:

java.net.SocketInputStream.socketRead0(Native Method)抛出了timeoutException

正好是返回连接没有问题,就是说这个检验是无效的。

那这个时候只能重试了:
在RestTemplate中使用了DefaultHttpRequestRetryHandler类进行重试,里面有一个retryRequest方法就是用来判断是否重试的。里面有这么一段代码

        if (handleAsIdempotent(request)) {
            // Retry if the request is considered idempotent
            return true;
        }

这段代码是判断是否有消息体,如果有消息体则不能重试,这是为了避免如果是插入或者修改的方法,不能轻易重试,否则会有风险。但是如果你的查询请求是post,那么也不会重试,可以自己实现一个retryHandler解决此问题。

四、最后

我贴上mysql的ping逻辑,在此方法中:
com.mysql.jdbc.ConnectionImpl#execSQL(com.mysql.jdbc.StatementImpl, java.lang.String, int, com.mysql.jdbc.Buffer, int, int, boolean, java.lang.String, com.mysql.jdbc.Field[], boolean)

public ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows, Buffer packet, int resultSetType, int resultSetConcurrency,
            boolean streamResults, String catalog, Field[] cachedMetadata, boolean isBatch) throws SQLException {
        synchronized (getConnectionMutex()) {
            //
            // Fall-back if the master is back online if we've issued queriesBeforeRetryMaster queries since we failed over
            //

            long queryStartTime = 0;

            int endOfQueryPacketPosition = 0;

            if (packet != null) {
                endOfQueryPacketPosition = packet.getPosition();
            }

            if (getGatherPerformanceMetrics()) {
                queryStartTime = System.currentTimeMillis();
            }

            this.lastQueryFinishedTime = 0; // we're busy!

            if ((getHighAvailability()) && (this.autoCommit || getAutoReconnectForPools()) && this.needsPing && !isBatch) {
                try {
                    pingInternal(false, 0);

                    this.needsPing = false;
                } catch (Exception Ex) {
                    createNewIO(true);
                }
            }

`RestTemplate` 是 Spring Boot 中用于发送 HTTP 请求的一个工具类,它封装了多个与网络通信相关的功能,并且支持异步操作。默认情况下,`RestTemplate` 的连接建立、请求发送和响应接收等操作都有一定的超时限制。这些超时设置包括连接超时、读取超时、写入超时等。 为了进行长连接并调整超时时间,你可以通过以下几种方式之一来进行配置: ### 1. 使用 `HttpClientProperties` 配置 Spring 提供了一个名为 `HttpClientProperties` 的类,可以用来自定义 HttpClient 的配置。对于 RestTemplate 来说,这通常意味着配置的是整个应用级别的超时属性,而不是针对某个具体的 REST API 调用。 ```java @Configuration public class HttpClientConfig { @Bean public ClientHttpRequestFactory clientHttpRequestFactory() { // 创建一个 HttpClientConfigurer 实例,并配置需要的超时属性 HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); HttpClientBuilder builder = HttpClients.custom(); // 设置全局超时时间(例如,设置总连接超时时间为5秒) int connectionTimeoutMs = 5000; int socketTimeoutMs = 60000; // 设置读取超时时间为60秒 builder.setConnectionManager(new PoolingHttpClientConnectionManager()); builder.setDefaultRequestConfig(RequestConfig.custom() .setConnectTimeout(connectionTimeoutMs) .setSocketTimeout(socketTimeoutMs) .build()); return factory; } } ``` ### 2. 直接在 `RestTemplate` 对象上设置超时 如果你想针对特定的 `RestTemplate` 实例或 API 调用进行超时时间的调整,可以在创建该实例时直接提供一个带有自定义超时属性的 `ClientHttpRequestFactory`。 ```java @Bean public RestTemplate restTemplate(HttpClientProperties properties) { // 获取自定义的 HttpClientConfigurer 实例 ClientHttpRequestFactory requestFactory = httpClientConfigurer(properties).getFactory(); // 创建一个带有自定义超时属性的 RestTemplate 实例 RestTemplate restTemplate = new RestTemplate(requestFactory); // 这样的效果是仅对这个 restTemplate 的请求应用上述超时配置 return restTemplate; } ``` ### 关联问题: 1. **如何检查`RestTemplate`的超时配置是否生效?** 可以通过模拟一次远程调用,并检查返回的结果或异常信息来验证配置是否按预期工作。如果配置不当,可能会遇到超时导致的异常。 2. **为什么设置长连接超时很重要?** 设置合适的超时时间有助于避免因长时间等待无响应而引起的资源浪费,同时也能提高系统的稳定性及响应速度。过短的时间可能导致系统频繁尝试重建连接,而过长则会增加延迟,影响用户体验和性能。 3. **在高并发场景下,如何优化`RestTemplate`的使用?** 在高并发环境下,合理利用线程池管理连接、适当提升超时阈值、以及采用批处理机制可以有效提高系统的整体吞吐量和效率。同时,考虑使用更高级的功能如 `WebClient` 或者自定义的 HTTP 客户端,这些客户端可能提供了更多的并发控制选项和优化策略。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值