?
一、client list
client list命令能列出與Redis服務端相連的所有客戶端連接信息。例如下面代碼是在一個Redis實例上執行client list的結果,其中每一行代表一個客戶端信息:
?
下面將選擇幾個重要的屬性進行說明,其余通過表格的形式進行展示
①標識:id、addr、fd、name
這四個屬性屬于客戶端的標識:
id:客戶端連接的唯一標識,這個id是隨著Redis的連接自增的,重啟 Redis后會重置為0
addr:客戶端連接的ip和端口
fd:socket的文件描述符,與lsof命令結果中的fd是同一個,如果fd=-1代表當前客戶端不是外部客戶端,而是Redis內部的偽裝客戶端
name:客戶端的名字,后面的client setName和client getName兩個命令會對其進行說明
②輸入緩沖區:qbuf、qbuf-free
Redis為每個客戶端分配了輸入緩沖區,它的作用是:將客戶端發送的命令臨時保存,同時Redis從會輸入緩沖區拉取命令并執行,輸入緩沖區為客 戶端發送命令到Redis執行命令提供了緩沖功能,如下圖所示
client list中qbuf和qbuf-free:
這兩個屬性分別代表這個緩沖區的總容量和剩余容量
Redis沒有提供相應的配置來規定每個緩沖區的大小,輸入緩沖區會根據輸入內容大小的不同動態調整,只是要求每個客戶端緩沖區的大小不能超過1G,超過后客戶端將被關閉
下面是Redis源碼中對于輸入緩沖區的硬編碼:
/* Protocol and I/O related defines */
#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
輸入緩沖使用不當會產生兩個問題:
一旦某個客戶端的輸入緩沖區超過1G,客戶端將會被關閉
輸入緩沖區不受maxmemory控制,假設一個Redis實例設置了maxmemory為4G,已經存儲了2G數據,但是如果此時輸入緩沖區使用了3G,已經超過maxmemory限制,可能會產生數據丟失、鍵值淘汰、OOM等情況(如下圖所示)
上圖的執行效果如下:
上面已經看到,輸入緩沖區使用不當造成的危害非常大,那么造成輸入緩沖區過大的原因有哪些?
輸入緩沖區過大主要是因為Redis的處理速度跟不上輸入緩沖區的輸入速度,并且每次進入輸入緩沖區的命令包含了大量 bigkey,從而造成了輸入緩沖區過大的情況
還有一種情況就是Redis發生了阻塞,短期內不能處理命令,造成客戶端輸入的命令積壓在了輸入緩沖區, 造成了輸入緩沖區過大
那么如何快速發現和監控呢?監控輸入緩沖區異常的方法有兩種:
通過定期執行client list命令,收集qbuf和qbuf-free找到異常的連接記錄 并分析,最終找到可能出問題的客戶端。
通過info命令的info clients模塊,找到最大的輸入緩沖區,例如下面命令中的其中client_recent_max_input_buffer代表最大的輸入緩沖區,例如可以設置超過10M就進行報警
上面兩種方法各有自己的優劣勢,下圖對兩種方法進行了對比:
運維提示:輸入緩沖區問題出現概率比較低,但是也要做好防范,在開發中要減少bigkey、減少Redis阻塞、合理的監控報警
③輸出緩沖區:obl、oll、omem
Redis為每個客戶端分配了輸出緩沖區,它的作用是:保存命令執行的結 果返回給客戶端,為Redis和客戶端交互返回結果提供緩沖
與輸入緩沖區不同的是:
輸出緩沖區的容量可以通過參數client-outputbuffer-limit來進行設置
并且輸出緩沖區做得更加細致,按照客戶端的不同分為三種:普通客戶端、發布訂閱客戶端、slave客戶端。如下圖所示
client-output-buffer-limit格式如下。參數意義為:
<class>:客戶端類型,分為三種。a)normal:普通客戶端;b) slave:slave客戶端,用于復制;c)pubsub:發布訂閱客戶端
<hard limit>:如果客戶端使用的輸出緩沖區大于該值,客戶端會被立即關閉
<soft limit>和<soft seconds>:如果客戶端使用的輸出緩沖區超過了并且持續了秒,客戶端會被立即關閉
Redis的默認配置是:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
和輸入緩沖區相同的是,輸出緩沖區也不會受到maxmemory的限制,如果使用不當同樣會造成maxmemory用滿產生的數據丟失、鍵值淘汰、OOM等情況
實際上輸出緩沖區由兩部分組成:
固定緩沖區(16KB):返回比較小的執行結果
動態緩沖區:返回比較大的結果。例如大的字符串、hgetall、smembers命令的結果等,通過Redis源碼中redis.h的redisClient結構體(Redis3.2版本變為Client)可以看到兩個緩沖區的實現細節:
typedef struct redisClient {
// 動態緩沖區列表
list *reply;
// 動態緩沖區列表的長度(對象個數)
unsigned long reply_bytes;
// 固定緩沖區已經使用的字節數
int bufpos;
// 字節數組作為固定緩沖區
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
固定緩沖區使用的是字節數組,動態緩沖區使用的是列表。當固定緩沖區存滿后會將Redis新的返回結果存放在動態緩沖區的隊列中,隊列中的每個對象就是每個返回結果,如下圖所示:
obl、oll、omem:
client list中的obl代表固定緩沖區的長度,oll代表動態緩沖區列表的長度,omem代表使用的字節數
例如下面代表當前客戶端的固定緩沖區的長度為0,動態緩沖區有4869個對象,兩個部分共使用了133081288字節=126M 內存:
id=7 addr=127.0.0.1:56358 fd=6 name= age=91 idle=0 flags=O db=0 sub=0 psub=0 multi=-1
qbuf=0 qbuf-free=0 obl=0 oll=4869 omem=133081288 events=rw cmd=monitor
監控輸出緩沖區的方法依然有兩種:
①通過定期執行client list命令,收集obl、oll、omem找到異常的連接記錄 并分析,最終找到可能出問題的客戶端
②通過info命令的info clients模塊,找到輸出緩沖區列表最大對象數,例如(其中,client_longest_output_list代表輸出緩沖區列表最大對象數):
這兩種統計方法的優劣勢和輸入緩沖區是一樣的,這里就不再贅述了
相比于輸入緩沖區,輸出緩沖區出現異常的概率相對會比較大,那么如何預防呢?方法如下:
進行上述監控,設置閥值,超過閥值及時處理
適當增大slave的輸出緩沖區的,如果master節點寫入較大,slave客戶 端的輸出緩沖區可能會比較大,一旦slave客戶端連接因為輸出緩沖區溢出 被kill,會造成復制重連
限制容易讓輸出緩沖區增大的命令,例如,高并發下的monitor命令就 是一個危險的命令
及時監控內存,一旦發現內存抖動頻繁,可能就是輸出緩沖區過大
限制普通客戶端輸出緩沖區的,把錯誤扼殺在搖籃中,例如可以進行如下設置:
client-output-buffer-limit normal 20mb 10mb 120
④客戶端的存活狀態(age、idle)
client list中的age和idle分別代表:當前客戶端已經連接的時間、最近一次的空閑時間:
例如下面這條記錄代表當期客戶端連接Redis的時間為304秒,其中空閑了0秒:
例如下面這條記錄代表當期客戶端連接Redis的時間為8888581秒,其中空閑了8888581秒。實際上這種就屬于不太正常的情況,當age等于idle時, 說明連接一直處于空閑狀態
演示案例
為了更加直觀地描述age和idle,下面用一個例子進行說明:
String key = "hello";
// 1) 生成jedis,并執行get操作
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.get(key));
// 2) 休息10秒
TimeUnit.SECONDS.sleep(10);
// 3) 執行新的操作ping
System.out.println(jedis.ping());
// 4) 休息5秒
TimeUnit.SECONDS.sleep(5);
// 5) 關閉jedis連接
jedis.close();
下面對代碼中的每一步進行分析,用client list命令來觀察age和idle參數的相應變化(備注:為了與redis-cli的客戶端區分,本次測試客戶端IP地址:10.7.40.98)
1)在執行代碼之前,client list只有一個客戶端,也就是當前的rediscli,下面為了節省篇幅忽略掉這個客戶端。
2)使用Jedis生成了一個新的連接,并執行get操作,可以看到IP地址為 10.7.40.98的客戶端,最后執行的命令是get,age和idle分別是1秒和0秒
3)休息10秒,此時Jedis客戶端并沒有關閉,所以age和idle一直在遞 增:
4)執行新的操作ping,發現執行后age依然在增加,而idle從0計算,也 就是不再閑置
5)休息5秒,觀察age和idle增加:
6)關閉Jedis,Jedis連接已經消失:
⑤客戶端類型(flag)
client list中的flag是用于標識當前客戶端的類型
例如flag=S代表當前客 戶端是slave客戶端、flag=N代表當前是普通客戶端,flag=O代表當前客戶端 正在執行monitor命令。下圖列出了11種客戶端類型:
序號 客戶端類型 說明 l N 普通客戶端 2 M 當前客戶端是master節點 3 s 當前客戶端是slave節點 4 o 當前客戶端正在執行monitor命令 5 x 當前客戶端正在執行事務 6 b 當前客戶端正在等得阻塞事件 7 i 當前客戶端正在等待VM IO,但是此狀態目前已經廢棄不用 8 d 一個受監視的鍵已被修改,EXEC命令將失敷 9 u 客戶端未被阻察 10 c 回復完整輸出后,關閉連接 11 A 盡可能快地關閉連接?
二、client setName和client getName
client setName xx
client setName
client setName用于給客戶端設置名字,這樣比較容易標識出客戶端的來源。例如將當前客戶端命名為test_client,可以執行如下操作:
此時再執行client list命令,就可以看到當前客戶端的name屬性為test_client:
client getName
client getName
如果想直接查看當前客戶端的name,可以使用client getName命令
第一次進入客戶端時,客戶端是沒有名字的,因此名字為空
更改名字之后,就可以看到更改后的名字了。例如:
client getName和setName命令可以做為標識客戶端來源的一種方式,但是通常來講,在Redis只有一個應用方使用的情況下,IP和端口作為標識會更加清晰。當多個應用方共同使用一個Redis,那么此時client setName可以作為標識客戶端的一個依據
三、client kill
client kill ip:port
此命令用于殺掉指定IP地址和端口的客戶端
由于一些原因(例如設置timeout=0時產生的長時間idle的客戶端),需要手動殺掉客戶端連接時,可以使用client kill命令
演示案例
例如左側為一個客戶端(127.0.0.1:34658),右側為一個客戶端(127.0.0.1:34660)
如果想殺掉127.0.0.1:34656的客戶端,可以執行:
執行命令后,client list結果只剩下了127.0.0.1:34658自己這個客戶端:
四、client pause
client pause timeout(毫秒)
client pause命令用于阻塞客戶端timeout毫秒數,在此期間客戶端連接將被阻塞。如下圖所示:
演示案例
例如在一個客戶端執行下面的命令,在之后的10000毫秒內的其他客戶端連接都會被阻塞
過一會后在另一個客戶端執行ping命令,發現整個ping命令執行了2.40秒(手動執行redis-cli,只為了演示,不代表真實執行時間):
該命令可以在如下場景起到作用:
client pause只對普通和發布訂閱客戶端有效,對于主從復制(從節點內部偽裝了一個客戶端)是無效的,也就是此期間主從復制是正常進行的, 所以此命令可以用來讓主從復制保持一致
client pause可以用一種可控的方式將客戶端連接從一個Redis節點切換到另一個Redis節點
需要注意的是在生產環境中,暫停客戶端成本非常高
五、monitor
monitor命令用于監控Redis正在執行的命令?
演示案例
如下圖所示:
我們打開了兩個redis-cli,右側先執行monitor命令,左側再執行其他命令
可以看到monitor命令能夠監聽其他客戶端正在執行的命令,并記錄了詳細的時間戳
注意事項:monitor的作用很明顯,如果開發和運維人員想監聽Redis正在執行的命令,就可以用monitor命令,但事實并非如此美好,每個客戶端都有自己的輸出緩沖區,既然monitor能監聽到所有的命令,一旦Redis的并發量過大, monitor客戶端的輸出緩沖會暴漲,可能瞬間會占用大量內存。