從Jedis看TCP常用的參數設置

個人博客請訪問 http://www.x0100.top                  

背景

由於微服務的興起,以及公司內部各種服務間的各種調用,最近也遇過幾次線上大量TIME_WAIT的問題,雖然知道怎麼解決,但也點燃了我對tcp協議重新溫習學習的興趣。

從何學習呢?重新看下幾百多頁的《TCP/IP詳解》,那太花時間了,以前頁看過一次。不如從一些優秀的開源項目裏,看下他們是怎麼使用tcp建立連接的?那就從jedis開始吧。😃

源碼分析步驟

下載源代碼

git clone https://github.com/xetorthio/jedis.git
git checkout tags/jedis-2.9.0

分析

從最簡單的jedis.set追到底層的tcp網絡編程吧

時序圖

image.png

核心tcp參數設置

socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method
socket.setSoTimeout(soTimeout);

1 setReuseAddress

可以看出setReuseAddress本質上就是對SO_REUSEADDR的設置

public void setReuseAddress(boolean on) throws SocketException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    getImpl().setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(on));
}

SO_REUSEADDR主要的作用(其它作用)就是對TIME_WAIT連接的重用,對於jedis,作者應該是希望客戶端重啓後,重新創建到redis服務器的tcp連接能夠重用之前的接口。因爲客戶端重啓時,活躍的jedis連接由於時客戶端主動關閉,會持續2*MSL的TIME_WAIT狀態,在此期間會一直佔用着客戶端端口。MSL的大小可通過以下命令查看

sysctl -a | grep tcp_fin_timeout
tcp_fin_timeout = 60

2 setKeepAlive

SO_KEEPALIVE是tcp利用心跳機制保持連接的存活,假如連接已經斷開,則會響應錯誤碼Broken Pipe給上層應用,由於keepalive發起心跳包的開始實際比較遲,對於調整後的linux操作系統仍然需要5分鐘,數值過小又會導致過多無用的包發出,5分鐘的時間長度發出後,通常也會由於各種各樣的原因連接早已被銷燬,例如客戶端服務器端的連接經過了lvs,haproxy各種各樣的代理,代理服務器本身也會有個keepalive的最長時間。一般業務對於SO_KEEPALIVE的依賴比較小,假如需要保持連接的話,會自己進行一些存活性的維持和判斷,例如

  1. 定期小週期發送心跳包keep alive

  2. 在使用連接前進行判斷,例如連接池通常採用的testOnBorrow, testOnReturn之類的機制。

sudo sysctl -a | grep tcp_keepalive
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 300 //通常默認值爲7200

3 setTcpNoDelay

TCP_NODELAY的作用就是禁用Nagle,減少由於小包帶來的推遲發送的延遲

If set, disable the Nagle algorithm. This means that segments are always sent as soon as possible, even if there is only a small amount of data. When not set, data is buffered until there is a suffi‐ cient amount to send out, thereby avoiding the frequent sending of small packets, which results in poor utilization of the network. This option is overridden by TCP_CORK; however, setting this option forces an explicit flush of pending output, even if TCP_CORK is currently set.

4 setSoLinger(true, 0)

這個選項的設置就是jedis關閉的時候直接發送RST,而不是按照正常的4次揮手的關閉流程,避免了客戶端TIME_WAIT的情況,但不是一個很好的pratice,因爲服務端會只會收到RST,這裏留下一個問題,redis是如何處理rst的,是否是直接忽略,不然就會堆積很多錯誤日誌?

5 socket.setSoTimeout(soTimeout)

SO_TIMEOUT的作用就是設置以下三個socket操作的超時時間,很明顯,ServerSocket.accept()是針對服務端而言,DatagramSocket.receive()是針對UDP,在這裏jedis生效的是SocketInputStream.read(),作用就是jedis發送命令後,開始讀redis請求的響應時間。

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

總結

tcp是一個非常複雜的網絡協議,但對於“客戶端”的tcp編程其實也不是特別難,從jedis在tcp的使用上看,直接用了blocking io,同時設置了5個參數,就滿足了大部分場合的使用,對於一般互聯網的服務,也只是使用多一個池化JedisPool而已。

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