redis7.2.2|Dict


字典是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 & 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值