首页 > 数据库 >[Redis]字典详解

[Redis]字典详解

时间:2024-07-18 19:29:45浏览次数:11  
标签:hash Redis 详解 dict key entry 字典

字典是 Redis 服务器中出现最为频繁的复合型数据结构,除了 hash 结构的数据会用到字典(dict)外,整个Redis 数据库的所有 key 和 value 也组成了一个全局字典还有带过期时间的 key 集合也是一个字典。zset 集合中存储 value 和 score 值的映射关系也是通过字典结构实现的。

struct RedisDb{
	dict* dict;//all keys key=>value
	dict* expires; //all expired keys key=>long(timestamp)
}

struct zset{
	dict *dict;//all valuesvalue=>score
	zskiplist *zsl;
}

字典内部结构

如图 5-4 所示,字典结构内部包含两个 hashtable,通常情况下只有一个 hashtable是有值的,但是在字典扩容缩容时,需要分配新的 hashtable,然后进行渐进式搬迁,这时候两个 hashtable 存储的分别是旧的 hashtable 和新的 hashtable。待搬迁结束后,旧的 hashtable 被删除,新的 hashtable 取而代之。

image

struct dict{
	dictht ht[2]
}

所以,字典数据结构的精华就落在了hashtable 结构上了。hashtable 的结构和Java 的 HashMap 几乎是一样的,都是通过分桶的方式解决 hash 冲突。第一维是数组,第二维是链表,如图 5-5 所示。数组中存储的是第二维链表的第一个元素的指针。

image

struct dictEntry{
	void* key;
	void* val;
	dictEntry* next; //链接下一个 entry
}
struct dictht{
	dictEntry** table; // 二维
	long size; //第一维数组的长度
	long used; // hash 表中的元素个数
}

渐进式 rehash

大字典的扩容是比较耗时间的,需要重新申请新的数组,然后将旧字典所有链表中的元素重新挂接到新的数组下面,这是一个0(n)级别的操作,作为单线程的Redis 很难承受这样耗时的过程,所以 Redis 使用渐进式 rehash 小步搬迁。虽然慢一点,但是肯定可以搬完。


dictEntry *dictAddRaw(dict *d,void *key,dictEntry **existing)
{
	long index;
	dictEntry *entry;
	dictht *ht;
	//这里进行小步搬迁
	if (dictIsRehashing(d)) _dictRehashStep(d);

}

/*Get the index of the new element,or -1 if the element already exists.*/
if ((index= _dictKeyIndex(d,key,dictHashKey(d,key)existing))==-1)
return NULL;
/*Allocate the memory and store the new entry.*Insert the element in top,with the assumption that in a
database大system it is more likely that recently added entries areaccessed
火more frequently.*/
//如果字典处于搬迁过程中,要将新的元素挂接到新的数组下面ht = dictIsRehashing(d)?&d->ht[1]:&d->ht[0];entry=malloc(sizeof(*entry));entry->next=ht->table[index];ht->table[indexl= entry;ht->used++;
/*Set the hash entry fields.*/dictSetKey(d,entry,key);
return entry;

搬迁操作埋伏在当前字典的后续指令中(来自客户端的 hset、hdel 等指令)但是有可能客户端闲下来了,却没有后续指令来触发这个搬迁,那么这时 Redis 就置之不理了吗?当然不会,优雅的 Redis 怎么可能设计得这么粗糙。Redis 还会在定时任务中对字典进行主动搬迁。


