基于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内存模型及应用解读》