今天看了redis dict部分的源码(dict.h,dict.c),跟大家分享一下。
这两个文件包含了redis的hashtable的接口和实现。
Redis中hashtable的主要特点是增量rehash,rehash毕竟是一个耗时的操作,redis把它分摊到hashtable的各个小操作中,从而让字典操作性能曲线比较平滑。
既然要增量rehash,就要在一个dict中保留两个hashtable,分批次从hashtable[0]搬运元素到hashtable[1]中。
dict的iterator,iterator分为安全的iterator和不安全的iterator,安全的iterator锁定dict,不让它进行rehash及新增元素,直到它释放为止(dict中对安全iterator进行计数)。(这个做法颇有启发)
redis中hashtable的数组总是2的幂,按照数据结构的理论,理想的应该是素数,作者可能是为了方便或是hash计算时更快速。
dict 中提供了两个hash函数,unsigned int dictGenHashFunction(const void *key, int len);这是大名鼎鼎的MurmurHash2,unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);这是字符串中常用的hash算法(djb hash),只是不区分大小写,从seed开始,hash*33+char。当然也有获得和设置hash seed的接口。
主要的结构如下:
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next;
} dictEntry;
有一些简单接口用宏包装,rehash触发的条件是used / size > 5。
一般的操作都会做一步rehash即把一个bucket从ht[0]放到ht[1]中,但是replace接口会做两次,因为会先调用find,再调用Add。
有专门的rehash接口,指定rehash几步,还可以指定花多少时间用于rehash,它实际上是每rehash100步检查下时间知道时间超出。