跋山涉水——深入 Redis 字典遍历

Signoff Criteria --- ocv/aocv/pocv之AOCV介绍 Advanced on chip variation,相比较于OCV来说,对于悲观度的去除很有效。相比较与ocv对于整个design中的lauch/capture path设置统一的derate值,aocv通过将cell derate值与cell的逻辑深度以及实际的位置结合起来进行derate值的设定。那么这种derate如何能够设置到具体的cell呢,其实是我们在跑STA时候会读入相关的aocv lib file,里面有专门对相关的cell进行的aocv derate值的描述。> table: \ 阅读详情

Redis 字典的遍历过程逻辑比较复杂,互联网上对这一块的分析讲解非常少。我也花了不少时间对源码的细节进行了整理,将我个人对字典遍历逻辑的理解呈现给各位读者。也许读者们对字典的遍历过程有比我更好的理解,还请不吝指教。

13467292-b39c02d9750fd26e

一边遍历一边修改

我们知道 Redis 对象树的主干是一个字典,如果对象很多,这个主干字典也会很大。当我们使用 keys 命令搜寻指定模式的 key 时,它会遍历整个主干字典。值得注意的是,在遍历的过程中,如果满足模式匹配条件的 key 被找到了,还需要判断 key 指向的对象是否已经过期。如果过期了就需要从主干字典中将该 key 删除。

void keysCommand(client *c) {

dictIterator *di; // 迭代器

dictEntry *de; // 迭代器当前的entry

sds pattern = c->argv[1]->ptr; // keys的匹配模式参数

int plen = sdslen(pattern);

int allkeys; // 是否要获取所有key,用于keys *这样的指令

unsigned long numkeys = 0;

void *replylen = addDeferredMultiBulkLength(c);

// why safe?

di = dictGetSafeIterator(c->db->dict);

allkeys = (pattern[0] == '*' && pattern[1] == '');

while((de = dictNext(di)) != NULL) {

sds key = dictGetKey(de);

robj *keyobj;

if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {

keyobj = createStringObject(key,sdslen(key));

// 判断是否过期,过期了要删除元素

if (expireIfNeeded(c->db,keyobj) == 0) {

addReplyBulk(c,keyobj);

numkeys++;

}

decrRefCount(keyobj);

}

}

dictReleaseIterator(di);

setDeferredMultiBulkLength(c,replylen,numkeys);

}

复制代码

那么,你是否想到了其中的困难之处,在遍历字典的时候还需要修改字典,会不会出现指针安全问题?

重复遍历

字典在扩容的时候要进行渐进式迁移,会存在新旧两个 hashtable。遍历需要对这两个 hashtable 依次进行,先遍历完旧的 hashtable,再继续遍历新的 hashtable。如果在遍历的过程中进行了 rehashStep,将已经遍历过的旧的 hashtable 的元素迁移到了新的 hashtable中,那么遍历会不会出现元素的重复?这也是遍历需要考虑的疑难之处,下面我们来看看 Redis 是如何解决这个问题的。

迭代器的结构

Redis 为字典的遍历提供了 2 种迭代器,一种是安全迭代器,另一种是不安全迭代器。

typedef struct dictIterator {

dict *d; // 目标字典对象

long index; // 当前遍历的槽位置,初始化为-1

int table; // ht[0] or ht[1]

int safe; // 这个属性非常关键,它表示迭代器是否安全

dictEntry *entry; // 迭代器当前指向的对象

dictEntry *nextEntry; // 迭代器下一个指向的对象

long long fingerprint; // 迭代器指纹,放置迭代过程中字典被修改

} dictIterator;

// 获取非安全迭代器,只读迭代器,允许rehashStep

dictIterator *dictGetIterator(dict *d)

{

dictIterator *iter = zmalloc(sizeof(*iter));

iter->d = d;

iter->table = 0;

iter->index = -1;

iter->safe = 0;

iter->entry = NULL;

iter->nextEntry = NULL;

return iter;

}

