redis代碼結構之三類型庫-list
1. 類型庫概述
下面介紹redis核心的內容以及所支援的資料類型及操作。首先看一下相應的資料結構:
typedef char *sds; //該檔案返回的基本上都是sds,即char *,也是實際儲存內容的地址。struct sdshdr { int len; //內容擁有的空間,不包括該結構的本身的大小,也不包括buf最後的結束符’\0’ int free; //剩下的可用記憶體空間,buf中可能有一些沒有使用,這些的長度就是free的值 char buf[]; //實際的儲存內容=strlen(buf)+free=len};
該類型是redis使用最多的類型,它類似c++的string類型。相應的函數包括sds sdsnewlen(const void *init, size_t initlen) ,它首先申請sh = zmalloc(sizeof(struct sdshdr)+initlen+1);記憶體(註:sizeof(struct sdshdr)=8),initlen就是sdshdr->len,而init則會被memcpy到sdshdr->buf裡,並且此時sdshdr->free為0。最後返回(char*)sh->buf,即實際內容的地址。其它的就類似於string的方法。
typedef struct redisObject { unsigned type:4; unsigned storage:2; /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */ unsigned encoding:4; unsigned lru:22; /* lru time (relative to server.lruclock) */ int refcount; void *ptr;} robj;
該結構定義了redis的操作對象的抽象資料類型。
Type:定義該資料的類型,當前的redis支援5種資料類型:REDIS_STRING、REDIS_LIST、REDIS_SET、REDIS_ZSET、REDIS_HASH。
Storage:定義資料當前的位置記憶體還是硬碟的swap。
Encoding:定義資料類型使用的編碼方式或者叫記憶體的儲存方式,當前redis支援8種encoding。各種類型及所支援的編碼見下表:
類型 |
REDIS_STRING(t_string.c) |
REDIS_LIST(t_list.c,ziplist.c) |
REDIS_SET(t_set.c) |
REDIS_ZSET(t_zset.c) |
REDIS_HASH(t_hash.c) |
命令 |
set,mset |
lpush,rpush,lpop,rpop |
sadd |
zadd |
Hset,hmset |
編碼 |
REDIS_ENCODING_RAW, REDIS_ENCODING_INT |
REDIS_ENCODING_ZIPLIST, REDIS_ENCODING_LINKEDLIST |
REDIS_ENCODING_HT, REDIS_ENCODING_INTSET |
REDIS_ENCODING_ZIPLIST, REDIS_ENCODING_SKIPLIST |
REDIS_ENCODING_HT, REDIS_ENCODING_ZIPMAP |
資料的類型是由使用者的命令決定的,另外所有的key都使用REDIS_STRING類型(在dbAdd的時候再提取出實際的內容sds copy = sdsdup(key->ptr);,這個就是真正儲存到dict裡的key值),並且它的encoding只為REDIS_ENCODING_RAW;而編碼則是由server根據配置及當前的系統情況進行選擇的(這個條件我們在後面的每種類型中分別介紹)。
Lru:
Refcount:該對象的引用個數
Ptr:指向實際儲存的內容
Server在接收到client請求的時候總是先把命令的所有成員轉換成type=REDIS_STRING,encoding = REDIS_ENCODING_RAW為的robj類型儲存到client->argv[]裡(0是命令,1是key,2,3..是value【2可能是expire值】,這個過程在processInlineBuffer或者processMultibulkBuffer函數裡完成)。同時在調用每個命令相應的處理函數的時候,大多數命令都會對value先進行一個tryObjectEncoding,即判斷該value是否可表示為long類型string2l(s,sdslen(s),&value),如果可以的話就使用shared.integers[value],或者encoding
= REDIS_ENCODING_INT,代碼如下:
if (!string2l(s,sdslen(s),&value)) return o;…if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS && pthread_equal(pthread_self(),server.mainthread)) { decrRefCount(o); incrRefCount(shared.integers[value]); return shared.integers[value];} else { o->encoding = REDIS_ENCODING_INT; sdsfree(o->ptr); o->ptr = (void*) value; return o;}下面我們分別介紹五種資料類型的相應結構及操作。(每種類型的操作指令及含義可參考http://www.w3ccollege.org/redis/redis-command-manual.html)2. REDIS_STRING(t_string.c)該類型的命令包括:set,setnx,setex,mset,incr,append等等。這裡我們只介紹set命令,它相應的命令回呼函數為setCommand(redisClient *c) { c->argv[2] = tryObjectEncoding(c->argv[2]); setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);}該類型的大多數命令都是通過調用setGenericCommand函數為內部介面:
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) { long seconds = 0; /* initialized to avoid an harmness warning */ if (expire) {//設定到期時間的操作 if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK) return; if (seconds <= 0) { addReplyError(c,"invalid expire time in SETEX"); return; } } //如果該key存在,並且命令要求在key不存在時才set,此時則返回 if (lookupKeyWrite(c->db,key) != NULL && nx) { addReply(c,shared.czero); return; } setKey(c->db,key,val); //向字典中追加key,val server.dirty++;//設定dirty持久化時使用 if (expire) setExpire(c->db,key,time(NULL)+seconds);//向expire dict中添加該事件 addReply(c, nx ? shared.cone : shared.ok);}這就是set操作的過程,很簡單。其內部的setKey就是調用db.c,dict.c的函數。