HTTP 連接池的使用

一、HTTP 的長短連接問題

HTTP1.0 當時用的還是短連接的方式,就是每次的請求都要建立一次TCP連接,創建TCP連接和關閉TCP的連接都是耗時的過程。

HTTP1.1 則對HTTP1.0做了很大的改進,默認使用的是長連接的方式。減少了建立連接和關閉連接的消耗。

二、httpClient 的使用

後端發送HTTP請求,一般使用的是apache裏面的這個jar包

  <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
       <version>4.5.10</version>
 </dependency>

不使用連接池的方式



import ch.qos.logback.core.encoder.EchoEncoder;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.Test;


public class HttpClientWithoutPoolTest extends BaseHttpClientTest {

    @Test
    public void test() throws Exception {
        startUpAllThread(getRunThreads(new HttpTread()));

        // 等待線程運行
        for (;;){}
    }


    private class HttpTread implements Runnable {
        @Override
        public void run() {

            // HttpClient 是線程安全的,正常使用應當用全局變量,但是一旦全局共用一個,httpclient內部構建的時候後new一個連接池。
            // 這個是故意這個使用保證每次new 一個新的連接,不用線程池。
            CloseableHttpClient httpClient = HttpClients.custom().build();
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
            long startTime = System.currentTimeMillis();
            try {
                CloseableHttpResponse response = httpClient.execute(httpGet);
                if (response != null) {
                    response.close();
                }
            } catch (Exception e) {

            }finally {

                addCost(System.currentTimeMillis() - startTime);
                if (NOW_COUNT.incrementAndGet() == REQUEST_COUNT) {
                    System.out.println(EVERY_REQ_COST.toString());
                }
            }

        }
    }
}

使用連接池的方式




import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.junit.Before;
import org.junit.Test;


public class HttpClientWithPoolTest extends BaseHttpClientTest {

    private CloseableHttpClient httpClient = null;

    @Before
    public void before() {
        initHttpClient();
    }



    @Test
    public void test() throws Exception {
        startUpAllThread(getRunThreads(new HttpTread()));

        // 等待線程運行
        for (;;){}
    }
    private class HttpTread implements Runnable {
        @Override
        public void run() {
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
            long startTime = System.currentTimeMillis();
            try {
                CloseableHttpResponse response = httpClient.execute(httpGet);
                if (response != null) {
                    response.close();
                }
            } catch (Exception e) {

            }finally {
                addCost(System.currentTimeMillis() - startTime);
                if (NOW_COUNT.incrementAndGet() == REQUEST_COUNT) {
                    System.out.println(EVERY_REQ_COST.toString());
                }
            }

        }
    }
    private void initHttpClient() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        //總連接池數
        connectionManager.setMaxTotal(1);

        //給每個域名設置單獨的連接池數量
        connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost("www.baidu.com")), 1);

        //setConnectTimeout 設置連接超時時間
        //setConnectionRequestTimeout 從連接池中拿連接的等待超時時間
        //setSocketTimeout 發出請求後等待端應答的超時時間
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(1000)
            .setConnectionRequestTimeout(2000)
            .setSocketTimeout(3000).build();
        //重試機制
        HttpRequestRetryHandler retryHandler = new StandardHttpRequestRetryHandler();

        httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .setDefaultRequestConfig(requestConfig)
            .setRetryHandler(retryHandler)
            .build();

        /**
         * 服務器端假設關閉了連接,對客戶端是不透明的,httpClient爲了解決這個問題,在某個連接使用之前會檢測這個連接是否超時,如果過時則失效
         * 這個做法會給每個請求增加一定的開銷,因此有個定時器任務專門回收不活動且失效的連接,可以一定程度上解決這個問題。
         */
        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
            new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    // 關閉失效的鏈接並從連接池中移除
                    connectionManager.closeExpiredConnections();
                    //關閉20秒內不活動的連接並從連接池中移除
                    connectionManager.closeIdleConnections(20,TimeUnit.SECONDS);
                } catch (Throwable e) {
                    e.printStackTrace();
                }

            }
        },0,5, TimeUnit.SECONDS);



    }
}

結論:使用連接池的方式可以提升整體的性能。

發佈了44 篇原創文章 · 獲贊 13 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章