HttpClient高并發(fā)下性能優(yōu)化-http連接池

首先,明確兩點(diǎn):

1.http連接池不是萬能的,過多的長連接會占用服務(wù)器資源,導(dǎo)致其他服務(wù)受阻
2.http連接池只適用于請求是經(jīng)常訪問同一主機(jī)(或同一個接口)的情況下
3.并發(fā)數(shù)不高的情況下資源利用率低下

那么,當(dāng)你的業(yè)務(wù)符合上面3點(diǎn),那么你可以考慮使用http連接池來提高服務(wù)器性能

使用http連接池的優(yōu)點(diǎn):

1.復(fù)用http連接,省去了tcp的3次握手和4次揮手的時(shí)間,極大降低請求響應(yīng)的時(shí)間
2.自動管理tcp連接,不用人為地釋放/創(chuàng)建連接

使用http連接池的大致流程 :

1.創(chuàng)建PoolingHttpClientConnectionManager實(shí)例
2.給manager設(shè)置參數(shù)
3.給manager設(shè)置重試策略
4.給manager設(shè)置連接管理策略
5.開啟監(jiān)控線程,及時(shí)關(guān)閉被服務(wù)器單向斷開的連接
6.構(gòu)建HttpClient實(shí)例
7.創(chuàng)建HttpPost/HttpGet實(shí)例,并設(shè)置參數(shù)
8.獲取響應(yīng),做適當(dāng)?shù)奶幚?br> 9.將用完的連接放回連接池

public class HttpConnectionPoolUtil {

    private static Logger logger = LoggerFactory.getLogger(HttpConnectionPoolUtil.class);

    private static final int CONNECT_TIMEOUT = Config.getHttpConnectTimeout();// 設(shè)置連接建立的超時(shí)時(shí)間為10s
    private static final int SOCKET_TIMEOUT = Config.getHttpSocketTimeout();
    private static final int MAX_CONN = Config.getHttpMaxPoolSize(); // 最大連接數(shù)
    private static final int Max_PRE_ROUTE = Config.getHttpMaxPoolSize();
    private static final int MAX_ROUTE = Config.getHttpMaxPoolSize();
    private static CloseableHttpClient httpClient; // 發(fā)送請求的客戶端單例
    private static PoolingHttpClientConnectionManager manager; //連接池管理類
    private static ScheduledExecutorService monitorExecutor;

    private final static Object syncLock = new Object(); // 相當(dāng)于線程鎖,用于線程安全

    /**
     * 對http請求進(jìn)行基本設(shè)置
     * @param httpRequestBase http請求
     */
    private static void setRequestConfig(HttpRequestBase httpRequestBase){
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT).build();

