1. 程式人生 > >Redis源碼剖析——客戶端和服務器

Redis源碼剖析——客戶端和服務器

byte 函數 step sla mount sock eve rep 實現

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);
    }``

Redis源碼剖析——客戶端和服務器