1.背景
? ? 最近一個項目中,出現(xiàn)了java.net.ConnectException:Cannotassignrequestedaddress的異常。通過查找資料、分析后,認(rèn)為是由于程序?qū)ν饨⑿逻B接,結(jié)果本地端口已經(jīng)用完導(dǎo)致的異常。
?2.問題原因
? ? 為什么會出現(xiàn)這種情況呢?就要從linux的TCP/IP協(xié)議棧說起了。 先看一下TCP/IP的狀態(tài)圖:
主動關(guān)閉連接的一方,連接會處在TIME-WAIT的狀態(tài)下,需要等2MSL時間后,系統(tǒng)才會回收這條連接,端口才可以繼續(xù)被使用。
? ? 我們的項目場景是需要發(fā)送大量的短連接。這樣在高并發(fā)的場景下,就會出現(xiàn)端口不足,從而拋出java.net.ConnectException:Cannotassignrequestedaddress的異常。
3.解決方案
3.1 橫向擴(kuò)展
? 簡單就是加機(jī)器,減少單臺服務(wù)器的TCP創(chuàng)建次數(shù)。
? 不過這樣需要注意幾個地方:
? ? 第一,項目是否可以支持橫向擴(kuò)展,我們的項目是基于kafka的consumer,很難簡單通過加機(jī)器做到橫向擴(kuò)展。
? ? 第二,需要明確單臺服務(wù)器的處理瓶頸,如果隨著業(yè)務(wù)量的不斷增加,還是會出現(xiàn)這種異常。需要對應(yīng)業(yè)務(wù)量的增加,不斷動態(tài)調(diào)整服務(wù)器數(shù)量。
3.2 調(diào)整linux內(nèi)核參數(shù)
? ? linux內(nèi)核中存在兩個參數(shù):
? ? ? net.ipv4.tcp_tw_reuse = 1表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認(rèn)為0,表示關(guān)閉;
? ? ? net.ipv4.tcp_tw_recycle = 1表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認(rèn)為0,表示關(guān)閉。
? ? 在/etc/sysctl.conf文件中加入上述參數(shù),然后執(zhí)行/sbin/sysctl -p讓參數(shù)生效。
? ? 但是由于我們項目在docker中運(yùn)行,并且很難單純通過增加機(jī)器進(jìn)行橫向擴(kuò)展。當(dāng)時從網(wǎng)上找到了docker調(diào)整網(wǎng)絡(luò)內(nèi)核參數(shù)的方式,但是經(jīng)過試驗(yàn),沒有效果。
3.3 針對該異常進(jìn)行單獨(dú)處理
? ? 如果在對實(shí)時性要求不是特別高的場景下,可以采用如果出現(xiàn)這種異常,就暫停發(fā)送程序,類似一種限流保護(hù)的機(jī)制,等到可以發(fā)送之后,再進(jìn)行發(fā)送。?
? ? 這樣處理,需要能夠明確項目本身或者項目的使用方能夠做緩沖。(我們項目的從kafka拉去消息,暫停發(fā)送后,將消息都緩存到了kafka中,是不存在風(fēng)險的)。
3.4 修改TCP短連接為長連接
? ? 出現(xiàn)這種問題,歸根到底還是因?yàn)樾枰l繁創(chuàng)建大量的連接。那么,可不可以修改一種方式避免,避免頻繁創(chuàng)建大量的TCP短連接。這需要根據(jù)項目的具體原因進(jìn)行評估。
4.總結(jié)
? ? 雖然是java程序員,還是需要了解操作系統(tǒng)的底層細(xì)節(jié)。這樣,出現(xiàn)問題可以從多個角度、多個層次去分析解決。