        httpRequestBase.setConfig(requestConfig);
    }

    public static CloseableHttpClient getHttpClient(String url){
        String hostName = url.split("/")[2];
        System.out.println(hostName);
        int port = 80;
        if (hostName.contains(":")){
            String[] args = hostName.split(":");
            hostName = args[0];
            port = Integer.parseInt(args[1]);
        }

        if (httpClient == null){
            //多線程下多個線程同時(shí)調(diào)用getHttpClient容易導(dǎo)致重復(fù)創(chuàng)建httpClient對象的問題,所以加上了同步鎖
            synchronized (syncLock){
                if (httpClient == null){
                    httpClient = createHttpClient(hostName, port);
                    //開啟監(jiān)控線程,對異常和空閑線程進(jìn)行關(guān)閉
                    monitorExecutor = Executors.newScheduledThreadPool(1);
                    monitorExecutor.scheduleAtFixedRate(new TimerTask() {
                        @Override
                        public void run() {
                            //關(guān)閉異常連接
                            manager.closeExpiredConnections();
                            //關(guān)閉5s空閑的連接
                            manager.closeIdleConnections(Config.getHttpIdelTimeout(), TimeUnit.MILLISECONDS);
                            logger.info("close expired and idle for over 5s connection");
                        }
                    }, Config.getHttpMonitorInterval(), Config.getHttpMonitorInterval(), TimeUnit.MILLISECONDS);
                }
            }
        }
        return httpClient;
    }

    /**
     * 根據(jù)host和port構(gòu)建httpclient實(shí)例
     * @param host 要訪問的域名
     * @param port 要訪問的端口
     * @return
     */
    public static CloseableHttpClient createHttpClient(String host, int port){
        ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainSocketFactory)
                .register("https", sslSocketFactory).build();

        manager = new PoolingHttpClientConnectionManager(registry);
        //設(shè)置連接參數(shù)
        manager.setMaxTotal(MAX_CONN); // 最大連接數(shù)
        manager.setDefaultMaxPerRoute(Max_PRE_ROUTE); // 路由最大連接數(shù)

        HttpHost httpHost = new HttpHost(host, port);
        manager.setMaxPerRoute(new HttpRoute(httpHost), MAX_ROUTE);

        //請求失敗時(shí),進(jìn)行請求重試
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
                if (i > 3){
                    //重試超過3次,放棄請求
                    logger.error("retry has more than 3 time, give up request");
                    return false;
                }
                if (e instanceof NoHttpResponseException){
                    //服務(wù)器沒有響應(yīng),可能是服務(wù)器斷開了連接,應(yīng)該重試
                    logger.error("receive no response from server, retry");
                    return true;
                }
                if (e instanceof SSLHandshakeException){
                    // SSL握手異常
                    logger.error("SSL hand shake exception");
                    return false;
                }
                if (e instanceof InterruptedIOException){
                    //超時(shí)
                    logger.error("InterruptedIOException");
                    return false;
                }
                if (e instanceof UnknownHostException){
                    // 服務(wù)器不可達(dá)
                    logger.error("server host unknown");
                    return false;
                }
                if (e instanceof ConnectTimeoutException){
                    // 連接超時(shí)
                    logger.error("Connection Time out");
                    return false;
                }
                if (e instanceof SSLException){
                    logger.error("SSLException");
                    return false;
                }

                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)){
                    //如果請求不是關(guān)閉連接的請求
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler).build();
        return client;
    }

    /**
     * 設(shè)置post請求的參數(shù)
     * @param httpPost
     * @param params
     */
    private static void setPostParams(HttpPost httpPost, Map<String, String> params){
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        Set<String> keys = params.keySet();
        for (String key: keys){
            nvps.add(new BasicNameValuePair(key, params.get(key)));
        }
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public static JsonObject post(String url, Map<String, String> params){
        HttpPost httpPost = new HttpPost(url);
        setRequestConfig(httpPost);
        setPostParams(httpPost, params);
        CloseableHttpResponse response = null;
        InputStream in = null;
        JsonObject object = null;
        try {
            response = getHttpClient(url).execute(httpPost, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                String result = IOUtils.toString(in, "utf-8");
                Gson gson = new Gson();
                object = gson.fromJson(result, JsonObject.class);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try{
                if (in != null) in.close();
                if (response != null) response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return object;
    }

    /**
     * 關(guān)閉連接池
     */
    public static void closeConnectionPool(){
        try {
            httpClient.close();
            manager.close();
            monitorExecutor.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

關(guān)鍵的地方有以下幾點(diǎn):
1.httpclient實(shí)例必須是單例,且該實(shí)例必須使用HttpClients.custom().setConnectionManager()來綁定一個PollingHttpClientConnectionManager,這樣該client每次發(fā)送請求都會通過manager來獲取連接,如果連接池中沒有可用連接的話,則該會阻塞線程,直到有可用的連接

2.httpclients4.5.x版本直接調(diào)用ClosableHttpResponse.close()就能直接把連接放回連接池,而不是關(guān)閉連接,以前的版本貌似要調(diào)用其他方法才能把連接放回連接池

3.由于服務(wù)器一般不會允許無限期的長連接,所以需要開啟監(jiān)控線程,每隔一段時(shí)間就檢測一下連接池中連接的情況,及時(shí)關(guān)閉異常連接和長時(shí)間空閑的連接,避免占用服務(wù)器資源.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子器净,更是在濱河造成了極大的恐慌惫叛,老刑警劉巖奕锌,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件著觉,死亡現(xiàn)場離奇詭異,居然都是意外死亡惊暴,警方通過查閱死者的電腦和手機(jī)饼丘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缴守,“玉大人葬毫,你說我怎么就攤上這事÷潘耄” “怎么了贴捡?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長村砂。 經(jīng)常有香客問我烂斋,道長,這世上最難降的妖魔是什么础废? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任汛骂,我火速辦了婚禮,結(jié)果婚禮上评腺,老公的妹妹穿的比我還像新娘帘瞭。我一直安慰自己,他們只是感情好蒿讥,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布蝶念。 她就那樣靜靜地躺著,像睡著了一般芋绸。 火紅的嫁衣襯著肌膚如雪媒殉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天摔敛,我揣著相機(jī)與錄音廷蓉,去河邊找鬼。 笑死马昙,一個胖子當(dāng)著我的面吹牛桃犬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播行楞,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼攒暇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了敢伸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤恒削,失蹤者是張志新(化名)和其女友劉穎池颈,沒想到半個月后尾序,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躯砰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年每币,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琢歇。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡兰怠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出李茫,到底是詐尸還是另有隱情揭保,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布魄宏,位于F島的核電站秸侣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宠互。R本人自食惡果不足惜味榛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望予跌。 院中可真熱鬧搏色,春花似錦、人聲如沸券册。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汁掠。三九已至略吨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間考阱,已是汗流浹背翠忠。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乞榨,地道東北人秽之。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像吃既,于是被迫代替她去往敵國和親考榨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容