//服务器定时任务
void databaseCron(){
if(server.activerehashing)3
for(j=0;j<dbs per call;j++){int work done =incrementallyRehash(rehash db);if (work done){/*If the function did some work,stop here
ll dowe’
*more at the nextcron loop.*/break;
}else{
/* If this db didn’t need rehash,we will try the next one.*/
rehash db++;
rehash db =server.dbnum;
}

查找过程

插入和删除操作都依赖于查找,必须先把元素找到,才可以进行数据结构的修改操作。hashtable 的元素是在第二维的链表上,所以我们首先要想办法定位出元素在哪个链表上。

func get(key){
	let index = hash_func(key) % size;
	let entry = table[index];
	while(entry != NULL){
		if entry.key == target{
			return entry.value;
		}
		entry = entry.next;
	}
}

值得注意的是代码中的 hash_func,它会将 key 映射为一个整数,不同的 key 会被映射成分布比较均匀散乱的整数。只有 hash 值均匀了,整个 hashtable 才是平衡的,所有的二维链表的长度就不会差距很远,查找算法的性能也就比较稳定。

hash 函数

hashtable 的性能好不好完全取决于 hash 函数的质量。如果 hash 函数可以将 key打散得比较均匀,那么这个 hash 函数就是个好函数。Redis 的字典默认的 hash 函数是 siphash。siphash 算法即使在输入 key 很小的情况下,也可以产生随机性特别好的输出,而且它的性能也非常突出。对于 Redis 这样的单线程来说,字典数据结构非常普遍,字典操作也会非常频繁,hash 函数自然是越快越好。

hash 攻击

如果 hash 函数存在偏向性,黑客就可能利用这种偏向性对服务器进行攻击。存在偏向性的 hash 函数在特定模式下的输入会导致 hash 第二维链表长度极为不均匀,甚至所有的元素都集中到个别链表中,直接导致查找效率急剧下降,从 0(1)退化到 O(n)有限的服务器计算能力将会被 hashtable 的查找效率彻底拖垮。这就是所谓的 hash 攻击。

扩容条件

/*Expand the hash table if needed */static int dictExpandIfNeeded(dict*d)
/*Incremental rehashing already in progress. Return.*/if(dictIsRehashing(d))return DICTOK;
/* If the hash table is empty expand it to the initial size.
大
if(d->ht[0].size ==0)return dictExpand(d, DICT HT INITIAL
SIZE);
/*If we reached the l:l ratio, and we are allowed to resizethe hash
table(global setting)or we should avoid it but the ratio大between
*elements/buckets is over the“safe”threshold,we resizedoubling
*the number of buckets.*

if(d->ht[0].used >= d->ht[0].size &&(dict can resized->ht[0l.used/d->ht[0].size >dict force resize ratio))
return dictExpand(d,d->ht[0].used*2);
return DICT OK;

正常情况下,当 hash 表中元素的个数等于第一维数组的长度时,就会开始扩容扩容的新数组是原数组大小的2倍。不过如果 Redis 正在做 bgsave,为了减少内存页的过多分离(Copy On Write),Redis 尽量不去扩容(dict can resize),但是如果hash 表已经非常满了,元素的个数已经达到了第一维数组长度的5倍(dict forceresize ratio),说明 hash 表已经过于拥挤了,这个时候就会强制扩容。

缩容条件

int htNeedsResize(dict*dict){
	long long size,used;
	size = dictSlots(dict);
	used = dictSize(dict);
	return (size>DICT_HT_INITIALSIZE && (used*100/size < HASHTABLEMIN));
}

当 hash 表因为元素逐渐被删除变得越来越稀疏时,Redis 会对 hash 表进行缩容来减少 hash 表的第一维数组空间占用。缩容的条件是元素个数低于数组长度的10%。缩容不会考虑 Redis 是否正在做 bgsave。

set 的结构

Redis 里面 set 的结构底层实现也是字典,只不过所有的 value 都是 NULL,其他特性和字典一模一样。

标签:hash,Redis,详解,dict,key,entry,字典
From: https://www.cnblogs.com/DCFV/p/18310295

相关文章

  • [php命令执行函数]详解各种php命令执行函数
    如下几种命令执行函数:目录systemexcpassthrushell_exec反引号``popensystemsystem函数简介:用于执行命令语法形式:system(string$command,int$return_var=?)command:必选参数,字符类型,被system函数执行的命令,如lsreturn_var:可选参数,整数类型,如果提供此参数,则com......
  • Java版本jdk8的特性Lambda表达式详解
    面向对象编程思想和函数式编程思想的区别面向对象编程:重点是对象,强调的是对象的状态和行为。面向对象编程使用类和实例来封装数据和行为,这可以让代码更加模块化和易于维护。函数式编程:重点是函数,强调的是函数的输入和输出,而不是对象的状态。函数式编程通常使用纯函数,即没......
  • [Redis]双写一致性
    经典缓存模式旁路缓存读数据首先读缓存,如果缓存中有这个数据,直接返回如果缓存中没有这个数据,去读数据库,如果数据库中有这个数据,先把这个数据更新到缓存,再返回数据如果数据库也没有这个数据,返回无数据写数据先更新数据库,再删除缓存读写穿透缓存这个模式在用户......
  • 【C语言】结构体,枚举,联合超详解!!!
    目录结构体结构体声明结构体成员的访问结构体自引用 结构体变量定义,初始化,传参 结构体内存对齐 位段枚举联合(共用体)结构体结构体声明1.概念1.结构体是一些值的集合,这些值称为成员变量。2.结构体的每个成员可以是不同类型的变量。3.数组:一组相同类型......
  • RabbitMQ-最常用的消息队列MQ安装详解!!
    RabbitMQ-最常用的消息队列MQ安装详解!!RabbitMQ-简介RabbitMQ是采用Erlang语言实现的高级消息队列协议(AMQP)的消息中间件。它最初起源于金融系统,用于在分布式系统中存储和转发消息。在RabbitMQ中,消息传递的过程可以想象成厨师做好饭菜放到服务台,服务台会暂存并最终将......
  • redis
    1.redis的数据类型简介:Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sortedsets)与范围查询,bitmaps,hyperloglogs和地理空间(geospatial)索引半径......
  • 字典树模板
    把数据都存在一个TrieNode数组里,只保存其指向关系。classTrieNode{public:boolend;vector<int>son;TrieNode(){end=false;son=vector<int>(26,-1);}};classTrie{public:vector<TrieNode>tree;Trie(){......
  • 小白C语言基础详解: 运算符
    运算符C语言的运算符非常多,一共有50多种,可以分成若干类。算术运算符算术运算符专门用于算术运算,主要有下面几种。+:正值运算符(一元运算符)-:负值运算符(一元运算符)+:加法运算符(二元运算符)-:减法运算符(二元运算符)*:乘法运算符/:除法运算符%:余值运算符(1)+,-+和-既可以作为一元......
  • 【C语言】逻辑操作符详解 - 《真假美猴王 ! 》
    目录C语言逻辑操作符详解1.逻辑与(`&&`)示例输出2.逻辑或(`||`)示例输出3.逻辑非(`!`)示例输出4.逻辑操作符应用实例示例1:条件判断输出示例2:多条件判断输出5.逻辑操作符的短路特性示例输出6.逻辑操作符的优先级示例输出7.参考文献8.结束语C语言逻......
  • Symfony框架详解:构建高效、可维护的Web应用
    引言Symfony是一个由SensioLabs开发并维护的PHP框架,遵循MVC(Model-View-Controller)设计模式。它不仅提供了一系列强大的工具和功能,还能通过其组件(如HttpFoundation、Routing、DependencyInjection等)单独使用。Symfony的设计目标是让开发者能够高效地构建高质量的Web应用程序,同......