Redis數據結構概覽(源碼分析)

經過學習Redis服務端設計這篇文章,相信你們對redis的總體架構有了最基本的認識,下面來學習Redis的基本結構,你們知道redis性能很高的緣由其中之一即是數據結構簡單。redis的數據結構支持多種類型,在內存使用方面處理的可謂是「錙銖必較」。那麼redis是如何設計的呢?c++

如今經過服務接收客戶端請求及處理流程爲例進行分析redis的基礎結構 以「set lemon coder」爲例:redis

結構

經過上圖,能夠看到服務在處理客戶端請求的時候 主要爲兩個步驟 1. 處理輸入流(解析請求) 2. 處理命令(將請求存儲db,以寫命令爲例)bash

下面經過源碼來分析 服務端實現的總體流程,本文源碼基於Redis版本5.0.5數據結構

第一個須要瞭解的是客戶端對象結構,每個客戶端鏈接,服務端都會建立一個client來與其對應。架構

src/server.h
/* 服務端建立的客戶端對象,只保留了本文須要的字段 */
typedef struct client {
    sds querybuf;           /* 輸入緩衝區 */
    int argc;               /* 參數的個數 */
    robj **argv;            /* 具體參數 */
    struct redisCommand *cmd, *lastcmd;  /* 命令處理方法 */
} client;
複製代碼

下面根據這幾個字段的處理來分析redis服務如何處理輸入流的函數

解析客戶端請求

實現方法爲processInlineBuffer()源碼分析

源碼1. 將輸入流querybuf轉爲argv的過程

querybuf爲sds類型性能

argv爲robj類型學習

src/networking.c
processInlineBuffer(){
    /* 使用\r\n分割參數 */
    querylen = newline-(c->querybuf+c->qb_pos);
    aux = sdsnewlen(c->querybuf+c->qb_pos,querylen);
    // 將入參進行拆分爲參數
    argv = sdssplitargs(aux,&argc);
    /* 爲全部的參數建立redisObject對象 */
    for (c->argc = 0, j = 0; j < argc; j++) {
      if (sdslen(argv[j])) {
          // 將參數進行轉換爲robj
          c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
          c->argc++;
       } else {
          sdsfree(argv[j]);
       }
    }
}
複製代碼

源碼2. createObject的處理

src/object.c
robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* 設置lru信息*/
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    return o;
}
複製代碼

處理命令 processCommand()

查找命令對應的處理函數,好比set會尋找到setCommandui

對key進行robj+sds類型轉換處理

key保存到全局字典dict中

value放入dict中

源碼1. 查找命令,執行命令

src/server.c
int processCommand(client *c) {
    /* 當前文件下的 setCommand */
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    /* 執行命令 */
    call(c,CMD_CALL_FULL);
    return C_OK;
}
複製代碼

源碼2. 執行命令setCommand

setCommand爲set指令的處理方法,若是指令爲hset,那麼對應的方法就是hsetCommand

src/t_string.c
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
void setCommand(client *c) {
  /* 判斷過時時間設置等邏輯*/
    .....
  /* 嘗試類型encoding */
  c->argv[2] = tryObjectEncoding(c->argv[2]);
  /* 執行基本命令 */
  setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
複製代碼

源碼3. 基本命令的處理

設置key value到字典中

若是有過時屬性,設置過時信息

void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    /* 設置key val 到dict中*/
    setKey(c->db,key,val);
    /* 若是有過時屬性,設置過時信息 */
    if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
    addReply(c, ok_reply ? ok_reply : shared.ok);
}
複製代碼

源碼4. 存儲字典

此處爲源碼3中setKey的實現,流程爲 1. 插入kv(若是key已經存在覆蓋設值),2. 移除過時信息,其中插入字典的方法爲dbAdd,其中包含sdsup方法即選擇合適的結構。

