深入Redis客戶端(redis客戶端屬性、redis緩沖區(qū)、關(guān)閉redis客戶端)
Redis 數(shù)據(jù)庫(kù)采用 I/O 多路復(fù)用技術(shù)實(shí)現(xiàn)文件事件處理器,服務(wù)器采用單線程單進(jìn)程的方式來處理多個(gè)客戶端發(fā)送過來的命令請(qǐng)求,它同時(shí)與多個(gè)客戶端建立網(wǎng)絡(luò)通信。服務(wù)器會(huì)為與它相連接的客戶端創(chuàng)建相應(yīng)的 redis.h/redisClient 結(jié)構(gòu),在這個(gè)結(jié)構(gòu)中保存了當(dāng)前客戶端的相關(guān)屬性及執(zhí)行相關(guān)功能時(shí)的數(shù)據(jù)結(jié)構(gòu)。
I/O 多路復(fù)用:linux有五類io模型 1.阻塞 2.非阻塞 3.io多路復(fù)用 4.事件驅(qū)動(dòng) 5.異步
阻塞:一次網(wǎng)絡(luò)io時(shí),C端發(fā)出請(qǐng)求,S端收到。當(dāng)C端發(fā)出一個(gè)請(qǐng)求,進(jìn)行io時(shí),就不能進(jìn)行其他操作了,需要同步的等待結(jié)果的返回。
io多路復(fù)用:有多個(gè)C端同時(shí)發(fā)送請(qǐng)求,這些IO操作會(huì)被selector(epoll,kqueue)給暫時(shí)掛起,入內(nèi)存隊(duì)列。此時(shí)S端可以自己選擇什么時(shí)候讀取、處理這些io,也就是說S端可以同時(shí)hold住多個(gè)io。
redis的文本事件處理器

