字典数据结构实现
redis DBKV 是通过字典来实现的;hash结构,当节点数量大于512个,或单个字符串长度大于64,hash结构采用字典实现;
相关数据结构如下
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
/* 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;//size - 1
unsigned long used; //当前数组中包含的元素
} dictht;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;
1.字符串经过hash运算得到64位整数;
2.相同字符串多次通过hash函数得到相同的64位整数;
3.整数对2的n次幂取余可以转化位运算
4.抽屉原理,n+1个苹果放到n个抽屉里,至少有一个抽屉的苹果个数大于等于2;64位整数远大于数组的长度,比如数组的长度位4,那么1 ,5,9,1+4n都映射到1号位数组,所以大概率会发生冲突
冲突
负载因子=used/size;used是数组中存储的元素的个数,size为数组大小,负载因子越大,发生冲突的可能性就越大,负载因子越小,发生冲突的可能性就越小。
redis的负载因子为1
扩容,当负载因子大于1后,就会发生扩容,扩容的规则是翻倍
但是,如果redis正在fork(在rdb,aof复写以及rdb-aof混用的情况)时,会组织扩容,但若此时负载因子大于5,索引效率极具下降,则立即扩容。
扩容是,相应插槽位置的数据会扩容到本来以及与其对应的扩容位置上,如下图
redis扩容时采取写时复制的原则,写时复制的核心思想是,只有在不得不复制的时候复制数据内容,示意图如下
缩容
如果负载因子小于0.1,则会发生缩容;缩容的规则是恰好包含used的2的n次方;
恰好的理解为:如果此时数组存储的元素个数为9,恰好包含这些元素的2的次方为2的4次方,也就是16个位置。缩容的规则如下图示意
渐进式rehash
当hashtable中元素过多的时候,不能一次性rehash到ht[1],这样会长期占用redis,其他命令得不到响应,所以需要使用渐进式rehash
rehash步骤
将ht[0]中的元素重新经过hash函数生成64位整数,再对ht[1]取余,从而映射到ht[1]
渐进式规则:
1.分治思想,将rehash分到之后的每步增删改查的操作之中;
2.在定时器中,最大执行一毫秒;每次步长100个槽位
注:处于渐进式rehash中会不会扩容,答案是不会的
大KEY
在redis实例中形成了很大的对象,比如一个很大的hash或很大的zset,这样的对象在扩容时,会一次性申请很大的内存,这会导致卡顿;如果这样的大key被删除,内存会一次性回收,卡顿现象会再次产生
如果观察到redis的内存大起大落,极有可能是大KEY导致的
该命令可以查询大key
跳表实现
理想跳表
redis跳表
从节约内存的角度出发,redis考虑牺牲到一点时间复杂度让跳表结构变的更加扁平,就像二叉堆改成四叉堆那样,并且redis限制跳表的最高层级位32
在redis中,节点数量大于128或字符串长度大于64,采用跳表结构(skiplist)
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;