// 获取安全迭代器,允许触发过期处理,禁止rehashStep

dictIterator *dictGetSafeIterator(dict *d) {

dictIterator *i = dictGetIterator(d);

i->safe = 1;

return i;

}

迭代器的「安全」指的是在遍历过程中可以对字典进行查找和修改,不用感到担心,因为查找和修改会触发过期判断,会删除内部元素。「安全」的另一层意思是迭代过程中不会出现元素重复,为了保证不重复,就会禁止 rehashStep。

而「不安全」的迭代器是指遍历过程中字典是只读的,你不可以修改,你只能调用 dictNext 对字典进行持续遍历,不得调用任何可能触发过期判断的函数。不过好处是不影响 rehash,代价就是遍历的元素可能会出现重复。

安全迭代器在刚开始遍历时,会给字典打上一个标记,有了这个标记,rehashStep 就不会执行,遍历时元素就不会出现重复。

typedef struct dict {

dictType *type;

void *privdata;

dictht ht[2];

long rehashidx;

// 这个就是标记,它表示当前加在字典上的安全迭代器的数量

unsigned long iterators;

} dict;

// 如果存在安全的迭代器,就禁止rehash

static void _dictRehashStep(dict *d) {

if (d->iterators == 0) dictRehash(d,1);

}

迭代过程

安全的迭代器在遍历过程中允许删除元素,意味着字典第一维数组下面挂接的链表中的元素可能会被摘走,元素的 next 指针就会发生变动,这是否会影响迭代过程呢?下面我们仔细研究一下迭代函数的代码逻辑。

dictEntry *dictNext(dictIterator *iter)

{

while (1) {

if (iter->entry == NULL) {

// 遍历一个新槽位下面的链表,数组的index往前移动了

dictht *ht = &iter->d->ht[iter->table];

if (iter->index == -1 && iter->table == 0) {

// 第一次遍历,刚刚进入遍历过程

// 也就是ht[0]数组的第一个元素下面的链表

if (iter->safe) {

// 给字典打安全标记,禁止字典进行rehash

iter->d->iterators++;

} else {

// 记录迭代器指纹,就好比字典的md5值

// 如果遍历过程中字典有任何变动,指纹就会改变

iter->fingerprint = dictFingerprint(iter->d);

}

}

iter->index++; // index=0,正式进入第一个槽位

if (iter->index >= (long) ht->size) {

// 最后一个槽位都遍历完了

if (dictIsRehashing(iter->d) && iter->table == 0) {

// 如果处于rehash中,那就继续遍历第二个 hashtable

iter->table++;

iter->index = 0;

ht = &iter->d->ht[1];

} else {

// 结束遍历

break;

}

}

// 将当前遍历的元素记录到迭代器中

iter->entry = ht->table[iter->index];

} else {

// 直接将下一个元素记录为本次迭代的元素

iter->entry = iter->nextEntry;

}

if (iter->entry) {

// 将下一个元素也记录到迭代器中,这点非常关键

// 防止安全迭代过程中当前元素被过期删除后,找不到下一个需要遍历的元素

// 试想如果后面发生了rehash,当前遍历的链表被打散了,会发生什么

// 这里要使劲发挥自己的想象力来理解

// 旧的链表将一分为二,打散后重新挂接到新数组的两个槽位下

// 结果就是会导致当前链表上的元素会重复遍历

// 如果rehash的链表是index前面的链表,那么这部分链表也会被重复遍历

iter->nextEntry = iter->entry->next;

return iter->entry;

}

}

return NULL;

}

// 遍历完成后要释放迭代器,安全迭代器需要去掉字典的禁止rehash的标记

// 非安全迭代器还需要检查指纹,如果有变动,服务器就会奔溃(failfast)

void dictReleaseIterator(dictIterator *iter)

{

if (!(iter->index == -1 && iter->table == 0)) {

if (iter->safe)

iter->d->iterators--; // 去掉禁止rehash的标记

else

assert(iter->fingerprint == dictFingerprint(iter->d));

}

zfree(iter);

}