客戶端的名字、套接字、標(biāo)志和時(shí)間屬性
127.0.0.1:6379> client list
id=2 addr=127.0.0.1:53555 fd=9 name= age=6 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
-
名字:在默認(rèn)情況下,連接到 Redis 服務(wù)器的客戶端是沒有名字(name屬性, 如上)的
給它設(shè)置名字
127.0.0.1:6379> client setname "local_client" OK 127.0.0.1:6379> client list id=2 addr=127.0.0.1:53555 fd=9 name=local_client age=110 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如果沒有為客戶端設(shè)置名字,那么這個(gè)客戶端的 name 屬性將會(huì)指向 NULL 指針;而如果為這個(gè)客戶端設(shè)置了名字,那么這個(gè)客戶端的 name 屬性將會(huì)指向一個(gè)字符串對(duì)象,這個(gè)對(duì)象中保存了客戶端的名字
套接字:
客戶端套接字由客戶端狀態(tài)的 fd 屬性記錄
當(dāng) fd 屬性值為-1 時(shí),表示這個(gè)客戶端是偽客戶端。偽客戶端的請(qǐng)求命令不是來源于網(wǎng)絡(luò)的,而是來源于 Lua 腳本或 AOF 文件(后續(xù)詳細(xì)介紹)的,所以偽客戶端不需要套接字連接,它也沒有套接字描述符。當(dāng)我們執(zhí)行的 Lua 腳本中含有 Redis 命令,或者使用 AOF 文件來還原數(shù)據(jù)庫(kù)狀態(tài)時(shí),就會(huì)用到偽客戶端。
當(dāng) fd 屬性值是大于-1 的整數(shù)時(shí),表示這個(gè)客戶端是普通客戶端。普通客戶端采用相關(guān)套接字來實(shí)現(xiàn)與服務(wù)器的通信,因此服務(wù)器會(huì)利用 fd 屬性來記錄客戶端套接字的描述符。
-
標(biāo)志屬性
客戶端的標(biāo)志屬性 flags 用來記錄客戶端的角色(Role)及客戶端目前所處的狀態(tài)。
flags 屬性的取值可以是單個(gè)標(biāo)志,也可以是多個(gè)二進(jìn)制或的組合標(biāo)志,具體如下。
單個(gè)標(biāo)志:flags=<flag>
組合標(biāo)志:flags=<flag1>|<flag2>|<flag3>|…
標(biāo)志使用常量來表示。Redis 所具有的所有標(biāo)志都定義在 redis.h 文件中。
記錄客戶端角色的標(biāo)志有如下幾個(gè)。
● 在利用 Redis 主從服務(wù)器實(shí)現(xiàn)復(fù)制時(shí),主從服務(wù)器會(huì)相互成為對(duì)方的客戶端,也就是從服務(wù)器是主服務(wù)器的客戶端,同時(shí)主服務(wù)器也是從服務(wù)器的客戶端。Redis 使用REDIS_MASTER 標(biāo)志來表示這個(gè)客戶端是主服務(wù)器,而使用 REDIS_SLAVE 標(biāo)志來表示另一個(gè)客戶端是從服務(wù)器。
● Redis 使用 REDIS_LUA_CLIENT 標(biāo)志來表示該客戶端是一個(gè)專門用于處理 Lua 腳本的偽客戶端,它主要用于執(zhí)行 Lua 腳本中包含的 Redis 命令。
● Redis 使用 REDIS_PRE_PSYNC 標(biāo)志來表示該客戶端是一個(gè)低于 Redis 2.8 版本的從服務(wù)器,此時(shí),對(duì)應(yīng)的主服務(wù)器不能使用 PSYNC 命令實(shí)現(xiàn)與從服務(wù)器的數(shù)據(jù)同步。只有當(dāng) REDIS_SLAVE 標(biāo)志處于打開狀態(tài)時(shí),才能使用 REDIS_PRE_PSYNC 標(biāo)志。
記錄客戶端當(dāng)前狀態(tài)的標(biāo)志有如下幾個(gè)。
● REDIS_ASKING 標(biāo)志表示客戶端向運(yùn)行在集群模式下的服務(wù)器節(jié)點(diǎn)發(fā)送了 ASKING 命令。
● REDIS_CLOSE_ASAP 標(biāo)志表示客戶端的輸出緩沖區(qū)過大,超出了服務(wù)器所允許的范圍。當(dāng)服務(wù)器在下一次執(zhí)行 serverCron 函數(shù)時(shí),會(huì)關(guān)閉這個(gè)輸出緩沖區(qū)過大的客戶端,以此來保證服務(wù)器的穩(wěn)定性不受這個(gè)客戶端影響。在關(guān)閉的時(shí)候,存儲(chǔ)在這個(gè)緩沖區(qū)中的數(shù)據(jù)也會(huì)被刪除,并且不會(huì)給客戶端返回任何信息。
● REDIS_CLOSE_AFTER_REPLY 標(biāo)志表示客戶端給服務(wù)器發(fā)送的命令請(qǐng)求中有錯(cuò)誤的協(xié)議內(nèi)容,或者用戶在客戶端中執(zhí)行了 CLIENT kill 命令。此時(shí)服務(wù)器會(huì)將客戶端輸出緩沖區(qū)中存儲(chǔ)的所有數(shù)據(jù)內(nèi)容發(fā)送給客戶端,然后關(guān)閉這個(gè)客戶端。
● REDIS_DIRTY_CAS 標(biāo)志表示事務(wù)使用 WATCH 命令監(jiān)視的數(shù)據(jù)庫(kù)鍵已經(jīng)被修改。
● REDIS_DIRTY_EXEC 標(biāo)志表示事務(wù)在命令入隊(duì)時(shí)出現(xiàn)錯(cuò)誤。
REDIS_DIRTY_CAS 和 REDIS_DIRTY_EXEC 標(biāo)志的出現(xiàn)都表示 Redis 事務(wù)的安全性已被破壞。只要這兩個(gè)標(biāo)志中的任何一個(gè)被打開,EXEC 命令都會(huì)執(zhí)行失敗。而只有在客戶端打開了 REDIS_MULTI 標(biāo)志的情況下,才能使用這兩個(gè)標(biāo)志。
● REDIS_MULTI 標(biāo)志表示客戶端正處于執(zhí)行事務(wù)的狀態(tài)中。
● REDIS_MONITOR 標(biāo)志表示客戶端正處于執(zhí)行 MONITOR 命令的狀態(tài)中。
● REDIS_FORCE_AOF 標(biāo)志表示讓服務(wù)器將當(dāng)前正在執(zhí)行的命令強(qiáng)制寫入 AOF 文件中。在執(zhí)行 PUBSUB 命令時(shí),會(huì)使客戶端打開 REDIS_FORCE_AOF 標(biāo)志。
● REDIS_FORCE_REPL 標(biāo)志表示強(qiáng)制讓主服務(wù)器將當(dāng)前正在執(zhí)行的命令復(fù)制給所有與它連接的從服務(wù)器。當(dāng)執(zhí)行 SCRIPT LOAD 命令時(shí),會(huì)使客戶端同時(shí)開啟 REDIS_FORCE_AOF 和 REDIS_FORCE_REPL 標(biāo)志。如果要實(shí)現(xiàn)主從服務(wù)器可以正確地載入 SCRIPT LOAD 命令指定的腳本,那么服務(wù)器必須使用 REDIS_FORCE_REPL 標(biāo)志,讓主服務(wù)器強(qiáng)制將 SCRIPT LOAD 命令分發(fā)給相應(yīng)的從服務(wù)器。
● REDIS_UNIX_SOCKET 標(biāo)志表示服務(wù)器連接客戶端使用的是 UNIX 套接字。
● REDIS_BLOCKED 標(biāo)志表示客戶端正處于被 BRPOP、BLPOP 等命令阻塞的狀態(tài)中。
● REDIS_UNBLOCKED 標(biāo)志表示客戶端不再阻塞,它從 REDIS_BLOCKED 標(biāo)志的阻塞狀態(tài)中脫離出來。只有在 REDIS_BLOCKED 標(biāo)志被打開的情況下,才能使用 REDIS_UNBLOCKED 標(biāo)志。
● REDIS_MASTER_FORCE_REPLY 標(biāo)志:在主從服務(wù)器進(jìn)行命令交互的過程中,從服務(wù)器需要向主服務(wù)器發(fā)送 REPLICATION ACK 命令。但是,在發(fā)送此命令之前,從服務(wù)器必須開啟主服務(wù)器對(duì)應(yīng)的客戶端的 REDIS_MASTER_FORCE_REPLY 標(biāo)志;否則主服務(wù)器會(huì)拒絕執(zhí)行從服務(wù)器發(fā)送的 REPLCATION ACK 命令。
-
時(shí)間屬性
● ctime 屬性:該屬性記錄了客戶端被創(chuàng)建的時(shí)間。利用這個(gè)時(shí)間可以計(jì)算出這個(gè)客戶端與服務(wù)器相連接的時(shí)間,單位為秒。在執(zhí)行 CLIENT list 命令后,返回的 age 域記錄了連接秒數(shù)
● lastinteraction 屬性:該屬性記錄了客戶端與服務(wù)器最后一次交互的時(shí)間。交互就是兩者之間互相發(fā)送命令請(qǐng)求與返回結(jié)果。利用 lastinteraction 屬性可以計(jì)算出客戶端的空轉(zhuǎn)時(shí)間,也就是在進(jìn)行最后一次交互之前過去了多少時(shí)間,單位為秒。CLIENT list 命令返回的 idle 域記錄了這個(gè)時(shí)間。當(dāng) idle 的值為 0 時(shí),表示空轉(zhuǎn)時(shí)間為 0 秒。
● obuf_soft_limit_reached_time 屬性:該屬性記錄了客戶端輸出緩沖區(qū)第一次達(dá)到軟性限制的時(shí)間
redis客戶端的緩沖區(qū)
一些概念
● 輸入緩沖區(qū):用于保存客戶端發(fā)送的命令請(qǐng)求。輸入緩沖區(qū)的大小是動(dòng)態(tài)變化的,它會(huì)根據(jù)輸入的內(nèi)容動(dòng)態(tài)縮小或增大。1GB 是輸入緩沖區(qū)的最大大小。如果輸入緩沖區(qū)的大小超過了 1GB,那么這個(gè)客戶端將會(huì)被關(guān)閉。
● 輸出緩沖區(qū):用于保存執(zhí)行客戶端請(qǐng)求命令返回的結(jié)果或返回值。每個(gè)客戶端都有兩個(gè)輸出緩沖區(qū),一個(gè)輸出緩沖區(qū)的大小是固定的,另一個(gè)輸出緩沖區(qū)的大小是可變的。
? 固定輸出緩沖區(qū):用于保存那些長(zhǎng)度比較小的返回值,比如常見的 OK、<nil> 或者一些短字符串、整數(shù)值及錯(cuò)誤值等。
? 可變輸出緩沖區(qū):用于保存那些長(zhǎng)度比較大的返回值,比如一個(gè)長(zhǎng)度比較大的字符串、大列表、大集合等。
buf 和 bufpos 屬性組成了客戶端固定大小的緩沖區(qū)。
buf 屬性是一個(gè)字節(jié)數(shù)組,數(shù)組大小為 REDIS_REPLY_CHUNK_BYTES 字節(jié)。REDIS_REPLY_CHUNK_BYTES 常量的默認(rèn)值為 16×1024,即 buf 數(shù)組的默認(rèn)大小為 16KB。
bufpos 屬性記錄了 buf 數(shù)組到目前為止已經(jīng)使用的字節(jié)數(shù)量。
當(dāng) buf 數(shù)組已經(jīng)存滿或者回復(fù)因?yàn)樘蠖鴽]有辦法存入 buf 數(shù)組時(shí),服務(wù)器就會(huì)使用可變大小的緩沖區(qū)。
鏈表 reply 和一個(gè)或多個(gè)字符串對(duì)象組成可變大小的輸出緩沖區(qū)。通過使用鏈表來連接多個(gè)字符串對(duì)象,服務(wù)器可以為客戶端保存一個(gè)非常長(zhǎng)的命令返回值,而不會(huì)受到大小的限制。如圖所示為可變大小的輸出緩沖區(qū)。

