全鏈路壓測出現大量TIME_WAIT

公司接了一個快遞行業的單子,爲了保證局方能在雙十一期間系統能順利運行,也爲了證實我們應用能夠抗下雙十一的峯值,於是配合局方做了一次應對雙十一流量高峯全鏈路壓測。模擬局方接口發起sip協議,通過華爲雲的接入,並獲取sip-calluuid轉換爲sip頭,經防火牆、負載均衡器最終負載到每臺機器,其中一臺服務器的負載併發量爲1200路併發,持續壓測了十幾分鍾後,明顯發現服務響應時間變慢,通過promotheus監控查看到調局方接口的響應延遲5+ minutes,但我們應用用的RestTemplate設置了讀超時時間和寫超時時間是5s,怎麼算也不會到5m。通過lsof -I命令查看發現大量TCP請求出現了Time_Wait

爲什麼出現大量的Time_wait,這個還得從TCP的四次揮手說起,當tcp連接發起斷開請求的時候並不是立馬斷開,而是主動關閉的一方先發起FIN請求,等被動方進入CLOSE_WAIT後,主動放將TCP狀態改爲TIME_WAIT,等待2MSL後才最終關閉TCP連接。那麼TCP爲什麼設計TIME_WAIT,有兩點好處:1,可靠地實現TCP全雙工連接地終止;2,運行老地重發分節在網絡中消逝。那麼長連接出現大量地TIME_WAIT是爲了保證雙工通信時可靠的,我們java的http請求都是短鏈接,我們知道http請求是無狀態的,這裏TIME_WAIT是維持2MSL,這個可以查閱《TCP/IP詳解》,

 

 

翻閱網絡上的資料,如果linux系統中出現大量的TIME_WAIT,修改linux系統的內核參數可解決此問題。步驟如下:

vi /etc/sysctl.conf,減小keepalive的連接時間,加大SYNC隊列的長度,容納更多的網絡連接數。

 

net.ipv4.tcp_keepalive_time = 1200

#表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改爲20分鐘。

net.ipv4.ip_local_port_range = 1024 65000

#表示用於向外連接的端口範圍。缺省情況下很小:32768到61000,改爲1024到65000。

net.ipv4.tcp_max_syn_backlog = 8192

#表示SYN隊列的長度,默認爲1024,加大隊列長度爲8192,可以容納更多等待連接的網絡連接數。

net.ipv4.tcp_max_tw_buckets = 5000

#表示系統同時保持TIME_WAIT套接字的最大數量,如果超過這個數字,TIME_WAIT套接字將立刻被清除並打印警告信息。

默認爲180000,改爲5000。對於Apache、Nginx等服務器,上幾行的參數可以很好地減少TIME_WAIT套接字數量,但是對於 Squid,效果卻不大。此項參數可以控制TIME_WAIT套接字的最大數量,避免Squid服務器被大量的TIME_WAIT套接字拖死。

 

修改linux參數後,再次開始壓測,但情況並不像資料說的那麼好,過了半個小時後請求又出現延遲,雖然情況好了很多,但已經超過了我們期望的每個請求響應時長在5s內,繼續查閱資料資料,發現我們使用的RestTemplate內部還是HttpClient,如果沒有配置連接數和併發數,默認的值少得可憐,修改內核中HttpClient的連接數,繼續壓測一切順利,每次響應時間在2s內。

 

   /**

     * 發送http請求,響應超時時間設置相對較長

     * 應用於推送數據等對結果無依賴

     * @return

     */

    @Bean

    public RestTemplate restTemplate() {

        // 長連接保持30秒

        PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);

        // 總連接數

        pollingConnectionManager.setMaxTotal(800);

        // 同路由的併發數

        pollingConnectionManager.setDefaultMaxPerRoute(800);

 

        HttpClientBuilder httpClientBuilder = HttpClients.custom();

        httpClientBuilder.setConnectionManager(pollingConnectionManager);

        // 重試次數,默認是3次,沒有開啓

        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true));

        // 保持長連接配置,需要在頭添加Keep-Alive

        httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());

 

        List<Header> headers = new ArrayList<>();

        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));

        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));

        headers.add(new BasicHeader("Accept-Language", "zh-CN"));

//        headers.add(new BasicHeader("Connection", "Keep-Alive"));

 

        httpClientBuilder.setDefaultHeaders(headers);

 

        HttpClient httpClient = httpClientBuilder.build();

 

        // httpClient連接配置,底層是配置RequestConfig

        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

        // 連接超時

        clientHttpRequestFactory.setConnectTimeout(config.getConnectionTimeOut() );

        // 數據讀取超時時間,即SocketTimeout

        clientHttpRequestFactory.setReadTimeout(config.getSoketTimeOut());

        // 連接不夠用的等待時間,不宜過長,必須設置,比如連接不夠用時,時間過長將是災難性的

        clientHttpRequestFactory.setConnectionRequestTimeout(200);

        // 緩衝請求數據,默認值是true。通過POST或者PUT大量發送數據時,建議將此屬性更改爲false,以免耗盡內存。

        // clientHttpRequestFactory.setBufferRequestBody(false);

 

        RestTemplate restTemplate = new RestTemplate();

        restTemplate.setRequestFactory(clientHttpRequestFactory);

        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

        return restTemplate;

    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章