// 计算字典的指纹,就是将字典的关键字段进行按位糅合到一起

// 这样只要有任意的结构变动,指纹都会发生变化

// 如果只是某个元素的value被修改了,指纹不会发生变动

long long dictFingerprint(dict *d) {

long long integers[6], hash = 0;

int j;

integers[0] = (long) d->ht[0].table;

integers[1] = d->ht[0].size;

integers[2] = d->ht[0].used;

integers[3] = (long) d->ht[1].table;

integers[4] = d->ht[1].size;

integers[5] = d->ht[1].used;

for (j = 0; j < 6; j++) {

hash += integers[j];

hash = (~hash) + (hash << 21);

hash = hash ^ (hash >> 24);

hash = (hash + (hash << 3)) + (hash << 8);

hash = hash ^ (hash >> 14);

hash = (hash + (hash << 2)) + (hash << 4);

hash = hash ^ (hash >> 28);

hash = hash + (hash << 31);

}

return hash;

}

值得注意的是在字典扩容时进行rehash,将旧数组中的链表迁移到新的数组中。某个具体槽位下的链表只可能会迁移到新数组的两个槽位中。

hash mod 2^n = k

hash mod 2^(n+1) = k or k+2^n

复制代码

迭代器的选择

除了keys指令使用了安全迭代器,因为结果不允许重复。那还有其它的地方使用了安全迭代器么,什么情况下遍历适合使用非安全迭代器呢?

简单一点说,那就是如果遍历过程中不允许出现重复,那就使用SafeIterator,比如下面的两种情况

bgaofrewrite需要遍历所有对象转换称操作指令进行持久化,绝对不允许出现重复

bgsave也需要遍历所有对象来持久化,同样不允许出现重复

如果遍历过程中需要处理元素过期,需要对字典进行修改,那也必须使用SafeIterator,因为非安全的迭代器是只读的。

其它情况下,也就是允许遍历过程中出现个别元素重复,不需要对字典进行结构性

Java高架构师、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频免费学习加群:835638062 点击链接加入群聊【Java高级架构】:https://jq.qq.com/?_wv=1027&k=5S3kL3v

HTB_Bike靶机之模板注入 船到桥头自然直 文章目录信息收集模板引擎SSIT 模板注入后放答案 信息收集 日常 A 扫描,两个端口 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA) | 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:. 阅读详情

相关推荐

pix4d安装步骤

pix4d三维模型软件的安装步骤

Little_Bee_27000的博客 1万+

redis学习笔记--1

因此,这个命令的含义是:尝试获取名为 lock:codehole 的分布式锁,在获取锁的过程中,如果这个锁已经被其他客户端获取,则当前客户端会等待,直到获取锁为止。ex=5 表示锁的过期时间为 5秒,其中5 是一个数字,表示锁的有效期。tag = random. next int() #随机数 if redis.set(key, tag, nx=True, ex=5}: do_ something () redis.delifequals(key, tag) #假想的 delifequals 指令。

didikai的博客 243

树莓派5安装ROS2小白指南避坑攻略

手把手教你如何在树莓派5上顺利安装ROS2,避开常见问题与陷阱,提升配置效率。针对树莓派5安装ros2过程中的依赖冲突、源配置错误等高频难题提供解决方案,适合新手快速上手。

weixin_35754962的博客 1005

跋山涉水 —— 深入 Redis 字典遍历

Redis 字典的遍历过程逻辑比较复杂,互联网上对这一块的分析讲解非常少。我也花了不少时间对源码的细节进行了整理,将我个人对字典遍历逻辑的理解呈现给各位读者。也许读者们对字典的遍历过程有比我更好的理解,还请不吝指教。

javagty6778的博客 212

frp内网穿透(socks隧道)

在实验过程中还是遇到了挺多的问题,但好在大多都解决了。不过还是遗留了一些不懂的问题,等以后再慢慢研究了。最后,以上仅为个人的拙见,如何有不对的地方,欢迎各位师傅指正与补充,有兴趣的师傅可以一起交流学习。