服務(wù)器采用軟性限制(Soft Limit)和硬性限制(Hard Limit)兩種模式來限制客戶端緩沖區(qū)的大小。
軟性限制:如果軟性限制所設(shè)置的大小小于輸出緩沖區(qū)的大小,且輸出緩沖區(qū)的大小不大于硬性限制所設(shè)置的大小,那么服務(wù)器會(huì)使用客戶端狀態(tài)結(jié)構(gòu)的 obuf_soft_limit_reached_time 屬性來記錄客戶端達(dá)到軟性限制的起始時(shí)間。之后服務(wù)器會(huì)繼續(xù)監(jiān)視客戶端,如果這個(gè)緩沖區(qū)的大小一直超出軟性限制,并且持續(xù)時(shí)間超過服務(wù)器設(shè)定的時(shí)長(zhǎng),那么服務(wù)器將會(huì)關(guān)閉這個(gè)客戶端。相反地,如果輸出緩沖區(qū)的大小在指定時(shí)間范圍之內(nèi)沒有超過軟性限制,那么這個(gè)客戶端不會(huì)被關(guān)閉,并且 obuf_soft_limit_reached_time 屬性的值也會(huì)被設(shè)置為 0。
當(dāng)輸出緩沖區(qū)的大小超過了硬性限制的大小時(shí),這個(gè)客戶端會(huì)被立即關(guān)閉。
-
設(shè)置軟性限制和硬性限制
我們可以使用 client-output-buffer-limit 選項(xiàng)來為普通客戶端、從服務(wù)器客戶端或執(zhí)行消息訂閱發(fā)布功能的客戶端設(shè)置軟性限制和硬性限制

