Redis原始碼剖析——客戶端和伺服器
Redis伺服器是典型的一對多伺服器程式:一個伺服器可以與多個客戶端建立網路連線。這篇文章將通過原始碼看看客戶端和伺服器的底層資料結構和工作過程
在Redis這種一對多的服務模式下,每個客戶端可以向伺服器傳送命令請求,而伺服器則接收並處理客戶端傳送的命令請求,並向客戶端返回命令回覆。通過使用由I/O多路複用技術實現的檔案事件處理器,Redis伺服器使用單執行緒單程式的方式來處理命令請求,並與多個客戶端進行網路通訊。
客戶端
客戶端資料結構
客戶端底層的資料結構如下:
typedef struct redisClient {
uint64_t id; / Client incremental unique ID. /
// 套接字描述符
int fd;
redisDb db;
int dictid;
// 客戶端名字
robj name; / As set by CLIENT SETNAME /
// 輸入緩衝區,儲存客戶端傳送的命令請求
sds querybuf;
size_t querybuf_peak; / Recent (100ms or more) peak of querybuf size /
// 命令和命令引數
int argc;
robj *argv;
// 命令實現函式字典
struct redisCommand cmd, lastcmd;
int reqtype;
int multibulklen; / number of multi bulk arguments left to read /
long bulklen; / length of bulk argument in multi bulk request /
list reply;
unsigned long reply_bytes; / Tot bytes of objects in reply list /
int sentlen; / Amount of bytes already sent in the current
buffer or object being sent. /
// 建立客戶端時間
time_t ctime; / Client creation time /
// 客戶端和伺服器最後一次進行互動的時間
time_t lastinteraction; / time of the last interaction, used for timeout /
time_t obuf_soft_limit_reached_time;
// 標誌,記錄客戶端的角色
int flags; / REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI … /
// 標誌是否通過身份驗證
int authenticated; / when requirepass is non-NULL /
… // 其他相關屬性
/* Response buffer */
// 回應緩衝區
int bufpos;
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
在客戶端的各個屬性中:
fd表示套接字描述符,偽客戶端的fd屬性的值為-1:偽客戶端處理的命令請求來源於AOF檔案或者Lua指令碼,而不是網路,所以這種客戶端不需要套接字連線;普通客戶端的fd屬性的值為大於-1的整數
命令和命令引數是對輸入緩衝的命令進行解析以後獲得命令和引數。
cmd 是命令的實現函式的陣列,命令實現函式的結構如下:
struct redisCommand {
// 命令名稱
char name;
// 命令執行函式
redisCommandProc proc;
// 引數個數
int arity;
// 字串表示flag
char sflags; / Flags as string representation, one char per flag. /
// 實際flag
int flags; / The actual flags, obtained from the `sflags` field. */
...
// 指定哪些引數是key
int firstkey; /* The first argument that`s a key (0 = no keys) */
int lastkey; /* The last argument that`s a key */
int keystep; /* The step between first and last key */
// 統計資訊
long long microseconds, calls;
};
客戶端的建立和關閉
當客戶端向伺服器發出connect請求的時候,伺服器的事件處理器就會對這個事件進行處理,建立相應的客戶端狀態,並將這個新的客戶端狀態新增到伺服器狀態結構clients連結串列的末尾
/*
-
建立一個新客戶端
/
redisClient createClient(int fd){// 分配空間
redisClient *c = zmalloc(sizeof(redisClient));// 當 fd 不為 -1 時,建立帶網路連線的客戶端
// 如果 fd 為 -1 ,那麼建立無網路連線的偽客戶端
// 因為 Redis 的命令必須在客戶端的上下文中使用,所以在執行 Lua 環境中的命令時
// 需要用到這種偽終端
if (fd != -1) {
// 非阻塞
anetNonBlock(NULL,fd);
// 禁用 Nagle 演算法
anetEnableTcpNoDelay(NULL,fd);
// 設定 keep alive
if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive);
// 繫結讀事件到事件 loop (開始接收命令請求)
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
}// 初始化各個屬性
// 預設資料庫
selectDb(c,0);
// 套接字
c->fd = fd;
…listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
// 如果不是偽客戶端,那麼新增到伺服器的客戶端連結串列中
if (fd != -1) listAddNodeTail(server.clients,c);
// 初始化客戶端的事務狀態
initClientMultiState(c);// 返回客戶端
return c;
}
對於客戶端的啟動程式,其大致的邏輯是:讀取本地配置,連線伺服器獲取伺服器的配置,獲取本地輸入的命令併傳送到伺服器
一個普通客戶端可以因為多種原因而被關閉:
如果客戶端程式退出或者被殺死,那麼客戶端與伺服器之間的網路連線將被關閉,從而造成客戶端被關閉。
如果客戶端向伺服器傳送了帶有不符合協議格式的命令請求,那麼這個客戶端也會被伺服器關閉。
如果客戶端成為了CLIENT KLLL命令的目標,那麼它也會被關閉。
關閉客戶端的底層實現:
/*
-
釋放客戶端
/
void freeClient(redisClient c){
listNode *ln;…
/ Free the query buffer /
sdsfree(c->querybuf);
c->querybuf = NULL;/ Deallocate structures used to block on blocking ops. /
if (c->flags & REDIS_BLOCKED) unblockClient(c);
dictRelease(c->bpop.keys);/ UNWATCH all the keys /
// 清空 WATCH 資訊
unwatchAllKeys(c);
listRelease(c->watched_keys);/ Unsubscribe from all the pubsub channels /
// 退訂所有頻道和模式
pubsubUnsubscribeAllChannels(c,0);
pubsubUnsubscribeAllPatterns(c,0);
dictRelease(c->pubsub_channels);
listRelease(c->pubsub_patterns);/* Close socket, unregister events, and remove list of replies and
- accumulated arguments. */
// 關閉套接字,並從事件處理器中刪除該套接字的事件
if (c->fd != -1) {
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
close(c->fd);
}
// 清空回覆緩衝區
listRelease(c->reply);// 清空命令引數
freeClientArgv(c);/ Remove from the list of clients /
// 從伺服器的客戶端連結串列中刪除自身
if (c->fd != -1) {
ln = listSearchKey(server.clients,c);
redisAssert(ln != NULL);
listDelNode(server.clients,ln);
}// 刪除客戶端的阻塞資訊
if (c->flags & REDIS_UNBLOCKED) {
ln = listSearchKey(server.unblocked_clients,c);
redisAssert(ln != NULL);
listDelNode(server.unblocked_clients,ln);
}…
if (c->name) decrRefCount(c->name);
// 清除引數空間
zfree(c->argv);
// 清除事務狀態資訊
freeClientMultiState(c);
sdsfree(c->peerid);
// 釋放客戶端 redisClient 結構本身
zfree(c);
}“ - accumulated arguments. */
相關文章
- 深入剖析Redis客戶端Jedis的特性和原理Redis客戶端
- Java UDP伺服器和客戶端原始碼 -javarevisitedJavaUDP伺服器客戶端原始碼
- 走近原始碼:Redis命令執行過程(客戶端)原始碼Redis客戶端
- Redis從客戶端登入伺服器Redis客戶端伺服器
- [Redis 客戶端整合] Java 中常用Redis客戶端比較Redis客戶端Java
- Redis-客戶端Redis客戶端
- redis客戶端管理Redis客戶端
- redis伺服器/客戶端安裝與配置Redis伺服器客戶端
- Linux 伺服器zabbix原始碼客戶端(agent)安裝Linux伺服器原始碼客戶端
- redis客戶端的使用Redis客戶端
- Redis客戶端連線Redis客戶端
- Redis C客戶端APIRedis客戶端API
- Redis 6.0 客戶端快取的伺服器端實現Redis客戶端快取伺服器
- 跟著大彬讀原始碼 - Redis 3 - 伺服器如何響應客戶端請求?(下)原始碼Redis伺服器客戶端
- 跟著大彬讀原始碼 - Redis 2 - 伺服器如何響應客戶端請求?(上)原始碼Redis伺服器客戶端
- Redis 客戶端 Jedis、lettuce 和 Redisson 對比Redis客戶端
- Tars-Java客戶端原始碼分析Java客戶端原始碼
- Telegram原始碼之安卓客戶端配置原始碼安卓客戶端
- Redis介紹 && Java客戶端操作RedisRedisJava客戶端
- redis:常用客戶端命令(redis-cli)Redis客戶端
- MQTT伺服器搭建服務端和客戶端MQQT伺服器服務端客戶端
- 初探 Redis 客戶端 Lettuce:真香!Redis客戶端
- 《球球大作戰》原始碼解析:伺服器與客戶端架構原始碼伺服器客戶端架構
- MapReduce——客戶端提交任務原始碼分析客戶端原始碼
- Java中OpenAI API客戶端原始碼教程JavaOpenAIAPI客戶端原始碼
- 使用Netty實現HTTP2伺服器/客戶端的原始碼和教程 - BaeldungNettyHTTP伺服器客戶端原始碼
- C#客戶端Redis伺服器的分散式快取C#客戶端Redis伺服器分散式快取
- ubuntu 下面 svn 伺服器端和客戶端的配置和使用Ubuntu伺服器客戶端
- Redis客戶端選型再分析Redis客戶端
- Windows下安裝redis客戶端WindowsRedis客戶端
- mysql、redis 客戶端連線池MySqlRedis客戶端
- day03-Redis的客戶端Redis客戶端
- Netty原始碼分析(三):客戶端啟動Netty原始碼客戶端
- React 伺服器端渲染和客戶端渲染效果對比React伺服器客戶端
- rsync客戶端一鍵安裝rsync指令碼(原始碼)客戶端指令碼原始碼
- 藍芽客戶端和伺服器的實現藍芽客戶端伺服器
- Redis學習筆記(十) 客戶端Redis筆記客戶端
- Redis客戶端連線數DevOpsRedis客戶端dev