2301_76227305的博客 1万+

Nginx源码安装和基础配置

Nginx简介 nginx: 世界第二大web服务器软件,以高并发、低消耗著称,源自于俄罗斯,创建者 Igor(伊戈尔),2004年开源,最早以代理服务器的身份出现,2015成立nginx公司,2019年以6.7亿被F5 Networks公司收购。 代理服务器: 正向:squid 反向: squid:最古老的代理服务器 LVS: 优点: 1.抗负载能力强、是工作在网络4层之上仅作分发之用 2.配置性比较低,这是一个缺点也是一个优点,简单不易出错 3.工作稳定,抗负载能力很强,有完整的双机热备方案

hjyhjy0的博客 500

Hi3798MV300魔百盒安全加固:从刷机到信任链重建

嵌入式设备安全加固是物联网时代的基础课题,其核心在于建立可验证的启动链与细粒度运行时权限控制。基于ARM TrustZone和Verified Boot原理,通过启用Secure Boot、强制SELinux Enforcing模式及内核态eBPF进程拦截,可在资源受限的SoC(如Hi3798MV300)上实现轻量级系统防护。该技术路径不依赖高性能硬件,而重在权限收敛与执行路径可信化,广泛适用于老旧机顶盒改造、智能家居网关加固及运营商定制终端二次赋能等场景。本文以CM201-2为实例,详解如何在1GB内存、

weixin_34319111的博客 425

多思计算机组成原理实验:模型机课程设计

计算机组成原理实验教程 基于多思网络虚拟实验系统 张雯雰

Nick的博客 2万+

Redis源码解析——字典遍历

之前两篇博文讲解了字典库的基础,本文将讲解其遍历操作。之所以将遍历操作独立成一文来讲,是因为其中的内容和之前的基本操作还是有区别的。特别是高级遍历一节介绍的内容,充满了精妙设计的算法智慧。(转载请指明出于breaksoftware的csdn博客) 迭代器遍历 由于Redis字典库有rehash机制,而且是渐进式的,所以迭代器操作可能会通过其他特殊方式来实现,以保...

方亮的专栏 2931

YOLOv改进 | YOLOv11更换主干Backbone之MobileNetV2(轻量化主干网络结构--高效轻量移动模型)

本文提出将YOLOv11的主干网络替换为轻量化的MobileNetV2,以提升模型在移动端设备的部署效率。MobileNetV2通过倒残差结构和线性瓶颈层,显著减少了计算量和参数量(降至1M~3M),同时保持多尺度特征提取能力。实验表明,改进后的模型在移动设备上可实现20-30 FPS的实时检测,适用于手机APP、无人机和工业监控等场景。核心代码展示了倒残差块的实现,通过深度可分离卷积和通道扩展/压缩实现高效特征提取。

走向CTO的路上... 1462

一文整理深度学习【深度学习linux的docker+pytorch+cuda+nvidia-docker+vscode配置】

docker+pytorch+cuda+nvidia-docker+vscode

LetsonH的博客 1336

redis不稳定字典的遍历

redis 不稳定字典的遍历给新观众老爷的开场redis 不稳定字典的遍历redis字典的状态字典的两种不稳定情况1.字典扩容,由小变大2.字典缩容,由大变小1个容易想到的遍历方法不用重新从头遍历的技巧字典扩容情况的遍历字典缩容情况的遍历小结参考资料彩蛋往期博客回顾dictScan源码与注释 给新观众老爷的开场 大家好,我是弟弟! 最近读了一遍 黄健宏大佬的 <<Redis 设计与实现>>,对Redis 3.0版本有了一些认识 该书作者有一版添加了注释的 redis 3.0源码 ??

a158372582的博客 641

基于Simulink的轨道交通信号控制系统仿真详细介绍