客戶端的 authenticated 屬性
authenticated 屬性是客戶端身份驗(yàn)證屬性,用于記錄客戶端是否通過了身份驗(yàn)證。這個(gè)屬性的值為 0 和 1,默認(rèn)值為 0。
當(dāng) authenticated 屬性值為 0 時(shí),服務(wù)器除執(zhí)行 AUTH 命令之外,將會(huì)拒絕執(zhí)行客戶端發(fā)送過來的其他所有命令。
客戶端的 argv 和 argc 屬性
argv 屬性:這是一個(gè)數(shù)組,數(shù)組中的每個(gè)元素都是一個(gè)字符串對(duì)象,其中 argv[0]是要執(zhí)行的命令,而之后的其他元素是傳給這個(gè)命令的參數(shù)。
argc 屬性:用于記錄 argv 屬性的數(shù)組長(zhǎng)度。
當(dāng)客戶端向服務(wù)器發(fā)送命令時(shí),服務(wù)器會(huì)將接收到的命令保存到客戶端狀態(tài)的 querybuf 屬性中。保存之后,服務(wù)器會(huì)分析這個(gè)命令的內(nèi)容,并將分析得出的命令參數(shù)及命令參數(shù)的個(gè)數(shù)分別保存到 argv 和 argc 屬性中

關(guān)閉客戶端
普通客戶端被關(guān)閉的幾種方式:
● 當(dāng)客戶端執(zhí)行了 CLIENT kill 命令時(shí),客戶端會(huì)被關(guān)閉。
● 當(dāng)客戶端進(jìn)程被殺死時(shí),客戶端將會(huì)斷開與服務(wù)器的連接,從而客戶端被關(guān)閉。
● 當(dāng)客戶端向服務(wù)器發(fā)送的命令是錯(cuò)誤協(xié)議格式時(shí),客戶端會(huì)被關(guān)閉。
● 當(dāng)客戶端發(fā)送的命令請(qǐng)求的大小超過了輸入緩沖區(qū)的限制大小時(shí),客戶端會(huì)被關(guān)閉。
● 當(dāng)發(fā)送給客戶端的命令執(zhí)行后返回結(jié)果的大小超過了輸出緩沖區(qū)的限制大小時(shí),客戶端也會(huì)被關(guān)閉。
● 當(dāng)為服務(wù)器設(shè)置了 timeout 參數(shù)值,同時(shí)客戶端的空轉(zhuǎn)時(shí)間又超過了 timeout 參數(shù)值時(shí),客戶端將會(huì)被關(guān)閉。而如果這個(gè)客戶端是主服務(wù)器,而從服務(wù)器被 BLPOP、BRPOP 等相關(guān)命令阻塞,或者從服務(wù)器正在執(zhí)行與訂閱發(fā)布相關(guān)的命令,此時(shí)就算客戶端的空轉(zhuǎn)時(shí)間超過了 timeout 參數(shù)值,這個(gè)客戶端也不會(huì)被關(guān)閉。