redis服務器是典型的一對多服務器,通過使用由IO多路復用技術實現的文件事件處理器,redis服務器使用了單線程單進程的方式來處理請求。
客戶端的屬性
描述符
客戶端狀態的?fd
?屬性記錄了客戶端正在使用的套接字描述符:
typedef struct redisClient {// ...int fd;// ...
} redisClient;
- 偽客戶端
fd
?值為?-1
?: 偽客戶端處理的命令請求來源于 AOF 文件或者 Lua 腳本, 而不是網絡, 所以這種客戶端不需要套接字連接。 - 普通客戶端?
fd
?值為大于?-1
?的整數: 普通客戶端使用套接字來與服務器進行通訊, 所以服務器會用?fd
?屬性來記錄客戶端套接字的描述符。?
標志
客戶端的標志屬性?flags
?記錄了客戶端的角色(role), 以及客戶端目前所處的狀態:
typedef struct redisClient {// ...int flags;// ...} redisClient;
flags
?屬性的值可以是單個標志:
flags = <flag>
也可以是多個標志的二進制或, 比如:
flags = <flag1> | <flag2> | ...
每個標志使用一個常量表示, 一部分標志記錄了客戶端的角色:
- 在主從服務器進行復制操作時, 主服務器會成為從服務器的客戶端, 而從服務器也會成為主服務器的客戶端。?
REDIS_MASTER
?標志表示客戶端代表的是一個主服務器,?REDIS_SLAVE
?標志表示客戶端代表的是一個從服務器。 REDIS_LUA_CLIENT
?標識表示客戶端是專門用于處理 Lua 腳本里面包含的 Redis 命令的偽客戶端。
另一部分標志記錄了客戶端目前所處的狀態:
以下內容為摘抄
REDIS_MONITOR?標志表示客戶端正在執行?MONITOR?命令。REDIS_UNIX_SOCKET?標志表示服務器使用 UNIX 套接字來連接客戶端。REDIS_BLOCKED?標志表示客戶端正在被?BRPOP?、?BLPOP?等命令阻塞。REDIS_UNBLOCKED?標志表示客戶端已經從?REDIS_BLOCKED?標志所表示的阻塞狀態中脫離出來,
不再阻塞。?REDIS_UNBLOCKED?標志只能在?REDIS_BLOCKED?標志已經打開的情況下使用。REDIS_MULTI?標志表示客戶端正在執行事務。REDIS_DIRTY_CAS?標志表示事務使用?WATCH?命令監視的數據庫鍵已經被修改,?
REDIS_DIRTY_EXEC?標志表示事務在命令入隊時出現了錯誤,
以上兩個標志都表示事務的安全性已經被破壞, 只要這兩個標記中的任意一個被打開,?
EXEC?命令必然會執行失敗。
這兩個標志只能在客戶端打開了?REDIS_MULTI?標志的情況下使用。REDIS_CLOSE_ASAP?標志表示客戶端的輸出緩沖區大小超出了服務器允許的范圍,
服務器會在下一次執行?serverCron?函數時關閉這個客戶端,
以免服務器的穩定性受到這個客戶端影響。
積存在輸出緩沖區中的所有內容會直接被釋放, 不會返回給客戶端。REDIS_CLOSE_AFTER_REPLY?標志表示有用戶對這個客戶端執行了?CLIENT_KILL?命令,
或者客戶端發送給服務器的命令請求中包含了錯誤的協議內容。
服務器會將客戶端積存在輸出緩沖區中的所有內容發送給客戶端, 然后關閉客戶端。REDIS_ASKING?標志表示客戶端向集群節點(運行在集群模式下的服務器)發送了?ASKING?命令。REDIS_FORCE_AOF?標志強制服務器將當前執行的命令寫入到 AOF 文件里面,
REDIS_FORCE_REPL?標志強制主服務器將當前執行的命令復制給所有從服務器。
執行?PUBSUB?命令會使客戶端打開?REDIS_FORCE_AOF?標志,
執行?SCRIPT_LOAD?命令會使客戶端打開?
REDIS_FORCE_AOF標志和?REDIS_FORCE_REPL?標志。在主從服務器進行命令傳播期間, 從服務器需要向主服務器發送?REPLICATION ACK?命令,
在發送這個命令之前, 從服務器必須打開主服務器對應的客戶端的?
REDIS_MASTER_FORCE_REPLY?標志, 否則發送操作會被拒絕執行。
以上提到的所有標志都定義在?redis.h
?文件里面。
PUBSUB
?命令和?SCRIPT?LOAD
?命令的特殊性
通常情況下, Redis 只會將那些對數據庫進行了修改的命令寫入到 AOF 文件, 并復制到各個從服務器: 如果一個命令沒有對數據庫進行任何修改, 那么它就會被認為是只讀命令, 這個命令不會被寫入到 AOF 文件, 也不會被復制到從服務器。
以上規則適用于絕大部分 Redis 命令, 但?PUBSUB?命令和?SCRIPT_LOAD?命令是其中的例外。
PUBSUB?命令雖然沒有修改數據庫, 但?PUBSUB?命令向頻道的所有訂閱者發送消息這一行為帶有副作用, 接收到消息的所有客戶端的狀態都會因為這個命令而改變。 因此, 服務器需要使用?REDIS_FORCE_AOF
?標志, 強制將這個命令寫入 AOF 文件, 這樣在將來載入 AOF 文件時, 服務器就可以再次執行相同的?PUBSUB?命令, 并產生相同的副作用。
SCRIPT_LOAD?命令的與?PUBSUB?命令類似
輸入緩沖區
客戶端狀態的輸入緩沖區用于保存客戶端發送的命令請求:
typedef struct redisClient {// ...sds querybuf;// ...} redisClient;
?redisClient 實例:
命令相關
在服務器將客戶端發送的命令請求保存到客戶端狀態的?querybuf
?屬性之后, 服務器將對命令請求的內容進行分析, 并將得出的命令參數以及命令參數的個數分別保存到客戶端狀態的?argv
?屬性和?argc
?屬性:
typedef struct redisClient {// ...robj **argv;int argc;// ...} redisClient;
argv
?屬性是一個數組, 數組中的每個項都是一個字符串對象: 其中?argv[0]
?是要執行的命令, 而之后的其他項則是傳給命令的參數。
argc
?屬性則負責記錄?argv
?數組的長度。
實現函數
?
當服務器從協議內容中分析并得出?argv
?屬性和?argc
?屬性的值之后, 服務器將根據項?argv[0]
?的值, 在命令表中查找命令所對應的命令實現函數。
(命令表是一個字典,字典的鍵是一個 SDS 結構, 保存了命令的名字, 字典的值是命令所對應的?redisCommand
?結構, 這個結構保存了命令的實現函數、 命令的標志、 命令應該給定的參數個數、 命令的總執行次數和總消耗時長等統計信息。)
輸出緩沖區
執行命令所得的命令回復會被保存在客戶端狀態的輸出緩沖區里面, 每個客戶端都有兩個輸出緩沖區:
- 固定大小的緩沖區用于保存那些長度比較小的回復, 比如?
OK
?、簡短的字符串值、整數值、錯誤回復,等等。 - 可變大小的緩沖區用于保存那些長度比較大的回復, 比如一個非常長的字符串值, 一個由很多項組成的列表, 一個包含了很多元素的集合, 等等。
?
其它
客戶端狀態的?authenticated
?屬性用于記錄客戶端是否通過了身份驗證,還有幾個和時間有關的屬性,敘述是一件挺無聊的事情,不再寫。