轨道交通信号控制系统是确保列车安全、高效运行的核心技术。通过Simulink进行信号控制系统的仿真,可以验证系统的设计、优化调度策略并提高安全性。以下是如何在MATLAB和Simulink中设计并仿真一个轨道交通信号控制系统的详细步骤。通过上述步骤,我们成功设计并实现了基于Simulink的轨道交通信号控制系统仿真。该系统能够根据列车位置、速度和轨道状态生成控制指令,确保列车的安全、高效运行。通过虚拟场景仿真、硬件在环测试和实机测试,验证了系统的性能,并通过参数优化进一步提升了系统的可靠性。引入智能调度。

xiaoheshang_123的博客 463

码洞原创深度技术文章大全 —— 高端面试必备

《快学 Go 语言》第 11 课 —— 协程《快学 Go 语言》第 10 课 —— 错误与异常《快学 Go 语言》第 9 课 —— 接口《快学 Go 语言》第 8 课 —...

codehole_的博客 293

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Redis之字典遍历

遍历和修改 Redis本身就是一个大字典,当我们使用keys命令查找这个大字典时,它会遍历整个redis,如果找到了符合匹配条件的key,还需要判断key指向的对象是否已经过期,如果过期了就需要进行删除操作。 void keysCommand(client *c) { dictIterator *di; // 迭代器 dictEntry *de; // 迭代器当前的entry sds pattern = c->argv[1]->ptr; // keys的匹配模式参数

cainiao1412的博客 1667

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

求不更学不动之Redis5.0新特性Stream尝鲜

Redis5.0最近被作者突然放出来了,增加了很多新的特色功能。而Redis5.0最大的新特性就是多出了一个数据结构Stream,它是一个新的强大的支持多播的可持久化的消息队列,作者坦言Redis Stream狠狠地借鉴了Kafka的设计。 Redis Stream的结构如上图所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的ID和对应的内容。消息是持久化的,Redis重启后...

weixin_34321977的博客 1305

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Redis-字典遍历内部实现原理源码

1、遍历Redis 1)Redis对象树的主干是一个字典,如果对象很多,那么这个树会很大,当我们执行keys命令时,首先它会遍历整棵树,查找出满足条件的key,其次会判断遍历出的key对应的对象是否过期,如果过期则从主干中删除掉 void keysCommand(client *c) {     dictIterator *di; // 迭代器     dictEn...

哆啦A梦 551

Redis源码解析:04字典的遍历dictScan

dict.c中的dictScan函数,用来遍历字典,迭代其中的每个元素。该函数使用的算法非常精妙!!!所以必须记录一下。 遍历一个稳定的字典,当然不是什么难事,但Redis中的字典因为有rehash的过程,使字典可能扩展,也可能缩小。这就带来了问题,如果在两次遍历中间,字典的结构发生了变化(扩展或缩小),字典中的元素所在的位置相应的会发生变化,那如何保证字典...

weixin_30394251的博客 276

RedisTemplate的各种操作(set、hash、list、string)

注入以下RedisTemplate @Autowired private RedisTemplate&lt;String,String&gt; redisTemplate; 1、保存和读取Set: 代码示例: SetOperations&lt;String, String&gt; set = redisTemplate.opsForSet(); set.add("set1","22...

lichuangcsdn的博客 10万+

c语言实现Redis遍历所有key

要遍历Redis所有key或者指定模式的key有两种方法:KEYS和SCAN 1.KEYS命令 KEYS pattern 查找所有符合给定模式pattern(正则表达式)的 key 。时间复杂度为O(N),N为数据库里面key的数量。例如,Redis在一个有1百万个key的数据库里面执行一次查询需要的时间是40毫秒 。 警告: KEYS 的速度非常快,但在一个大的数据库中使用它仍然...

king_jie0210的专栏 2812

字典哈希表的实现原理_redis字典实现哈希表初探

Redis字典在redis中被广泛运用,作为redis字典底层实现的哈希表结构是怎样的呢,今天结合源码初步了解下typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; ...

weixin_39569543的博客 193
上一篇: Redis面试总结
下一篇: redis系列:分布式锁
普通网友
博客等级 码龄8年 55粉丝 197原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值