文章目录
字典是Redis服务器中出现最为频繁的复合型数据结构,除了hash结构的数据会用到字典外,整个redis数据库的所有key和value也组成一个全局字典,还有带有过期时间的key集合也是一个字典。zet集合中存储value和score值的映射关系也是通过字典结构实现的。
先看一下redisDB的结构,它里面存储着数据字典和过期字典
Struct
redisDB
[redis-7.2.2\src\server.h]
redisDB
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 *blocking_keys_unblock_on_nokey; /* Keys with clients waiting for
* data, and should be unblocked if key is deleted (XREADEDGROUP).
* This is a subset of blocking_keys*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
//当前的数据库ID
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;
dict
struct dict {
//dictType是一个struct里面存储了各种与hashtable相关的function pointer,详细的见下面
dictType *type;
/*
正常情况下,使用ht_table[0]存储数据;
发生扩容或者缩容时,用ht_table[1]。
*/
dictEntry **ht_table[2];
/*
对应着两个ht_table的元素个数;
ht_used[0]表示ht_table[0]的对象总数;
ht_used[1]表示ht_table[1]的对象总数.
*/
unsigned long ht_used[2];
/*
标记当前的hashtable是否处于rehash状态,如果rehashidx==-1则当前没有处于rehash状态,如果rehashidx>=0则表明当前处于rehash状态并且按照rehashidx指示的顺序进行迁移。
*/
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
/*
该值大于0表示rehash终止,小于0表示编码错误
*/
int16_t pauserehash;
/*
通过ht_size_exp可以计算出两个表的一维长度也即是槽的个数。
pow(2,ht_size_exp[0])表示ht_table[0]槽的个数;
pow(2,ht_size_exp[1])表示ht_table[1]槽的个数;
*/
signed char ht_size_exp[2];
/*
metadata是用于存储额外信息的字段,可以存储一些元数据
*/
void *metadata[];
};
dictType
typedef struct dictType {
uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(dict *d, const void *key);
void *(*valDup)(dict *d, const void *obj);
int (*keyCompare)(dict *d, const void *key1, const void *key2);
void (*keyDestructor)(dict *d, void *key);
void (*valDestructor)(dict *d, void *obj);
int (*expandAllowed)(size_t moreMem, double usedRatio);
/*
如果设置了'no_value'标志,则表示没有使用值,即字典是一
个集合。设置此标志时,无法访问dictEntry的值,也无法使用
dicsetkey()。metadata也不能使用。
*/
unsigned int no_value:1;
/*
如果no_value =1并且所有的键都是奇数(LSB=1),那么设置
keys_are_odd =1可以实现另一个优化:在不分配dictEntry
的情况下存储键。
*/
unsigned int keys_are_odd:1;
size_t (*dictEntryMetadataBytes)(dict *d);
size_t (*dictMetadataBytes)(void);
void (*afterReplaceEntry)(dict *d, dictEntry *entry);
} dictType;
- set是一个集合,它的底层实现也是dict,但是对于集合来说只有key值没有value值;所以为了兼容set的实现,dictType中有一个标志性的字段"no_value",只要设置了该字段就表明这个dictEntry中只有key值没有value值,也就实现了set。
dictEntry
//dict中的entry
struct dictEntry {
void *key;//指向key
//union是指内存的同一个位置可以存储不同的数据类型,是为了兼容不同类型的value。
//当value是uint64_t、int64_t、double的数据类型的时候,
//可以直接内嵌在dictentry中,无需为此分配额外的内存,这样可以节省内存
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; /* Next entry in the same hash bucket. 采用拉链法解决哈希冲突*/
void *metadata[]; //存储额外的信息
/*
一个任意数量的字节(从指针对齐的地址开始),大小由dictType
的dictEntryMetadataBytes()返回。
*/
};
当no_value=1时对应的结构是
//只有key没有value相当于set
typedef struct {
void *key;
dictEntry *next;
} dictEntryNoValue;
宏定义
[redis-7.2.2\src\dict.h]
//根据ht_size_exp计算dictht_size
#define DICTHT_SIZE(exp) ((exp) == -1 ? 0 : (unsigned long)1<<(exp))
//获取sizemask
#define DICTHT_SIZE_MASK(exp) ((exp) == -1 ? 0 : (DICTHT_SIZE(exp))-1)
//每一个hashtable的初始化大小
#define DICT_HT_INITIAL_EXP 2
#define DICT_HT_INITIAL_SIZE (1<<(DICT_HT_INITIAL_EXP))
#define dictFreeVal(d, entry) do {
\
if ((d)->type->valDestructor) \
(d)->type->valDestructor((d), dictGetVal(entry)); \
} while(0)
#define dictFreeKey(d, entry) \
if ((d)->type->keyDestructor) \
(d)->type->keyDestructor((d), dictGetKey(entry))
#define dictCompareKeys(d, key1, key2) \
(((d)->type->keyCompare) ? \
(d)->type->keyCompare((d), key1, key2) : \
(key1) == (key2))
#define dictEntryMetadataSize(d) ((d)->type->dictEntryMetadataBytes \
? (d)->type->dictEntryMetadataBytes(d) : 0)
#define dictMetadataSize(d) ((d)->type->dictMetadataBytes \
? (d)->type->dictMetadataBytes() : 0)
//获取key的哈希值
#define dictHashKey(d, key) ((d)->type->hashFunction(key))
//获取两个dictht的总slot个数
#define dictSlots(d) (DICTHT_SIZE((d)->ht_size_exp[0])+DICTHT_SIZE((d)->ht_size_exp[1]))
//获取两个dictht的总对象个数
#define dictSize(d) ((d)->ht_used[0]+(d)->ht_used[1])
//判断当前是否处在rehash阶段
#define dictIsRehashing(d) ((d)->rehashidx != -1)
//终止rehash
#define dictPauseRehashing(d) ((d)->pauserehash++)
//重新开始rehash
#define dictResumeRehashing(d) ((d)->pauserehash--)
/* If our unsigned long type can store a 64 bit number, use a 64 bit PRNG. */
#if ULONG_MAX >= 0xffffffffffffffff
#define randomULong() ((unsigned long) genrand64_int64())
#else
#define randomULong() random()
#endif
散列函数
- redis的字典默认的哈希函数是siphash,siphash算法即使在key很小的情况下,也可以产生随机性特别好的输出,性能非常突出。
散列冲突
- 通过dictEntry的结构中包含"struct entry *next 指向同一个slot的下一个next entry"可以得出,dict采用"拉链法"解决"散列冲突"。
dictEntry pointer bit tricks[指针位技巧]
/*
指向dictEntry的指针中的最低3位决定了该指针实际指向的是什么。如果最小的位被设置,它就是一个键值。否则,最少的3位有效位标记条目的类型。
*/
#define ENTRY_PTR_MASK 7 /* 111 与Mask相与获取数据的低三bit位*/
#define ENTRY_PTR_NORMAL 0 /* 000 已分配的entry并且带有value值*/
#define ENTRY_PTR_NO_VALUE 2 /* 010 已分配的entry但是不带有value值*/
/*
返回1:entry pointer指向一个key值,而不是已经分配好的entry。
其它情况返回0.
*/
static inline int entryIsKey(const dictEntry *de) {
//最低位设置了就是key
return (uintptr_t)(void *)de &