总结之《Redis设计与实现》node
Redis中是使用对象来即是数据库中的键和值。redis
// server.h ... #define LRU_BITS 24 ... typedef struct redisObject { unsigned type:4; 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; void *ptr; } robj;
type
: 4 bits, 表示是什么类型的Redis Object。encoding
: 4 bits, 类型的编码,是指定使用type
的编码方式。*ptr
: 指向底层实现数据结构的指针。lru
: 24 bits, 记录了该对象最后一次被命令程序访问的时间。refcount
: 引用计数。对象 | 对象type的属性值 | TYPE 命令的输出 |
---|---|---|
字符串 | OBJ_STRING 0 |
"string" |
列表 | OBJ_LIST 1 |
"list" |
集合 | OBJ_SET 2 |
"set" |
有序集合 | OBJ_ZSET 3 |
"zset" |
哈希 | OBJ_HASH 4 |
"hash" |
编码常量 | 对应底层数据结构 |
---|---|
OBJ_ENCODING_RAW 0 |
简单动态字符串,只有string类型才会使用这个encoding值 |
OBJ_ENCODING_INT 1 |
long类型的整数 |
OBJ_ENCODING_HT 2 |
字典dict |
OBJ_ENCODING_ZIPMAP 3 |
旧类,再也不使用 |
OBJ_ENCODING_LINKEDLIST 4 |
双端列表,已再也不用 |
OBJ_ENCODING_ZIPLIST 5 |
压缩列表ziplist |
OBJ_ENCODING_INTSET 6 |
整数集合intset |
OBJ_ENCODING_SKIPLIST 7 |
跳跃表skiplist ,用于有序集合 |
OBJ_ENCODING_EMBSTR 8 |
表示一种与robj 嵌入的特殊sds 字符串 |
OBJ_ENCODING_QUICKLIST 9 |
quicklist 快速列表 |
类型 | 编码 | 对象 |
---|---|---|
OBJ_STRING |
OBJ_ENCODING_RAW 0 |
使用SDS字符串实现的字符串对象 |
OBJ_STRING |
OBJ_ENCODING_INT 1 |
使用整数值实现的字符串对象(整数值类型为long ) |
OBJ_STRING |
OBJ_ENCODING_EMBSTR 8 |
使用SDS字符串实现的字符串对象,主要区别为在内存中字符串与对象相邻 |
OBJ_ENCODING_INT
在robj
中,有一个属性refcount
是用来保存引用计数,对于字符串对象,Redis会在初始化服务器时,建立1万个字符串对象,包含了 0 ~ 9999 的整数字,当须要使用到这些整数字符串对象,服务器就会使用这些共享对象,而不须要新建整数对象。数据库
OBJ_ENCODING_EMBSTR
与 OBJ_ENCODING_RAW
的区别若是对象中保存的字符串长度小于OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
将会使用OBJ_ENCODING_EMBSTR
编码。缓存
//object.c /* Create a string object with EMBSTR encoding if it is smaller than * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is * used. * * The current limit of 39 is chosen so that the biggest string object * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */ #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44 robj *createStringObject(const char *ptr, size_t len) { if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) // 若是长度小于OBJ_ENCODING_EMBSTR_SIZE_LIMIT,将会调用createEmbeddedStringObject()建立 OBJ_ENCODING_EMBSTR 编码的字符串对象 return createEmbeddedStringObject(ptr,len); else // 若是长度大于OBJ_ENCODING_EMBSTR_SIZE_LIMIT,将会调用createRawStringObject()建立 OBJ_ENCODING_RAW 编码的字符串对象 return createRawStringObject(ptr,len); }
OBJ_ENCODING_EMBSTR
编码是用于专门保存短字符串的优化方式,同样会使用SDS结构来保存字符串。但OBJ_ENCODING_RAW
编码会调用两次内存分配函数来分别建立robj
和SDS字符串。而OBJ_ENCODING_EMBSTR
编码则经过调用一次内存分配函数来分配一个连续的空间,空间中依次包含robj
和SDS字符串。服务器
OBJ_ENCODING_EMBSTR
优势:数据结构
OBJ_ENCODING_EMBSTR
编码的字符串更好得利用SDS缓存带来的优点。OBJ_ENCODING_EMBSTR
编码的字符串对象中字符串作任何改变,都会使其转换成OBJ_ENCODING_RAW
编码。robj *createStringObjectFromLongLong(long long value)
建立OBJ_ENCODING_INT
编码的字符串对象:app
// object.c robj *createStringObjectFromLongLong(long long value) { robj *o; // 若是值在 0 ~ 9999 之间,则返回共享的整数字符串对象 if (value >= 0 && value < OBJ_SHARED_INTEGERS) { // 若是值在 0 ~ 9999 之间,则返回共享的整数字符串对象 incrRefCount(shared.integers[value]); o = shared.integers[value]; } else { if (value >= LONG_MIN && value <= LONG_MAX) { // 若是值是在 long 的范围内,则直接保存long类型的整数值 // 来建立整数字符串对象 o = createObject(OBJ_STRING, NULL); o->encoding = OBJ_ENCODING_INT; o->ptr = (void*)((long)value); } else { // 若是值是在 long 的范围外,则使用SDS字符串来保存整数 // 来建立整数字符串对象 o = createObject(OBJ_STRING,sdsfromlonglong(value)); } } return o; }
robj *createEmbeddedStringObject(const char *ptr, size_t len)
建立OBJ_ENCODING_EMBSTR
编码的字符串对象:less
/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is * an object where the sds string is actually an unmodifiable string * allocated in the same chunk as the object itself. */ robj *createEmbeddedStringObject(const char *ptr, size_t len) { // 申请robj、sdshdr和字符长度之和的内存 robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1); // sdshdr8 紧接着 robj struct sdshdr8 *sh = (void*)(o+1); // 初始化robj的属性值 o->type = OBJ_STRING; o->encoding = OBJ_ENCODING_EMBSTR; // 指向sds字符串结构 o->ptr = sh+1; o->refcount = 1; // 设置lru访问时间 if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL; } else { o->lru = LRU_CLOCK(); } // sds字符串初始化 sh->len = len; sh->alloc = len; sh->flags = SDS_TYPE_8; if (ptr) { memcpy(sh->buf,ptr,len); sh->buf[len] = '\0'; } else { memset(sh->buf,0,len+1); } return o; }
robj *createRawStringObject(const char *ptr, size_t len)
建立OBJ_ENCODING_RAW
编码的字符串对象:ide
/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain * string object where o->ptr points to a proper sds string. */ robj *createRawStringObject(const char *ptr, size_t len) { // 直接调用createObject()建立字符串对象 return createObject(OBJ_STRING, sdsnewlen(ptr,len)); } 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程序访问时间 /* Set the LRU to the current lruclock (minutes resolution), or * alternatively the LFU counter. */ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL; } else { o->lru = LRU_CLOCK(); } return o; }
类型 | 编码 | 对象 |
---|---|---|
OBJ_LIST |
OBJ_ENCODING_ZIPLIST 5 |
使用压缩列表建立的列表对象(在Redis 4.0.11代码中看,彷佛没有使用该编码的列表对象了) |
OBJ_LIST |
OBJ_ENCODING_QUICKLIST 9 |
使用快速列表建立的列表对象 |
在Redis 4.0.11代码中看,列表对象的底层实现,都采用快速列表(quicklist)实现了。函数
robj *createQuicklistObject(void)
建立一个空的编码为OBJ_ENCODING_QUICKLIST
的列表对象:
// object.c robj *createQuicklistObject(void) { // 建立一个空的快速链表 quicklist *l = quicklistCreate(); // 传入快速列表,建立列表对象,而后将编码指定为快速列表 robj *o = createObject(OBJ_LIST,l); o->encoding = OBJ_ENCODING_QUICKLIST; return o; }
void listTypePush(robj *subject, robj *value, int where)
将value
添加到列对象subject
中,where
控制是插入队列尾仍是队列头:
// t_list.c void listTypePush(robj *subject, robj *value, int where) { // 判断subject是否为队列(4.0.11中全部队列的实现均为快速列表) if (subject->encoding == OBJ_ENCODING_QUICKLIST) { // where 为 0 表示列表头,不然表示列表尾 int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL; // 将value解码 // 其实是判断value是哪一种编码的字符串,若是是OBJ_ENCODING_INT类型的字符串 // 则将其 整型值 转成一个sds字符串类型,并返回一个新的obj对象 // 若是是 OBJ_ENCODING_EMBSTR 或 OBJ_ENCODING_RAW,则只是对value的引用值+1 value = getDecodedObject(value); size_t len = sdslen(value->ptr); // 将value的值加入到快速列表中(头或尾) quicklistPush(subject->ptr, value->ptr, len, pos); // 判断value是否能够被释放 // 若是value的refcount == 1, 将会调用对应释放函数 // 若是value的refcount > 1, 则将其值减1 decrRefCount(value); } else { serverPanic("Unknown list encoding"); } }
robj *listTypePop(robj *subject, int where)
在队列subject
中从where
指定的头或者尾中弹出(pop)元素项,并返回:
robj *listTypePop(robj *subject, int where) { long long vlong; robj *value = NULL; // where 为 0 表示列表头,不然表示列表尾 int ql_where = where == LIST_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL; // 检查subject的编码是否为快速队列 if (subject->encoding == OBJ_ENCODING_QUICKLIST) { // 在快速队列中弹出一个元素项,并调用在快速列表的函数中调用listPopSaver()将返回值封装成robj // 若是元素项的值是(long long类型)整数,则写在vlong中,这时候须要本身将其组装成robj if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value, NULL, &vlong, listPopSaver)) { if (!value) // 弹出的元素项是long long整数,封装成robj value = createStringObjectFromLongLong(vlong); } } else { serverPanic("Unknown list encoding"); } return value; } void *listPopSaver(unsigned char *data, unsigned int sz) { return createStringObject((char*)data,sz); }
哈希对象的底层编码是压缩列表 (ziplist)和字典(dict,hashtable)。
类型 | 编码 | 对象 |
---|---|---|
OBJ_HASH |
OBJ_ENCODING_ZIPLIST 5 |
压缩列表实现的哈希对象 |
OBJ_HASH |
OBJ_ENCODING_HT 2 |
字典(哈希表)实现的哈希对象 |
在 Redis 4.0.11 中,当知足如下两条件之一时,OBJ_ENCODING_ZIPLIST
编码的哈希对象,会转换成OBJ_ENCODING_HT
编码的哈希对象:
OBJ_HASH_MAX_ZIPLIST_ENTRIES 512
(该值能够经过配置hash-max-ziplist-entries
改变)时,将进行编码转换。OBJ_HASH_MAX_ZIPLIST_VALUE 64
(该值能够经过配置hash-max-ziplist-value
改变)时,将进行编码转换。默认状况下,Redis会使用OBJ_ENCODING_ZIPLIST
编码,直至以上两种状况的其中一种发生。
PS: 从代码上看,目前只支持OBJ_ENCODING_ZIPLIST
-> OBJ_ENCODING_HT
,不支持OBJ_ENCODING_HT
-> OBJ_ENCODING_ZIPLIST
。
robj *createHashObject(void)
建立一个空的哈希对象:
// object.c robj *createHashObject(void) { // 新建一个压缩列表 unsigned char *zl = ziplistNew(); // 建立一个哈希对象,并指定编码为压缩列表 robj *o = createObject(OBJ_HASH, zl); o->encoding = OBJ_ENCODING_ZIPLIST; return o; }
int hashTypeSet(robj *o, sds field, sds value, int flags)
将键field
,值value
添加到哈希对象o
中:
// t_hash.c /* Add a new field, overwrite the old with the new value if it already exists. * Return 0 on insert and 1 on update. * * By default, the key and value SDS strings are copied if needed, so the * caller retains ownership of the strings passed. However this behavior * can be effected by passing appropriate flags (possibly bitwise OR-ed): * * HASH_SET_TAKE_FIELD -- The SDS field ownership passes to the function. * HASH_SET_TAKE_VALUE -- The SDS value ownership passes to the function. * * When the flags are used the caller does not need to release the passed * SDS string(s). It's up to the function to use the string to create a new * entry or to free the SDS string before returning to the caller. * * HASH_SET_COPY corresponds to no flags passed, and means the default * semantics of copying the values if needed. * */ #define HASH_SET_TAKE_FIELD (1<<0) #define HASH_SET_TAKE_VALUE (1<<1) #define HASH_SET_COPY 0 // 将键`field`,值`value`添加到哈希对象`o`中,若是键`field`已经存在,将会用新值`value`覆盖旧值 // flags的做用是标志是否要在函数中释放`filed`或者`value` int hashTypeSet(robj *o, sds field, sds value, int flags) { int update = 0; if (o->encoding == OBJ_ENCODING_ZIPLIST) { // 若是hash对象是 压缩列表 unsigned char *zl, *fptr, *vptr; // zl指向压缩列表 zl = o->ptr; // 获取压缩列表中头元素向的位置 fptr = ziplistIndex(zl, ZIPLIST_HEAD); if (fptr != NULL) { // 检查压缩列表中,是否已经包含键field fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); if (fptr != NULL) { // 若是键field已经存在,则在如下操做中更新对应的value /* Grab pointer to the value (fptr points to the field) */ // vprt指向对应的值位置 vptr = ziplistNext(zl, fptr); serverAssert(vptr != NULL); // 将更新标志位置为1 update = 1; // 删除旧值 /* Delete value */ zl = ziplistDelete(zl, &vptr); // 添加新值 /* Insert new value */ zl = ziplistInsert(zl, vptr, (unsigned char*)value, sdslen(value)); } } // 若是标志位 为 0,表示filed不在压缩队列中,须要插入 // 若是标志位 为 1,表示已经更新,无须操做 if (!update) { /* Push new field/value pair onto the tail of the ziplist */ zl = ziplistPush(zl, (unsigned char*)field, sdslen(field), ZIPLIST_TAIL); zl = ziplistPush(zl, (unsigned char*)value, sdslen(value), ZIPLIST_TAIL); } o->ptr = zl; /* Check if the ziplist needs to be converted to a hash table */ // 检查键值对的数量,看是否须要将底层实现转换编码为字典(哈希表) if (hashTypeLength(o) > server.hash_max_ziplist_entries) hashTypeConvert(o, OBJ_ENCODING_HT); } else if (o->encoding == OBJ_ENCODING_HT) { // 若是hash对象的编码为字典(哈希表) // 查找键filed是否已经存在,若是有这返回de dictEntry *de = dictFind(o->ptr,field); if (de) { // 值已经存在,把旧值释放掉 sdsfree(dictGetVal(de)); // 将新值value写入,经过flags判断是否须要释放value if (flags & HASH_SET_TAKE_VALUE) { dictGetVal(de) = value; value = NULL; } else { dictGetVal(de) = sdsdup(value); } update = 1; } else { // 插入新键值对 sds f,v; // 经过flags判断是否须要释放field if (flags & HASH_SET_TAKE_FIELD) { f = field; field = NULL; } else { f = sdsdup(field); } // 经过flags判断是否须要释放value if (flags & HASH_SET_TAKE_VALUE) { v = value; value = NULL; } else { v = sdsdup(value); } // 往字典添加新键值对 dictAdd(o->ptr,f,v); } } else { serverPanic("Unknown hash encoding"); } /* Free SDS strings we did not referenced elsewhere if the flags * want this function to be responsible. */ // 经过flags判断 // 释放掉对应的failed或者value if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field); if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value); return update; }
集合对象的底层编码是字典(dict,hashtable)和整数集合(intset)。
类型 | 编码 | 对象 |
---|---|---|
OBJ_SET |
OBJ_ENCODING_HT 2 |
字典实现的集合对象 |
OBJ_SET |
OBJ_ENCODING_INTSET 6 |
整数集合实现的集合对象 |
在 Redis 4.0.11 中,当知足如下两条件之一时,OBJ_ENCODING_INTSET
编码的集合对象,会转换成OBJ_ENCODING_HT
编码的集合对象:
OBJ_ENCODING_INTSET
会转变为OBJ_ENCODING_HT
。OBJ_SET_MAX_INTSET_ENTRIES 512
(能够经过设置set-max-intset-entries
改变数值)时,OBJ_ENCODING_INTSET
会转变为OBJ_ENCODING_HT
。PS: 从代码上看,目前只支持OBJ_ENCODING_INTSET
-> OBJ_ENCODING_HT
,不支持OBJ_ENCODING_HT
-> OBJ_ENCODING_INTSET
。
robj *setTypeCreate(sds value)
经过值值value
得类型来判断建立一个集合对象,这个方法并不会将值value
放入到集合中:
robj *setTypeCreate(sds value) { if (isSdsRepresentableAsLongLong(value,NULL) == C_OK) // 判断值'value'是否为一个整数,若是是,则使用整数集合编码的集合对象 return createIntsetObject(); // 'value'值不是整数,使用字典编码的集合对象 return createSetObject(); }
int setTypeAdd(robj *subject, sds value)
将值value
添加到集合subject
中,若是值已经存在,函数返回0,不然返回1:
/* Add the specified value into a set. * * If the value was already member of the set, nothing is done and 0 is * returned, otherwise the new element is added and 1 is returned. */ int setTypeAdd(robj *subject, sds value) { long long llval; if (subject->encoding == OBJ_ENCODING_HT) { // 若是集合编码是字典 dict *ht = subject->ptr; // 往字典ht中添加键value // 若是键已经存在于ht, dictAddRaw()会返回NULL // 不然会返回一个哈希节点 dictEntry *de = dictAddRaw(ht,value,NULL); if (de) { // 设置节点的键值 // 因为是集合,因此字典节点的值设为NULL dictSetKey(ht,de,sdsdup(value)); dictSetVal(ht,de,NULL); return 1; } } else if (subject->encoding == OBJ_ENCODING_INTSET) { // 若是集合编码是 整数集合 // 判断要插入的值value是否能够转成整数 if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) { // 若是能够转成整数,则能够继续用 整数集合 编码 uint8_t success = 0; // 尝试插入的键值 // 若是成功则 success 为 1 // 若是值已经存在,则success 为 0 subject->ptr = intsetAdd(subject->ptr,llval,&success); if (success) { // 成功,检查集合对象中元素项的数量,若是超过限制,则转换编码为字典编码 /* Convert to regular set when the intset contains * too many entries. */ if (intsetLen(subject->ptr) > server.set_max_intset_entries) setTypeConvert(subject,OBJ_ENCODING_HT); return 1; } } else { // 若是不能够转成整数,则须要将编码转为 字典 /* Failed to get integer from object, convert to regular set. */ setTypeConvert(subject,OBJ_ENCODING_HT); // 转换后,字典插入键值 /* The set *was* an intset and this value is not integer * encodable, so dictAdd should always work. */ serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK); return 1; } } else { serverPanic("Unknown set encoding"); } return 0; }
有序集合对象的底层编码是压缩列表(ziplist)和跳跃表(skiplist)。
类型 | 编码 | 对象 |
---|---|---|
OBJ_ZSET |
OBJ_ENCODING_ZIPLIST 6 |
压缩列表实现的有序集合对象 |
OBJ_ZSET |
OBJ_ENCODING_SKIPLIST 7 |
跳跃表实现的有序集合对象 |
压缩表内的集合元素按照分值从小到大排序,每一个集合元素使用2个压缩表节点表示,第一个节点保存元素的成员(member),第二个节点保存元素的分值(score)。
跳跃表的时序,是采用zset
结构,zset
中同时包含一个字典和一个跳跃表。
// server.h typedef struct zset { dict *dict; zskiplist *zsl; } zset;
结构如图(图来之《Redis设计与实现》):
在 Redis 4.0.11 中,当知足如下两条件之一时,OBJ_ENCODING_ZIPLIST
编码的有序集合对象,会转换成OBJ_ENCODING_SKIPLIST
编码的有序集合对象:
OBJ_ZSET_MAX_ZIPLIST_ENTRIES 128
(能够经过设置zset-max-ziplist-entries
改变数值)时,OBJ_ENCODING_ZIPLIST
会转变为OBJ_ENCODING_SKIPLIST
;OBJ_ZSET_MAX_ZIPLIST_VALUE 64
(能够经过设置zset-max-ziplist-value
改变数值)时,OBJ_ENCODING_ZIPLIST
会转变为OBJ_ENCODING_SKIPLIST
;若是OBJ_ENCODING_SKIPLIST
中的元素知足OBJ_ZSET_MAX_ZIPLIST_ENTRIES 128
和OBJ_ZSET_MAX_ZIPLIST_VALUE 64
,也会编码为OBJ_ENCODING_ZIPLIST
的。(PS: 在 Redis 4.0.11 中,只有执行ZINTERSTORE
或者ZUNIONSTORE
两个命令才会触发,由于在代码上看,zsetConvertToZiplistIfNeeded()
函数只有在zunionInterGenericCommand()
函数函数中有调用)。
void zaddGenericCommand(client *c, int flags)
ZADD
和ZINCRBY
命令的实现函数:
// t_zset.c /* This generic command implements both ZADD and ZINCRBY. */ void zaddGenericCommand(client *c, int flags) { ... /* Lookup the key and create the sorted set if does not exist. */ 检查key是否在数据库中 zobj = lookupKeyWrite(c->db,key); if (zobj == NULL) { // Key不在数据库中,则新建一个 有序集合对象 if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */ if (server.zset_max_ziplist_entries == 0 || server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr)) { // 若是设置了”zset_max_ziplist_entries“为0,表示全部的有序集合对象 都以跳跃表实现 // 检查输入的元素的字符长度是否超过了server.zset_max_ziplist_value,超过了则使用跳跃表编码的有序集合 zobj = createZsetObject(); } else { // 没有强制使用 跳跃表 编码 // 并且 // 输入的第一个元素字符长度小于server.zset_max_ziplist_value zobj = createZsetZiplistObject(); } dbAdd(c->db,key,zobj); } else { if (zobj->type != OBJ_ZSET) { addReply(c,shared.wrongtypeerr); goto cleanup; } } ... }
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore)
往有序集合对象zobj
中添加分值为score
的元素ele
;flags
是传入的操做标识,同时也是函数结果标识;newscore
若是不传入NULL
,当使用ZADD_INCR
的时候,会将新值写入newscore
:
// server.h /* Input flags. */ #define ZADD_NONE 0 #define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */ #define ZADD_NX (1<<1) /* Don't touch elements not already existing. */ #define ZADD_XX (1<<2) /* Only touch elements already exisitng. */ /* Output flags. */ #define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/ #define ZADD_NAN (1<<4) /* Only touch elements already exisitng. */ #define ZADD_ADDED (1<<5) /* The element was new and was added. */ #define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */ // t_zset.c /* Add a new element or update the score of an existing element in a sorted * set, regardless of its encoding. * * The set of flags change the command behavior. They are passed with an integer * pointer since the function will clear the flags and populate them with * other flags to indicate different conditions. * * The input flags are the following: * * 对当前元素的分值进行增长,而不是更新操做。若是元素不存在,则以0当作以前的分值。 * ZADD_INCR: Increment the current element score by 'score' instead of updating * the current element score. If the element does not exist, we * assume 0 as previous score. * 仅当元素不存在才执行操做 * ZADD_NX: Perform the operation only if the element does not exist. * 仅当元素存在才执行操做 * ZADD_XX: Perform the operation only if the element already exist. * * When ZADD_INCR is used, the new score of the element is stored in * '*newscore' if 'newscore' is not NULL. * * The returned flags are the following: * 返回结果的缘由: * * * 给定的分值并非一个数字 * ZADD_NAN: The resulting score is not a number. * 元素是新增的 * ZADD_ADDED: The element was added (not present before the call). * 元素的分值已经更新(说明元素不是新增的) * ZADD_UPDATED: The element score was updated. * 由于ZADD_NX和ZADD_XX的缘故,没有执行操做 * ZADD_NOP: No operation was performed because of NX or XX. * * Return value: * * The function returns 1 on success, and sets the appropriate flags * ADDED or UPDATED to signal what happened during the operation (note that * none could be set if we re-added an element using the same score it used * to have, or in the case a zero increment is used). * * The function returns 0 on erorr, currently only when the increment * produces a NAN condition, or when the 'score' value is NAN since the * start. * * The commad as a side effect of adding a new element may convert the sorted * set internal encoding from ziplist to hashtable+skiplist. * * Memory managemnet of 'ele': * * The function does not take ownership of the 'ele' SDS string, but copies * it if needed. */ // 函数返回1表示执行成功,0 表示失败,成功或者失败的理由能够参考flags的值 int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) { /* Turn options into simple to check vars. */ // 若是incr不是0, 表示分值是相加操做 int incr = (*flags & ZADD_INCR) != 0; // 若是nx不是0,则仅当元素不存在才执行操做 int nx = (*flags & ZADD_NX) != 0; // 若是xx不是0,仅当元素存在才执行操做 int xx = (*flags & ZADD_XX) != 0; *flags = 0; /* We'll return our response flags. */ double curscore; // 判断传入的分值是否一个无效参数 /* NaN as input is an error regardless of all the other parameters. */ if (isnan(score)) { *flags = ZADD_NAN; return 0; } /* Update the sorted set according to its encoding. */ if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { // 编码为 压缩列表 unsigned char *eptr; if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) { // ele元素已经存在 /* NX? Return, same element already exists. */ if (nx) { // nx(则仅当元素不存在才执行操做)不为0 // 缘由写入flags *flags |= ZADD_NOP; return 1; } /* Prepare the score for the increment if needed. */ if (incr) { // 分值相加操做 score += curscore; if (isnan(score)) { *flags |= ZADD_NAN; return 0; } // 若是newscore不是NULL,写入newscore,让函数调用者能够获得最新的分值 if (newscore) *newscore = score; } /* Remove and re-insert when score changed. */ if (score != curscore) { // 分值变更了,将旧的节点删除,从新插入,使得从新排序 zobj->ptr = zzlDelete(zobj->ptr,eptr); zobj->ptr = zzlInsert(zobj->ptr,ele,score); *flags |= ZADD_UPDATED; } return 1; } else if (!xx) { // ele元素不存在,且xx(仅当元素存在才执行操做)为0 /* Optimize: check if the element is too large or the list * becomes too long *before* executing zzlInsert. */ // 压缩列表的整数集合插入新节点 zobj->ptr = zzlInsert(zobj->ptr,ele,score); // 检查整数集合的长度是否超出了“zset_max_ziplist_entries”而致使须要转换编码 if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries) zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); // 检查新插入元素ele的长度是否超出了“zset_max_ziplist_value”而致使须要转换编码 if (sdslen(ele) > server.zset_max_ziplist_value) zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); if (newscore) *newscore = score; *flags |= ZADD_ADDED; return 1; } else { *flags |= ZADD_NOP; return 1; } } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { // 编码为 跳跃表 zset *zs = zobj->ptr; zskiplistNode *znode; dictEntry *de; // 在字典中查找该元素ele是否已经存在 de = dictFind(zs->dict,ele); if (de != NULL) { // ele已经存在 /* NX? Return, same element already exists. */ if (nx) { // nx(则仅当元素不存在才执行操做)不为0 // 缘由写入flags *flags |= ZADD_NOP; return 1; } curscore = *(double*)dictGetVal(de); /* Prepare the score for the increment if needed. */ if (incr) { // 分值相加操做 score += curscore; if (isnan(score)) { *flags |= ZADD_NAN; return 0; } // 若是newscore不是NULL,写入newscore,让函数调用者能够获得最新的分值 if (newscore) *newscore = score; } /* Remove and re-insert when score changes. */ if (score != curscore) { // 更新分值 zskiplistNode *node; // 先删除旧的节点 serverAssert(zslDelete(zs->zsl,curscore,ele,&node)); // 从新以新的分值插入 znode = zslInsert(zs->zsl,score,node->ele); /* We reused the node->ele SDS string, free the node now * since zslInsert created a new one. */ node->ele = NULL; zslFreeNode(node); /* Note that we did not removed the original element from * the hash table representing the sorted set, so we just * update the score. */ // 更新字典中的信息 dictGetVal(de) = &znode->score; /* Update score ptr. */ *flags |= ZADD_UPDATED; } return 1; } else if (!xx) { // ele存在于有序集合 // 新建节点插入 ele = sdsdup(ele); znode = zslInsert(zs->zsl,score,ele); serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK); *flags |= ZADD_ADDED; if (newscore) *newscore = score; return 1; } else { *flags |= ZADD_NOP; return 1; } } else { serverPanic("Unknown sorted set encoding"); } return 0; /* Never reached. */ }
redisObject
中的属性refcount
是记录对象的引用计数
refcount
初始化为1;refcount
增1;refcount
减1;refcount
为0时,所占用的内存会被释放。函数 | 做用 |
---|---|
void decrRefCount(robj *o) |
引用计数减1,若是减1后等于0,则释放对象 |
void incrRefCount(robj *o) |
引用计数增1 |
robj *resetRefCount(robj *obj) |
将引用计数设为0,但不释放对象 |
使用refcount
,能够实现对象共享,Redis会在初始化服务器时,建立1万个字符串对象,包含了 0 ~ 9999 的整数字,当须要使用到这些整数字符串对象,服务器就会使用这些共享对象,而不须要新建整数对象。