【Redis学习笔记】链表和字典实现

链表

1.链表提供了高效的节点重排能力,已经顺序性的节点访问方式,还有灵活地增删能力
2.结构

//链表节点
struct listNode{
     
	listNode* prev;
	listNode* next;
	void* value;
}
//链表
struct list{
     
	listNode* head;
	listNode* tail;
	long len;
}

如图所示:
【Redis学习笔记】链表和字典实现_第1张图片

字典

字典在Redis应用非常广泛,可以说整个Redis都是基于字典来实现的。例如Redis的数据库就是使用字典作为底层实现,对数据库的增删改查都是转换为对字典的操作;其次字典还是哈希表的实现之一。其实现由哈希表、哈希节点、字典实现:

//哈希表
struct dictht{
     
	dictEntry **table;//哈希表数组
	long size;//表大小
	long sizeMask;//哈希表大小掩码,等于size-1,用于计算索引
	long used;//已有节点数量
}
//哈希节点
struct dictEntry{
     
	void *key;//键
	void *value;//值
	dictEntry* next;//下个哈希节点
}
//字典
struct dict{
     
	dictType* type;
	dictht ht[2];
	int rehashidx;//rehash索引,为-1时rehash停止
}

其中,字典结构中包含了两个哈希表,一般情况下只会用到ht[0]表,而在rehash过程中还会使用ht[1]表,而reashidx用于标记rehash的进度。

哈希算法

Redis采用MurmurHash2算法来计算键的哈希值,同时使用按位与和sizeMask计算键所在的位置,其伪代码如下:

	hash = MurmurHash2(key);
	index = hash & ht.sizeMask;

同时Redis采用链地址法来解决键的冲突,如果存在keyA和keyB计算处理来的index一样,则将这两个键存在同个表位置,用一个next指针连接起来形成链表。

rehash

随着操作的进行,哈希表保存的数据会逐渐增加或者减少,这个时候原先的哈希表的负载就没有维持在一个合理的范围,就需要系统对哈希表进行调整。
1.为字典的ht[1]进行空间分配,该表的大小计算方式如下:
1.1 进行扩展操作,则ht[1]的大小为大于等于ht[0].used*2的第一个2^n
1.2 进行缩小操作,则ht[1]的大小为大于等于ht[0].used/2的第一个2^n
2.将保存在ht[0]的键值对重新hash到ht[1]上面
3.迁移完ht[0]的数据后,清理ht[1]

发起时机

1 服务器没有执行BGSAVE 或者BGREWRITEAOF,并且哈希表的负载因子大于等于1
2.服务器没有执行BGSAVE 或者BGREWRITEAOF,并且哈希表的负载因子大于等于5
3.服务器没有执行BGSAVE 或者BGREWRITEAOF,并且哈希表的负载因子小于0.1
负载因子=used/size

渐进式rehash

由于Redis是单进程程序,所以任何一个耗时长的指令都会阻塞后序指令的操作。所以考虑到减少哈希表重新映射减少对系统的阻塞,Redis采用了渐进式rehash的方法来将ht[0]的键值对迁移到ht[1]
操作步骤
1.分配ht[1]空间
2.初始化rehashidx,设为0,表示rehashidx开始
3.每次进行字典操作的时候,都会将rehashidx位置的键值对迁移到ht[1],完成后rehashidx数值加1
4.随着操作的进行,最后将ht[0]中的所有数据都rehash到ht[1]表中,释放ht[0]表,将rehashidx置为-1
注意:在hash过程中,字典会持有两个哈希表,这就表示每次操作都需要检查两个表,如:
1.每次查找、更新、删除,都先在ht[0]中操作,找不到则在ht[1]中操作
2.每次新增值都会直接保存到ht[1]表中,避免重复操作

你可能感兴趣的:(Redis,REDIS)