基於redis5.0的版本。
HashMap<String, String>
存儲。ValueObject
,裏面包含了額外的擴展信息。若是要給緩存加上過時時間:html
ValueObject
上加上過時時間
字段。方式二:再建一個對應的過時時間Map,Map的值爲過時時間。redis
在redis系統內部,有一個redisServer
結構體的全局變量server
,server
保存了redis服務端全部的信息,包括當前進程的PID、服務器的端口號、數據庫個數、統計信息等等。固然,它也包含了數據庫信息,包括數據庫的個數、以及一個redisDb數組
。每個redisDb
互相獨立,互不干擾。算法
struct redisServer { …… redisDb *db; int dbnum; // Total number of configured DBs …… } /* Redis database representation. There are multiple databases identified * by integers from 0 (the default database) up to the max configured * database. The database number is the 'id' field in the structure. */ typedef struct redisDb { dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of keys with a timeout set */ dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ int id; /* Database ID */ long long avg_ttl; /* Average TTL, just for stats */ list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ } redisDb;
redis並無直接使用c語言傳統的字符串,而是本身構建了SDS(simple dynamic string)
。爲何不直接使用c語言的字符串?數據庫
‘\0’
字符結尾,若是存儲的數據字節自己包含‘\0’
,就會對結果形成影響。// sds.h // 五種header類型,flags取值爲0~4 define SDS_TYPE_5 0 define SDS_TYPE_8 1 define SDS_TYPE_16 2 define SDS_TYPE_32 3 define SDS_TYPE_64 4 /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* buf數組已使用的長度,不包含最後的結束符'\0' */ uint8_t alloc; /* buf數組最大容量,不包含最後的結束符'\0' */ unsigned char flags; /* 老是佔用一個字節,其中的最低3個bit用來表示header的類型。 */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; };
一個sds
字符串的完整結構,由在內存地址上先後相鄰的兩部分組成:數組
header
。一般包含字符串的長度(len)
、最大容量(alloc)
和flags
。sdshdr5有所不一樣。NULL
結束符,即ASCII碼爲0的'\0'
字符。這是爲了和傳統C字符串兼容。之因此字符數組的長度比最大容量多1個字節,就是爲了在字符串長度達到最大容量時仍然有1個字節存放NULL
結束。上圖是sds的一個內部結構的例子。圖中展現了兩個sds字符串s1和s2的內存結構,一個使用sdshdr8
類型的header,另外一個使用sdshdr16
類型的header。但它們都表達了一樣的一個長度爲6的字符串的值:"tielei"
。
sds的字符指針(s1和s2)就是指向真正的數據(字符數組)開始的位置,而header
位於內存地址較低的方向。在sds.h
中有一些跟解析header
有關的宏定義。緩存
// sds.h define SDS_TYPE_MASK 7 define SDS_TYPE_BITS 3 define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ sh = s_malloc(hdrlen+initlen+1); // 額外的一個結束符字節 if (init==SDS_NOINIT) init = NULL; else if (!init) memset(sh, 0, hdrlen+initlen+1); if (sh == NULL) return NULL; s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; switch(type) { case SDS_TYPE_5: { *fp = type | (initlen << SDS_TYPE_BITS); break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } if (initlen && init) memcpy(s, init, initlen); s[initlen] = '\0'; return s; } /* Create an empty (zero length) sds string. Even in this case the string * always has an implicit null term. */ sds sdsempty(void) { return sdsnewlen("",0); } /* Create a new sds string starting from a null terminated C string. */ sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); } /* Duplicate an sds string. */ sds sdsdup(const sds s) { return sdsnewlen(s, sdslen(s)); } static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->len; case SDS_TYPE_16: return SDS_HDR(16,s)->len; case SDS_TYPE_32: return SDS_HDR(32,s)->len; case SDS_TYPE_64: return SDS_HDR(64,s)->len; } return 0; } /* Free an sds string. No operation is performed if 's' is NULL. */ void sdsfree(sds s) { if (s == NULL) return; s_free((char*)s-sdsHdrSize(s[-1])); } // sds.c static inline int sdsHdrSize(char type) { switch(type&SDS_TYPE_MASK) { case SDS_TYPE_5: return sizeof(struct sdshdr5); case SDS_TYPE_8: return sizeof(struct sdshdr8); case SDS_TYPE_16: return sizeof(struct sdshdr16); case SDS_TYPE_32: return sizeof(struct sdshdr32); case SDS_TYPE_64: return sizeof(struct sdshdr64); } return 0; } static inline char sdsReqType(size_t string_size) { if (string_size < 1<<5) // 32 return SDS_TYPE_5; if (string_size < 1<<8) // 256 return SDS_TYPE_8; if (string_size < 1<<16) // 64K return SDS_TYPE_16; #if (LONG_MAX == LLONG_MAX) if (string_size < 1ll<<32) // 4GB return SDS_TYPE_32; return SDS_TYPE_64; #else return SDS_TYPE_32; #endif
其中SDS_HDR
用來從sds
字符串得到header
起始位置的指針,好比SDS_HDR(8, s1)
表示s1
的header
指針,SDS_HDR(16, s2)
表示s2
的header
指針。服務器
sdsnewlen
建立一個長度爲initlen
的sds字符串,並使用init
指向的字符數組(任意二進制數據)來初始化數據。若是init
爲NULL
,那麼使用全0
來初始化數據。它的實現中,咱們須要注意的是:數據結構
SDS_TYPE_5
類型的header,而是轉而使用SDS_TYPE_8
類型的header。這是由於建立的空字符串通常接下來的操做極可能是追加數據,但SDS_TYPE_5
類型的sds字符串不適合追加數據(會引起內存從新分配)。header
、數據
、最後的多餘字節
(hdrlen+initlen+1)。NULL結束符(s[initlen] = ‘\0’)
。 固然,使用SDS_HDR以前咱們必須先知道究竟是哪種header,這樣咱們才知道SDS_HDR
第1個參數應該傳什麼。由sds字符指針得到header類型的方法是,先向低地址方向偏移1個字節的位置,獲得flags
字段。好比,s1[-1]和s2[-1]分別得到了s1和s2的flags的值。而後取flags的最低3個bit獲得header的類型。app
有了header指針,就能很快定位到它的len和alloc字段:ide
0x06
,表示字符串數據長度爲6
;alloc的值爲0x80
,表示字符數組最大容量爲128
。0x0006
,表示字符串數據長度爲6
;alloc的值爲0x03E8
,表示字符數組最大容量爲1000
。(注意:圖中是按小端地址構成) 在各個header的類型定義中,還有幾個須要咱們注意的地方:
__attribute__ ((packed))
,是爲了讓編譯器以緊湊模式來分配內存(按實際佔用字節數進行對齊)。若是沒有這個屬性,編譯器可能會爲struct的字段作優化對齊,在其中填充空字節。那樣的話,就不能保證header和sds的數據部分牢牢先後相鄰,也不能按照固定向低地址方向偏移1個字節的方式來獲取flags字段了。char buf[]
。咱們注意到這是一個沒有指明長度的字符數組,這是C語言中定義字符數組的一種特殊寫法,稱爲柔性數組(flexible array member),只能定義在一個結構體的最後一個字段上。它在這裏只是起到一個標記的做用,表示在flags字段後面就是一個字符數組,或者說,它指明瞭緊跟在flags字段後面的這個字符數組在結構體中的偏移位置。而程序在爲header分配的內存的時候,它並不佔用內存空間。若是計算sizeof(struct sdshdr16)
的值,那麼結果是5個字節,其中沒有buf字段。sdshdr5
與其它幾個header結構不一樣,它不包含alloc字段,而長度使用flags的高5位來存儲。所以,它不能爲字符串分配空餘空間。若是字符串須要動態增加,那麼它就必然要從新分配內存才行。因此說,這種類型的sds字符串更適合存儲靜態的短字符串(長度小於32)。 至此,咱們很是清楚地看到了:sds字符串的header,其實隱藏在真正的字符串數據的前面(低地址方向/小端)。這樣的一個定義,有以下幾個好處:
sds相對c語言字符串的優勢:
len
快速得到字符串長度,便於統計。len
判斷字符串是否結束,雖然數據庫通常用於保存穩步數據,但保存二進制的常見也很多見,若是二進制數據中也存在'\0'
字符,用c語言字符串就會出現異常問題(只能取到第一個'\0'
以前的字符)。'\0'
字符結尾對慣例,爲了讓SDS可用重用部分<string.h>
庫定義對函數。當SDS API須要對SDS進行修改時,API會先檢查SDS的空間是否知足修改所需的要求,若是不知足,API會自動將SDS的空間擴展至執行所需的大小,而後才執行實際的修改:
len
屬性將和free
屬性同樣,buf數組實際的長度將是len+free+1
字節。// sds.h define SDS_MAX_PREALLOC (1024*1024) // sds.c /* Enlarge the free space at the end of the sds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * * Note: this does not change the *length* of the sds string as returned * by sdslen(), but only the free buffer space we have. */ sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; size_t avail = sdsavail(s); size_t len, newlen; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC) /* 小於1M */ newlen *= 2; else newlen += SDS_MAX_PREALLOC; type = sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is * not able to remember empty space, so sdsMakeRoomFor() must be called * at every appending operation. */ if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ newsh = s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, newlen); return s; }
sdsMakeRoomFor
是sds實現中很重要的一個函數。關於它的實現代碼,咱們須要注意的是:
SDS_MAX_PREALLOC
個字節,這個常量在sds.h中定義爲(1024*1024)=1MB
。s_realloc
,試圖在原來的地址上從新分配空間。s_realloc的具體實現得看Redis編譯的時候選用了哪一個allocator
(在Linux上默認使用jemalloc
)。但無論是哪一個realloc
的實現,它所表達的含義基本是相同的:它儘可能在原來分配好的地址位置從新分配,若是原來的地址位置有足夠的空餘空間完成從新分配,那麼它返回的新地址與傳入的舊地址相同;不然,它分配新的地址塊,並進行數據搬遷。一個database
內的這個映射關係是用一個dict
來維護的。dict
的key
固定用一種數據結構來表達就夠了,這就是動態字符串sds
。而value
則比較複雜,爲了在同一個dict
內可以存儲不一樣類型的value
,這就須要一個通用的數據結構
,這個通用的數據結構就是redisObject
。
// server.h // type #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. */ // encoding #define OBJ_ENCODING_RAW 0 /* Raw representation */ #define OBJ_ENCODING_INT 1 /* Encoded as integer */ #define OBJ_ENCODING_HT 2 /* Encoded as hash table */ #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */ #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */ struct redisObject { unsigned type:4; // 4bit,對象類型,值包括string,hash,list,set,zset等,能夠用type {key}命令查看 unsigned encoding:4; // 4bit,編碼類型,如字符串類型的OBJ_ENCODING_RAW,OBJ_ENCODING_INT,OBJ_ENCODING_EMBSTR,能夠用object encoding {key}命令查看 unsigned lru:LRU_BITS; // LRU_BITS 佔24bit,記錄對象最後一次被訪問的時間,作LRU替換算法用,能夠用object idletime {key}命令查看 int refcount; // 記錄當前對象被引用數,用於經過引用數回收內存,能夠用object refcount {key}命令查看 void *ptr; // 數據指針。指向真正的數據。好比,一個表明string的robj,它的ptr可能指向一個sds結構;一個表明list的robj,它的ptr可能指向一個quicklist。 };
refcount
屬性記錄,建立一個新對象時,引用計數值會初始化爲1
;對象被一個新程序使用時,它的引用計數值會被增1
;再也不被一個程序使用時減1
;引用計數值變爲0
,對象所佔用的內存會被釋放
。Redis的del
命令就依賴decrRefCount
操做將value
釋放。// object.c void decrRefCount(robj *o) { if (o->refcount == 1) { switch(o->type) { case OBJ_STRING: freeStringObject(o); break; case OBJ_LIST: freeListObject(o); break; case OBJ_SET: freeSetObject(o); break; case OBJ_ZSET: freeZsetObject(o); break; case OBJ_HASH: freeHashObject(o); break; case OBJ_MODULE: freeModuleObject(o); break; case OBJ_STREAM: freeStreamObject(o); break; default: serverPanic("Unknown object type"); break; } zfree(o); } else { if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0"); if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--; } }
對象共享:對象的引用計數屬性還帶有對象共享的做用。
shared.integers(默認0-9999)
的範圍中(實際上,生產的數字的引用次數refcount
固定爲2147483647
),若是命中就不進行對象建立,直接使用對應的共享對象,並將引用計數加一。100
的字符串對象同時被鍵A
和鍵B
共享以後的樣子,能夠看到,除了對象的引用計數從以前的1
變成了2
以外, 其餘屬性都沒有變化(理論上是這樣,但因爲數字的引用次數固定,因此實際上計數並不會有這樣的變化)。Redis爲何只對包含整數值得字符串對象進行共享?
O(1)
。O(N)
。O(N2)
。·須要提醒的是,append
和setbit
命令會解除對象的共享狀態
,例如:
set testref 1
命令以後,再經過object debug testref
, 能夠看到refcount:2147483647
。append testref 1
命令以後,再經過object debug testref
, 能夠看到refcount:1
。// server.h #define OBJ_SHARED_REFCOUNT INT_MAX #define OBJ_SHARED_INTEGERS 10000 // object.c robj *makeObjectShared(robj *o) { serverAssert(o->refcount == 1); o->refcount = OBJ_SHARED_REFCOUNT; return o; } // server.c void createSharedObjects(void) { …… for (j = 0; j < OBJ_SHARED_INTEGERS; j++) { shared.integers[j] = makeObjectShared(createObject(OBJ_STRING,(void*)(long)j)); shared.integers[j]->encoding = OBJ_ENCODING_INT; } …… } // t_string.c void appendCommand(client *c) { …… o = dbUnshareStringValue(c->db,c->argv[1],o); …… } // db.c robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) { serverAssert(o->type == OBJ_STRING); if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) { robj *decoded = getDecodedObject(o); o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr)); decrRefCount(decoded); dbOverwrite(db,key,o); } return o; }
redisObject的結構與對象類型、編碼、內存回收、共享對象都有關係;一個redisObject對象的大小爲16
字節:4bit+4bit+24bit+4Byte+8Byte=16Byte
。
以上內容參考自:
《redis設計與實現》
《 Redis源碼剖析系列》
《 Redis內部數據結構詳解系列》
《 多是目前最詳細的Redis內存模型及應用解讀》