set
用來存儲string的類型數據java
> set key hello
OK
複製代碼
get
來獲取string類型的值git
> get key
"hello"
複製代碼
若是在set
執行的時候,key已經存在,則會覆蓋原有key的值github
> set key anotherValue
OK
> get key
"anotherValue"
複製代碼
redis.c
中數組 redisCommandTable 爲全部暴漏出去的命令列表,以及實現命令的函數指針redis
struct redisCommand redisCommandTable[] = {
...
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
...
}
複製代碼
從這裏能夠看到 setCommand 即爲 set方法的入口。數組
Code.SLICE.source("c->argv[2] = tryObjectEncoding(c->argv[2]);")
.interpretation("在對set的格式作完語法校驗,同時取得相應的命令屬於 NX/XX/EX/PX/直接set以後,根據value來獲取編碼");
Code.SLICE.source("setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);")
.interpretation("根據實際狀況存儲k-v對");
複製代碼
在執行Set以前,redis並非直接將原有傳入的string儲存,而是先選擇了作一層編碼,編碼以後再來存安全
Code.SLICE.source("len = sdslen(s);")
.interpretation("獲取要存儲的字符串值的長度,s取值即 redisObject指向的 數據字節指針");
Code.SLICE.source("if (len <= 20 && string2l(s,len,&value))")
.interpretation("判斷字符串的長度若是小於20而且可以轉成long 類型,執行轉成long 的邏輯,並結果存儲到value");
//...
Code.SLICE.source(" o->encoding = OBJ_ENCODING_INT;\n" +
" o->ptr = (void*) value;")
.interpretation("斷定好是能夠轉成long則設定編碼方式爲int,同時數據指針就直接存儲值");
//...
Code.SLICE.source("if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) ")
.interpretation("若是字符串長度知足emb的長度條件(44),使用emb編碼,使得經過一次內存分配函數的調用就能夠拿到連續的內存空間存儲 redisObject和 數據 sdshdr");
//...
Code.SLICE.source(" emb = createEmbeddedStringObject(s,sdslen(s));")
.interpretation("將值使用emb編碼後再返回");
//...
Code.SLICE.source("if (o->encoding == OBJ_ENCODING_RAW &&\n" +
" sdsavail(s) > len/10)\n" +
" {\n" +
" o->ptr = sdsRemoveFreeSpace(o->ptr);\n" +
" }")
.interpretation("若是超過了emb限制,則儘可能的去較少浪費的空間,將原始的內容直接返回");
//...
複製代碼
對於 string 來講,編碼是根據value的長度來按照不一樣的編碼方式處理bash
在轉碼過程當中,傳進來的數據會被轉成 redisObject數據結構
typedef struct redisObject {
unsigned type:4; //指string/list/hash/zset/set
unsigned encoding:4; //數據本身的編碼格式
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount; //數據被引用的次數,爲0表示能夠安全回收這個對象
void *ptr; //對象數據
} robj;
複製代碼
實際存儲的時候會去檢查是否已經有同名的key函數
Code.SLICE.source(" if (lookupKeyWrite(db,key) == NULL) {\n" +
" dbAdd(db,key,val);\n" +
" } else {\n" +
" dbOverwrite(db,key,val);\n" +
" }")
.interpretation("若是以前沒有存過,就直接添加,不然去覆蓋");
複製代碼
每次在查找key的時候,同時也會去檢查key是否是已通過期了,知足過時條件的key會被刪除,而後再將傳進來的string建立 sds 對象,存儲起來優化
//...
Code.SLICE.source("char type = sdsReqType(initlen);")
.interpretation("根據要新建的字符串獲取不一樣的類型,類型就是宏定義的 0 1 2 3 4這5個取值的類型,表明不一樣的 sdshdr 結構\n");
//...
Code.SLICE.source(" switch(type) {\n" +
" case SDS_TYPE_5: {\n" +
" *fp = type | (initlen << SDS_TYPE_BITS);\n" +
" break;\n" +
" }\n" +
" case SDS_TYPE_8: {\n" +
" SDS_HDR_VAR(8,s);\n" +
" sh->len = initlen;\n" +
" sh->alloc = initlen;\n" +
" *fp = type;\n" +
" break;\n" +
" }\n" +
" case SDS_TYPE_16: {\n" +
" SDS_HDR_VAR(16,s);\n" +
" sh->len = initlen;\n" +
" sh->alloc = initlen;\n" +
" *fp = type;\n" +
" break;\n" +
" }\n" +
" case SDS_TYPE_32: {\n" +
" SDS_HDR_VAR(32,s);\n" +
" sh->len = initlen;\n" +
" sh->alloc = initlen;\n" +
" *fp = type;\n" +
" break;\n" +
" }\n" +
" case SDS_TYPE_64: {\n" +
" SDS_HDR_VAR(64,s);\n" +
" sh->len = initlen;\n" +
" sh->alloc = initlen;\n" +
" *fp = type;\n" +
" break;\n" +
" }\n" +
" }")
.interpretation("類型不一樣建立不一樣的結構");
複製代碼
字節長度不一樣建立的結構大小也不一樣,以 shshdr8 爲例
Code.SLICE.source("struct __attribute__ ((__packed__)) sdshdr8 {\n" +
" uint8_t len; /* 已經使用的長度 */\n" +
" uint8_t alloc; /* 分配的長度 */\n" +
" unsigned char flags; /* 3 lsb of type, 5 unused bits */\n" +
" char buf[];\n" +
"};")
.interpretation("len表示使用了的長度,alloc表示分配的空間長度,flags的最低三個bit用來表示header的類型,類型好比 sdshdr8")
.interpretation("1:uint8_t指的是 unsigned char ,大小爲1字節 char buf[]自己不計算大小,只是真實數據存儲的時候,會在 buf最後添加 1個 \0,爲了和C作兼容,方便利用C的一些函數")
.interpretation("2:__attribute__ ((__packed__)) 是爲了告訴編譯器,以緊湊的方式存放,不作對齊,redis這樣作方便獲取數據,好比要拿到flag只須要獲取 buf的前一個地址便可");
複製代碼
不一樣的結構,header 佔據空間也就不同
在讀到set命令以後,對於傳進來的數據會轉換成redisObject,而根據string value長度的不一樣使用不一樣的編碼,同時存儲的結構也會不同,以達到優化內存的目的
set源碼執行詳細過程請戳這裏
Redis內部數據結構詳解(2)——sds 張鐵蕾
Redis內部數據結構詳解(3)——robj 張鐵蕾