src/db.c
void setKey(redisDb *db, robj *key, robj *val) {
    if (lookupKeyWrite(db,key) == NULL) {
        dbAdd(db,key,val);
    } else {
        dbOverwrite(db,key,val);
    }
    incrRefCount(val);
    removeExpire(db,key);
    signalModifiedKey(db,key);
}
src/db.c
void dbAdd(redisDb *db, robj *key, robj *val) {
    sds copy = sdsdup(key->ptr);
    int retval = dictAdd(db->dict, copy, val);
}
複製代碼

源碼5. 類型處理

  1. 將key轉化爲合適的結構
  2. 將kv插入字典中,此時value爲空
  3. 將value轉換爲合適的結構
  4. 將value插入字典中
src/db.c
void dbAdd(redisDb *db, robj *key, robj *val) {
    sds copy = sdsdup(key->ptr);
    int retval = dictAdd(db->dict, copy, val);

    serverAssertWithInfo(NULL,key,retval == DICT_OK);
    if (val->type == OBJ_LIST ||
        val->type == OBJ_ZSET)
        signalKeyAsReady(db, key);
    if (server.cluster_enabled) slotToKeyAdd(key);
}
src/sds.c
/* 對sds類型進行轉換爲合適的結構 */
sds sdsdup(const sds s) {
  return sdsnewlen(s, sdslen(s));
}
src/dict.c
/* 添加一個元素到字典中 */
int dictAdd(dict *d, void *key, void *val)
{
    dictEntry *entry = dictAddRaw(d,key,NULL);

if (!entry) return DICT_ERR;
    dictSetVal(d, entry, val);
return DICT_OK;
}
src/dict.h
/* 對val轉換爲合適的結構 */
define dictSetVal(d, entry, _val_) 
do { 
  if ((d)->type->valDup) 
      (entry)->v.val = (d)->type->valDup((d)->privdata, _val_); 
  else 
      (entry)->v.val = (_val_); 
} while(0)
複製代碼

以上就是服務處理請求的流程,主要包含:處理輸入流(解析參數)、存儲(確認類型、插入全局字典)。

注意點:

經過上面的對過時時間的設置,會對過時時間進行removeExpire(db,key)操做,操做完成以後會檢測是否配置了過時時間,若是沒有設置則不過時。看個示例:

127.0.0.1:6379> set lemon coder ex 100
OK
127.0.0.1:6379> pttl lemon
(integer) 95426
127.0.0.1:6379> set lemon coder
OK
127.0.0.1:6379> pttl lemon
(integer) -1
複製代碼

結構編碼關係

經過上面的針對字符串的setCommand命令的分析,能夠看到查找命令過程,不一樣的指令會對應不一樣的處理流程,分別建立不一樣的類型,並根據值的大小及個數選擇合適的數據結構。下面來看下redis有哪些類型和數據結構,以及他們之間的關係。

類型

src/server.h:492
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
複製代碼

數據結構(內部編碼)

/* 內部編碼*/
#define OBJ_ENCODING_RAW 0 /* Raw 字符串 */
#define OBJ_ENCODING_INT 1 /* int 字符串(所有爲數字的狀況) */
#define OBJ_ENCODING_HT 2 /* 哈希表 */
#define OBJ_ENCODING_ZIPMAP 3 /* zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* linkedlist */
#define OBJ_ENCODING_ZIPLIST 5 /* 壓縮列表ziplist */
#define OBJ_ENCODING_INTSET 6 /* intset */
#define OBJ_ENCODING_SKIPLIST 7 /* 跳躍列表skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* 字符串 */
#define OBJ_ENCODING_QUICKLIST 9 /* 快速列表 quickList(linkedList+ziplist) */
#define OBJ_ENCODING_STREAM 10 /* stream */
複製代碼

對應關係

關係

上圖爲redis的結構及內部編碼,後續做者的文章將詳細介紹每一種結構,以及每一種結構如何進行編碼的轉換和擴容及縮容。

以上就是本篇文章的所有內容,感謝您的閱讀,若是您在其中發現任何不正確或者不嚴謹的描述,歡迎指正。

原文地址:Redis數據結構概覽(源碼分析)

歡迎關注做者公衆號:

相關文章
相關標籤/搜索