天天看點

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 /

    // 從伺服器的用戶端連結清單中删除自身

    ln = listSearchKey(server.clients,c);

    redisAssert(ln != NULL);

    listDelNode(server.clients,ln);

    // 删除用戶端的阻塞資訊

    if (c->flags & REDIS_UNBLOCKED) {

    ln = listSearchKey(server.unblocked_clients,c);

    listDelNode(server.unblocked_clients,ln);

    if (c->name) decrRefCount(c->name);

    // 清除參數空間

    zfree(c->argv);

    // 清除事務狀态資訊

    freeClientMultiState(c);

    sdsfree(c->peerid);

    // 釋放用戶端 redisClient 結構本身

    }``

繼續閱讀