当前位置: 首页 > 后端技术 > PHP

【Redis5源码学习】redis命令randomkey解析

时间:2023-03-30 03:40:15 PHP

baiyan命令语法命令含义:从当前选中的数据库中随机返回一个key命令格式:RANDOMKEY命令实战:127.0.0.1:6379>keys*1)"kkk"2)"key1"127.0.0.1:6379>randomkey"key1"127.0.0.1:6379>randomkey"kkk"返回值:随机key;如果数据库为空,则返回nil源代码分析主流程keys命令对应的处理函数为randomKeyCommand():voidrandomkeyCommand(client*c){robj*key;//存储获取的密钥if((key=dbRandomKey(c->db))==NULL){//调用核心函数dbRandomKey()addReply(c,shared.nullbulk);//返回零返回;}addReplyBulk(c,key);//返回密钥decrRefCount(key);//减少引用计数}随机键生成和过期判断randomKeyCommand()调用dbRandomKey()函数实际生成一个随机键:intmaxtries=100;intallvolatile=dictSize(db->dict)==dictSize(db->e??xpires);while(1){sds密钥;robj*keyobj;de=dictGetRandomKey(db->dict);//获取一个随机的dictEntryif(de==NULL)returnNULL;//获取失败返回NULLkey=dictGetKey(de);//获取keykeyobj=creatindictEntryeStringObject(键,sdslen(键));//根据key字符串生成robjif(dictFind(db->e??xpires,key)){//在过期字典中查找key...if(expireIfNeeded(db,keyobj)){//判断是否是密钥已过期decrRefCount(keyobj);//如果过期,删除key并减少引用计数continue;//当前key已经过期无法返回,只会返回未过期的key用于下一次随机生成}}returnkeyobj;}}然后这一层的主要逻辑就是调用dictGetRandomKey()获取一个随机的dictEntry。假设我们已经获得了一个随机生成的dictEntry,然后我们取出密钥。由于过期的key无法返回,所以我们需要先判断key是否过期。过期无法退货,直接continue;如果没有过期,可以退货。真正的获取随机key的算法那我们继续跟进dictGetRandomKey()函数,看看是用什么算法随机生成dictEntry的:dictEntry*dictGetRandomKey(dict*d){dictEntry*he,*orighe;无符号长h;intlistlen,listele;如果(dictSize(d)==0)返回NULL;//传入的字典是空的,所以不需要生成if(dictIsRehashing(d))_dictRehashStep(d);//执行一次rehash操作if(dictIsRehashing(d)){//如果是rehash,注意保证随机种子从两个哈希表中均匀分布do{h=d->rehashidx+(random()%(d->ht[0].size+d->ht[1].size-d->rehashidx));//计算一个随机的hash值,这个hash值必须在rehashidx的最后he=(h>=d->ht[0].size)?d->ht[1].table[h-d->ht[0].size]:d->ht[0].table[h];//根据上面计算的hash值得到对应的bucket}while(他==NULL);//一直循环计算,取最后一个计算结果不为空的桶}else{//不在rehash,只有一张哈希表do{h=random()&d->ht[0].sizemask;//直接计算哈希值he=d->ht[0].table[h];//取出哈希表中的第h个桶}while(he==NULL);//循环计算,取最后一个计算结果不为空的桶}//现在我们得到一个不为空的桶,这个桶后面还附加了一个或多个dictEntry(解决hash冲突的链地址法),所以还需要计算一个随机索引来决定访问哪个dickEntry链表节点listlen=0;orighe=他;while(he){he=he->next;听着++;//计算链表的长度}listele=random()%listlen;//取随机数的余数到链表长度,确定取哪个节点he=orighe;while(listele--)he=he->next;//从前到后遍历这个桶上的链表,找到这个节点returnhe;//最后返回这个节点}这个函数会先判断字典为空,然后进行单次Step-by-steprehash操作,和调用dictAdd()等字典函数效果一样,是progressive的一部分重新哈希技术。这里先回顾一下字典的整体结构:由于rehash会影响随机数种子的生成,根据当前字典是否正在rehash,需要分两种情况讨论:第一种:rehash正在进行中.那么当前字典的结构是:部分键在第一个哈希表上,其余键在第二个哈希表上。为了平均分配两个哈希表可能被访问的概率,需要将两个哈希表放在一起考虑。该算法是:h=d->rehashidx+(random()%(d->ht[0].size+d->ht[1].size-d->rehashidx));//计算随机hash值,这个hash值一定在rehashidx后面,这里rehashidx将两个hash表的大小之和减去一个随机数取余。这样的取余操作可以保证哈希值会随机落在索引大于rehashidx的桶上。因为rehashidx表示rehash的进度。这个rehashidx表示第一个哈希表上这个索引之前的数据,即[0,rehashidx-1],这个闭区间的数据已经被rehash到第二个哈希表中了。而大于等于这个rehashidx的元素还在第一个哈希表上。因此,这确保了结果h上的任何桶都是非空的并且具有值。接下来只需要判断h值在哪个哈希表上,然后到哈希表上对应位置的桶中取值即可:he=(h>=d->ht[0].size)?d->ht[1].table[h-d->ht[0].size]:d->ht[0].table[h];第二种:没有rehash操作。然后所有的键都在唯一的第一本字典上。这种情况很简单。可以直接计算字典长度的余数,或者对字典的sizemask进行按位与运算,可以保证计算结果落在HaGreek表中。Redis选择了后者:h=random()&d->ht[0].sizemask;//对sizemask按位与运算计算hash值he=d->ht[0].table[h];//取出哈希表上的第h个bucket接下来,我们找到了一个非空的bucket,但是还没有结束。由于可能存在hash冲突,redis采用链地址的方式解决hash冲突,所以会在一个bucket后面挂载多个dictEntry,形成一个链表。因此,我们还需要考虑取链表节点上的哪个dictEntry。这个算法比较简单,直接用random()的结果求链表长度的余数即可:listele=random()%listlen;//用随机数取链表长度的余数,决定取哪个节点while(listele--)he=he->next;//从前到后遍历这个桶上的链表,找到这个节点。至此,我们已经在一个随机的bucket上找到了一个随机的dictEntry节点,然后我们就可以将它返回给client了。