redis 源码分析(一)HashTable 上篇: https://www.jianshu.com/p/a57a6e389a03
本文主要介绍 redis HashTable dict 的迭代器部分,同时给出了关于 dict 所有 API 的简要功能介绍,最后列举了 redis 关于 HashTable 的几个命令实现。关于 redis HashTable 特性的介绍,其实在 redis 源码分析(一)HashTable 上篇 已经介绍的很清楚了,本文纯属个人对源码的兴趣,或许阐述的不够清楚,敬请谅解。
一、dictIterator dict 的迭代器
1. 结构体 dictIterator
typedef struct dictIterator {
dict *d;
long index;
int table, safe;
dictEntry *entry, *nextEntry;
long long fingerprint;
} dictIterator;
-
dictIterator.dict指明当前迭代器正在迭代的dict -
dictIterator.index当前迭代到的dict.dictht[0/1].table的下标 -
dictIterator.table当前要迭代的dict.dictht的下标 -
dictIterator.safe当前迭代器是否是安全的 -
dictIterator.entry迭代器的当前dictEntry -
dictIterator.nextEntry迭代器的下一个dictEntry -
dictIterator.fingerprint开始迭代时dict的指纹
2. dictFingerprint 获取 dict 指纹
long long dictFingerprint(dict *d) {
long long integers[6], hash = 0;
int j;
integers[0] = (long) d->ht[0].table;
integers[1] = d->ht[0].size;
integers[2] = d->ht[0].used;
integers[3] = (long) d->ht[1].table;
integers[4] = d->ht[1].size;
integers[5] = d->ht[1].used;
for (j = 0; j < 6; j++) {
hash += integers[j];
hash = (~hash) + (hash << 21);
hash = hash ^ (hash >> 24);
hash = (hash + (hash << 3)) + (hash << 8);
hash = hash ^ (hash >> 14);
hash = (hash + (hash << 2)) + (hash << 4);
hash = hash ^ (hash >> 28);
hash = hash + (hash << 31);
}
return hash;
}
-
dict.dictht[0]和dict.dictht[1]的sizeused以及table指针的地址,这6个信息参与了dict指纹的计算,我们可以理解:当dict发生变化时,dict的指纹也会发生变化。
3. dictGetSafeIterator 和 dictGetIterator 获取 dict 的一个迭代器
dictIterator *dictGetIterator(dict *d){
dictIterator *iter = zmalloc(sizeof(*iter));
iter->d = d;
iter->table = 0;
iter->index = -1;
iter->safe = 0;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
dictIterator *dictGetSafeIterator(dict *d) {
dictIterator *i = dictGetIterator(d);
i->safe = 1;
return i;
}
- 安全迭代器
dictIterator.safe=1 - 不安全迭代器
dictIterator.safe=0 - 安全迭代器和不安全迭代器的区别是:
不安全迭代器只能使用 dictNext 方法,安全迭代器能使用与 dict 相关的所有方法
4. dictNext 迭代器下一步
dictEntry *dictNext(dictIterator *iter){
while (1) {
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
if (iter->index == -1 && iter->table == 0) {
if (iter->safe)
iter->d->iterators++;
else
iter->fingerprint = dictFingerprint(iter->d);
}
iter->index++;
if (iter->index >= (long) ht->size) {
if (dictIsRehashing(iter->d) && iter->table == 0) {
iter->table++;
iter->index = 0;
ht = &iter->d->ht[1];
} else {
break;
}
}
iter->entry = ht->table[iter->index];
} else {
iter->entry = iter->nextEntry;
}
if (iter->entry) {
iter->nextEntry = iter->entry->next;
return iter->entry;
}
}
return NULL;
}
dictNext 的逻辑是:
- 从
dictIterator.index开始遍历dictIterator.dict.dictht[dictIterator.table]中的dictEntry - 如果
dictIterator.index >= dictIterator.dict.dictht[dictIterator.table].size && dictIterator.dict.rehashidx == -1(迭代器的迭代下标大于等于正在迭代的dict.dictht[dictIterator.table].size并且dictIterator.dict并没有处于rehash状态)时,说明迭代完成了。 - 如果
dictIterator.safe == 1会dictIterator.dict.iterators++即:dict.iterators记录了自身安全迭代器的数量(因为不安全的迭代器不会改变dict) - 如果
dictIterator.safe == 0会在第一次dictNext时通过dictFingerprint记录dict的指纹,当释放迭代器时,会比对当时的dict指纹和刚迭代时的dict指纹,这样就能知道,在整个迭代过程中dict是否发生过改变
5. dictReleaseIterator 释放 dict 迭代器
void dictReleaseIterator(dictIterator *iter){
if (!(iter->index == -1 && iter->table == 0)) {
if (iter->safe)
iter->d->iterators--;
else
assert(iter->fingerprint == dictFingerprint(iter->d));
}
zfree(iter);
}
dictReleaseIterator 的逻辑比较简单,除了释放自身所在内存外,还会 dict.iterators-- 以维护每个 dict 的迭代器数量
二、dict API 介绍
dict *dictCreate(dictType *type, void *privDataPtr);
int dictExpand(dict *d, unsigned long size);
int dictAdd(dict *d, void *key, void *val);
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing);
dictEntry *dictAddOrFind(dict *d, void *key);
int dictReplace(dict *d, void *key, void *val);
int dictDelete(dict *d, const void *key);
dictEntry *dictUnlink(dict *ht, const void *key);
void dictFreeUnlinkedEntry(dict *d, dictEntry *he);
void dictRelease(dict *d);
dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
dictEntry *dictGetFairRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictGetStats(char *buf, size_t bufsize, dict *d);
uint64_t dictGenHashFunction(const void *key, int len);
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *d, void(callback)(void*));
void dictEnableResize(void);
void dictDisableResize(void);
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(uint8_t *seed);
uint8_t *dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
uint64_t dictGetHash(dict *d, const void *key);
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);
-
dictCreate初始化一个dict(此时dict.dictht[0].table和dict.dictht[1].table都还是 null) -
dictExpand对dict进行扩容(第一次对刚初始化的dict进行扩容时,会扩容到 4 个dictEntry大小) -
dictAdd向dict中放入一对 key=>value(底层会调dictAddRaw) -
dictAddRaw在dict.dictht[0/1].tablekey 应该存储的位置放一个空dictEntry -
dictAddOrFind和dictAddRaw的区别是:dictAddRaw当 key 应该存储的位置上已经有dictEntry时会返回这个dictEntry,而dictAddRaw则会报错 -
dictReplace替换 key 的 value。如果 key 不存在,等同于dictAdd -
dictDelete删除 key -
dictUnlink删除 key,区别在于dictDelete会删除 key 对应的dictEntry所在列表后,立即释放掉dictEntry,而dictUnlink则会将dictEntry返回,但用户使用完dictEntry后,需要手动调用dictFreeUnlinkedEntry释放 -
dictFreeUnlinkedEntry释放dictUnlink返回的dictEntry -
dictRelease删除并释放dict(重点是需要释放dict.dictht[0/1].table) -
dictFind在dict中查找 key -
dictFetchValue获取 key 的 value(底层调用dictFind实现) -
dictResize带条件的dictExpand -
dictGetIterator获取一个不安全的迭代器 -
dictGetSafeIterator获取一个安全的迭代器 -
dictNext顺着迭代器访问下一个dictEntry -
dictReleaseIterator释放迭代器 -
dictGetRandomKey随机获取一个 key 的dictEntry -
dictGetFairRandomKey从dictGetSomeKeys的一组dictEntry中随机返回一个 -
dictGetSomeKeys随机获取指定数量个 key 的dictEntry数组 -
dictGetStats输出 HashTable 的 stats 信息,包含:table sizenumber of elementsdifferent slotsmax chain lengthavg chain length (counted)avg chain length (computed)等信息 -
dictGenHashFunction返回一个siphashHashFunction -
dictGenCaseHashFunction返回一个siphash_nocaseHashFunction -
dictEmpty清空dict,清空后的dict比dictCreate后的dict的区别是:清空后的dict,dict.dictht[0/1].table是个空数组,而非 null -
dictEnableResize设置 dict_can_resize 为 1 -
dictDisableResize设置 dict_can_resize 为 0 -
dictRehashrehash 指定个下标 -
dictRehashMillisecondsrehash 指定时间 -
dictScan遍历dict,和dictIterator的区别是,dictScan直接给出了 next 的回调函数。用php 数组作比:dictIterator是 foreach,dictScan是 array_walk -
dictGetHash计算一个 key 的 hash 值(dict.dictht[0/1].table的下标)
三、redis dict 几个相关命令的实现
1. 结构体
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4
#define OBJ_ENCODING_RAW 0
#define OBJ_ENCODING_INT 1
#define OBJ_ENCODING_HT 2
#define OBJ_ENCODING_ZIPMAP 3
#define OBJ_ENCODING_LINKEDLIST 4
#define OBJ_ENCODING_ZIPLIST 5
#define OBJ_ENCODING_INTSET 6
#define OBJ_ENCODING_SKIPLIST 7
#define OBJ_ENCODING_EMBSTR 8
#define OBJ_ENCODING_QUICKLIST 9
#define OBJ_ENCODING_STREAM 10
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;
2. hsetnx(key, field, value)
功能:如果 key 是 HashTable 且 field 不存在时,将 key.field 设置为 value
robj *o;
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTypeTryConversion(o,c->argv,2,3);
if (hashTypeExists(o, c->argv[2]->ptr)) {
addReply(c, shared.czero);
} else {
hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
addReply(c, shared.cone);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
hsetnx 的逻辑是(以 key robj.encoding=OBJ_ENCODING_HT为例):
-
hashTypeLookupWriteOrCreate先在 db 中寻找 key 的 robj,如果找不到会创建一个type=OBJ_HASH, encoding=OBJ_ENCODING_ZIPLIST的 robj 并放到 db 中(因为 key 如果不存在 hsetnx 必然成功) -
hashTypeTryConversion如果key对应的robj.type == OBJ_ENCODING_ZIPLIST且filed或者value的 encoding 是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR且长度大于了 64,会将key的 robj.type 改为 OBJ_ENCODING_HT -
hashTypeExists会调用dictFind来查找 key -
hashTypeSet如果dictFind找到了就修改dictEntry.v,如果没找到就dictAdd一个 -
addReply返回操作结果
3. hset(key, field, value) 或 hmset(key, field1, value1, field2, value2,...)
功能:向 HashTable
key存入 key=>value
int i, created = 0;
robj *o;
if ((c->argc % 2) == 1) {
addReplyError(c,"wrong number of arguments for HMSET");
return;
}
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTypeTryConversion(o,c->argv,2,c->argc-1);
for (i = 2; i < c->argc; i += 2)
created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
char *cmdname = c->argv[0]->ptr;
if (cmdname[1] == 's' || cmdname[1] == 'S') {
addReplyLongLong(c, created);
} else {
addReply(c, shared.ok);
}
hset 和 hmset 的逻辑是:
-
hset和hmset的redisCommand是同一个Command实现
{"hset",hsetCommand,-4,
"write use-memory fast @hash",
0,NULL,1,1,1,0,0,0},
{"hmset",hsetCommand,-4,
"write use-memory fast @hash",
0,NULL,1,1,1,0,0,0},
- 查询或创建 key 的 robj
- 如果需要的话转化 robj.encoding
- 根据 hset 的参数个数循环
hashTypeSet -
hashTypeSet的逻辑同hsetnx:如果dictFind到就修改dictEntry.v,如果找不到就dictAdd -
hset的返回值是created,hmset的返回值是shared.ok即字符串OK
4. hincrby key field increment
功能:为 HashTable
key下的field的 value 增加increment。如果key下的field的 value 不是整数类型,会报错:(error) ERR hash value is not an integer
long long value, incr, oldvalue;
robj *o;
sds new;
unsigned char *vstr;
unsigned int vlen;
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) {
if (vstr) {
if (string2ll((char*)vstr,vlen,&value) == 0) {
addReplyError(c,"hash value is not an integer");
return;
}
}
} else {
value = 0;
}
oldvalue = value;
if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
addReplyError(c,"increment or decrement would overflow");
return;
}
value += incr;
new = sdsfromlonglong(value);
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
hincrby 的逻辑是:
-
getLongLongFromObjectOrReply获取指定参数的long long(整数型)值。 - 查询或创建 key 的 robj
- 获取 HashTable
key下的field值,如果不能转化成long long,报错:hash value is not an integer - 对
increment后的值做上下限判断,防止溢出 - 下限是
-9223372036854775808上限是9223372036854775807 - 将
increment后的值转化成long long类型,然后hashTypeSet - 返回
increment后的值







网友评论