Redis 網絡通信及連接機制學習


看了這篇文章

http://blog.nosqlfan.com/html/4153.html

本文所述內容基於 Redis2.6 及以上版本。

注:在客戶端通過 info 命令可以查看服務器版本信息,以及很多其他信息。

> info
# Server
redis_version:3.2.3

 

主要介紹了 Redis 處理客戶端連接的一些內部實現機制,包括連接處理、超時、緩沖區等一系列內容。

 

連接的建立

Redis 通過監聽一個 TCP 端口或者 Unix socket 的方式來接收來自客戶端的連接,當一個連接建立后,Redis 內部會進行以下一些操作:

首先,客戶端 socket 會被設置為非阻塞模式,因為 Redis 在網絡事件處理上采用的是非阻塞多路復用模型。
然后為這個socket 設置 TCP_NODELAY 屬性,禁用 Nagle 算法
然后創建一個 readable 的文件事件用於監聽這個客戶端 socket 的數據發送

上面提到了Nagle算法,看這里基本就可以了 (http://baike.baidu.com/view/2468335.htm)

Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥着許多小數據塊。

Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小於MSS尺寸的數據塊,
所謂“未被確認”,是指一個數據塊發送出去后,沒有收到對方發送的ACK確認該數據已收到。 Nagle算法只允許一個未被ACK的包存在於網絡,它並不管包的大小,因此它事實上就是一個擴展的停
-等協議,只不過它是基於包停-等的,而不是基於字節停-等的。 TCP_NODELAY 選項 默認情況下,發送數據采用Nagle 算法。這樣雖然提高了網絡吞吐量,但是實時性卻降低了,在一些交互性很強的應用程序來說是不允許的,
使用TCP_NODELAY選項可以禁止Nagle 算法。 因為Nagle有時候會很慢: 因為TCP
/IP中不僅僅有nagle算法,還有一個TCP確認延遲機制 。當Server端收到數據之后,它並不會馬上向client端發送ACK,
而是會將ACK的發送延遲一段時間(假設為t),它希望在t時間內server端會向client端發送應答數據,這樣ACK就能夠和應答數據一起發送,
就像是應答數據捎帶着ACK過去。 TCP_CORK 選項 所謂的CORK就是塞子的意思,形象地理解就是用CORK將連接塞住,使得數據先不發出去,等到拔去塞子后再發出去。
設置該選項后,內核會盡力把小數據包拼接成一個大的數據包(一個MTU)再發送出去,當然若一定時間后(一般為200ms,該值尚待確認),
內核仍然沒有組合成一個MTU時也必須發送現有的數據。 Nagle算法與CORK算法區別 Nagle算法關心的是網絡擁塞問題,只要所有的ACK回來則發包。避免網絡因為太多的小包(協議頭的比例非常大)而擁塞(其實就是避免太多包,它不關注包的大小)。 而CORK算法則是為了提高網絡的利用率,使得總體上協議頭占用的比例盡可能的小(它關注包的大小,希望把小包湊成大的包)。 它們的着眼點不一樣。 在用戶控制的層面上,Nagle算法完全不受用戶socket的控制,你只能簡單的設置TCP_NODELAY而禁用它,
CORK算法同樣也是通過設置或者清除TCP_CORK使能或者禁用之。

然后檢查最大連接數:

當客戶端連接被初始化后,Redis會查看目前的連接數,然后對比配置好的 maxclients 值,如果目前連接數已經達到最大連接數 maxclients 了,
那么說明這個連接不能再接收,Redis 會直接返回客戶端一個連接錯誤,並馬上關閉掉這個連接。

注:先建立連接,后檢查,然后再斷開。提高了並發量。

 

服務端處理順序

如果有多個客戶端連接上 Redis,並且都向 Redis 發送命令,那么 Redis 服務端會先處理哪個客戶端的請求呢?

答案其實並不確定,主要與兩個因素有關,
一是客戶端對應的 socket 對應的數字的大小,
二是 kernal 報告各個客戶端事件的先后順序。

Redis 處理一個客戶端傳來數據的步驟如下:

它對觸發事件的 socket 調用一次 read(),只讀一次(而不是把這個 socket 上的消息讀完為止),是為了防止由於某個別客戶端持續發送太多命令,
導致其它客戶端的請求長時間得不到處理的情況。
當然,當這一次 read()調用完成后,它里面無論包含多少個命令,都會被一次性順序地執行。這樣就保證了對各個客戶端命令的公平對待。

 

關於最大連接數 maxclients

在 Redis2.4 中,最大連接數是被直接硬編碼在代碼里面的,
而在2.6版本中這個值變成可配置的。
maxclients 的默認值是 10000,你也可以在 redis.conf 中對這個值進行修改。

Redis 還會照顧到系統本身對進程使用的文件描述符數量的限制。在啟動時 Redis 會檢查系統的 soft limit,以查看打開文件描述符的個數上限。

如果系統設置的數字,小於咱們希望的最大連接數加32,那么這個 maxclients 的設置將不起作用,Redis 會按系統要求的來設置這個值。
(加32是因為 Redis 內部會使用最多32個文件描述符,所以連接能使用的相當於所有能用的描述符號減32)。

當上面說的這種情況發生時(maxclients 設置后不起作用的情況),Redis 的啟動過程中將會有相應的日志記錄。

--maxclients 100000 [41422] 23 Jan 11:28:33.179 # Unable to set the max number of files limit to 100032 (Invalid argument), 
setting the max clients configuration to 10112.

修改soft limit

ulimit -Sn 100000 # This will only work if hard limit is big enough.
sysctl -w fs.file-max=100000

 

輸出緩沖區大小限制

可能是一個簡單的命令,能夠產生體積龐大的返回數據。

另外也有可能因為執行命令太多,產生的返回數據的速率超過了往客戶端發送的速率,這時也會產生消息堆積,
從而造成輸出緩沖區越來越大,占用過多內存,甚至導致系統崩潰。

所以 Redis 設置了一些保護機制來避免這種情況的出現,這些機制作用於不同種類的客戶端,有不同的輸出緩沖區大小限制,限制方式有兩種:

一種是大小限制,當某一個客戶端的緩沖區超過某一大小時,直接關閉掉這個客戶端連接
另一種是當某一個客戶端的緩沖區持續一段時間占用空間過大時,也直接關閉掉客戶端連接

對於不同客戶端的策略如下:

對普通客戶端來說,限制為0,也就是不限制,因為普通客戶端通常采用阻塞式的消息應答模式,如:發送請求,等待返回,再發請求,再等待返回。
這種模式通常不會導致輸出緩沖區的堆積膨脹。
對於 Pub
/Sub 客戶端來說,大小限制是32m,當輸出緩沖區超過32m時,會關閉連接。持續性限制是,當客戶端緩沖區大小持續60秒超過8m,也會導致連接關閉。
而對於 Slave 客戶端來說,大小限制是256m,持續性限制是當客戶端緩沖區大小持續60秒超過64m時,關閉連接。

這樣也學習到了,Redis的客戶端分為三種:普通客戶端、Pub/Sub客戶端、Slave客戶端。

 

輸入緩沖區大小限制

比較暴力,當客戶端傳輸的請求大小超過1G時,服務端會直接關閉連接。

這種方式可以有效防止一些客戶端或服務端 bug 導致的輸入緩沖區過大的問題。

 

Client 超時

對當前的 Redis 版本來說,服務端默認是不會關閉長期空閑的客戶端的。但是你可以修改默認配置來設置你希望的超時時間。
比如客戶端超過多長時間無交互,就直接關閉。同理,這也可以通過 CONFIG SET 命令或者修改 redis.conf 文件來配置。
值得注意的是,超時時間的設置,只對普通客戶端起作用,對 Pub
/Sub 客戶端來說,長期空閑狀態是正常的
另外,實際的超時時間可能不會像設定的那樣精確,這是因為 Redis 並不會采用計時器或者輪訓遍歷的方法來檢測客戶端超時,而是通過一種漸近式的方式來完成,
每次檢查一部分。所以導致的結果就是,可能你設置的超時時間是10s,但是真實執行的時間是超時12s后客戶端才被關閉。

 

CLIENT 命令

其實說的是和CLIENT連接相關的命令。Redis 的 CLIENT 命令能夠實現三種功能:檢查連接的狀態,殺掉某個連接以及為連接設置名字。

比如下面這條命令(實際實驗):

> client list
id=2 addr=10.117.146.21:55330 fd=6 name= age=1759348 idle=0 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf
id=35 addr=10.117.146.21:19654 fd=5 name= age=1422 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client

如上面命令的輸出可知,目前此 Redis 有兩個客戶端連接,每一行表示一個連接的各項信息:

addr: 客戶端的TCP地址,包括IP和端口
fd: 客戶端連接 socket 對應的文件描述符句柄號
name: 連接的名字,默認為空,可以通過 CLIENT SETNAME 設置
age: 客戶端存活的秒數
idle: 客戶端空閑的秒數
flags: 客戶端的類型 (N 表示普通客戶端,更多類型見 http://redis.io/commands/client-list)
omem: 輸出緩沖區的大小
cmd: 最后執行的命令名稱

當你通過上面命令獲取到客戶端列表后,就可以通過 CLIENT KILL 命令來殺死指定的連接了。CLIENT KILL 的參數就是上面的 addr 值。

如上面提到的 CLIENT SETNAME 和 CLIENT GETNAME 可以用來為一個連接設置一個名字。

 

完。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM