专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

Redis源码分析之字典dict

  一、dict的使用场景
  redis数据结构set,zset,hash,string类型的会用到dict。zset,hash类型在数据量少的时候会在ziplist,数据量大时,zset使用dictskiplist的数据结构,dict来存储value和score的映射关系。set在集合元素全部为int这种特殊情况下会使用intset,通过二叉查找来查找数据。
  其他场景例如集群模式中用来存储ip:port与clusterNode的映射关系。二、dict的数据结构
  核心数据结构定义如下有:
  2。1dictEntrytypedefstructdictEntry{voidkey;union{voidval;uint64tu64;int64ts64;doubled;}v;structdictEntrynext;}dictEntry;
  2。2dicthttypedefstructdictht{dictEntrytable;tablesizeunsignedlongsize;用于将哈希值映射到table的位置索引size1,hash(key)sizemask来计算落到table的那个bucket上unsignedlongsizemask;dict中数据个数,usedsize的比值就是装载因子}dictht;
  2。3dicttypedefstructdict{dictTypetype;voidprivdata;dicththt〔2〕;表示rehash的步骤,rehashidx1当前没有进行rehashlongrehashidx;numberofiteratorscurrentlyrunningunsignedlongiterators;}dict;
  2。4dictTypetypedefstructdictType{uint64t(hashFunction)(constvoidkey);void(keyDup)(voidprivdata,constvoidkey);void(valDup)(voidprivdata,constvoidobj);int(keyCompare)(voidprivdata,constvoidkey1,constvoidkey2);void(keyDestructor)(voidprivdata,voidkey);void(valDestructor)(voidprivdata,voidobj);}dictType;
  层属关系代码看着不太直观,我们换成图:
  redisdict核心数据结构模型三、dict的核心API
  dict在需要扩展内存时避免一次性对所有key进行rehash,而是将rehash操作分散到对于dict的各个增删改查的操作中去。这种方法能做到每次只对一小部分key进行重哈希,而每次重哈希之间不影响dict的操作。这是为了避免rehash期间单个请求的响应时间剧烈增加。
  判断是否进行rehashstaticintdictExpandIfNeeded(dictd){判定是否当前处于rehash的状态。if(dictIsRehashing(d))returnDICTOK;初始设置,tablesize4if(dht〔0〕。size0)returndictExpand(d,DICTHTINITIALSIZE);Ifwereachedthe1:1ratio,andweareallowedtoresizethehashtable(globalsetting)orweshouldavoiditbuttheratiobetweenelementsbucketsisoverthesafethreshold,weresizedoublingthenumberofbuckets。if(dht〔0〕。useddht〔0〕。size(dictcanresize数据量超过tablesize的5倍dht〔0〕。useddht〔0〕。sizedictforceresizeratio)){根据当前dict元素数量的2倍进行扩展returndictExpand(d,dht〔0〕。used2);}returnDICTOK;}
  dict扩展逻辑intdictExpand(dictd,unsignedlongsize){thesizeisinvalidifitissmallerthanthenumberofelementsalreadyinsidethehashtableif(dictIsRehashing(d)dht〔0〕。usedsize)returnDICTERR;dicthtn;thenewhashtableunsignedlongrealsizedictNextPower(size);Rehashingtothesametablesizeisnotuseful。if(realsizedht〔0〕。size)returnDICTERR;根据新的size重新分配一个hashtablen。sizerealsize;n。sizemaskrealsize1;n。tablezcalloc(realsizesizeof(dictEntry));n。used0;Isthisthefirstinitialization?Ifsoitsnotreallyarehashingwejustsetthefirsthashtablesothatitcanacceptkeys。if(dht〔0〕。tableNULL){dht〔0〕n;returnDICTOK;}重新分配的hashtable置于ht〔1〕dht〔1〕n;开始渐进rehash的标记drehashidx0;returnDICTOK;}如果2nused2返回2n,hashtable的size一定是2nstaticunsignedlongdictNextPower(unsignedlongsize){unsignedlongiDICTHTINITIALSIZE;if(sizeLONGMAX)returnLONGMAX1LU;while(1){if(isize)returni;i2;}}
  开启渐进式rehash
  什么时候开启渐进式rehash,在每次从hashtable中定位key的时候,代码如下staticlongdictKeyIndex(dictd,constvoidkey,uint64thash,dictEntryexisting){unsignedlongidx,table;dictEntryhe;if(existing)existingNULL;判断是否需要扩展if(dictExpandIfNeeded(d)DICTERR)return1;拉链法遍历hashtable,遍历两个hashtableht〔0〕,ht〔1〕,如果当前没有在rehash,则只遍历ht〔0〕for(table0;table1;table){计算数据所在bucket位置,找到链表第一个entryidxhashdht〔table〕。sizemask;Searchifthisslotdoesnotalreadycontainthegivenkeyhedht〔table〕。table〔idx〕;遍历链表,比较key值while(he){if(keyhekeydictCompareKeys(d,key,hekey)){if(existing)existinghe;return1;}hehenext;}if(!dictIsRehashing(d))break;}returnidx;}
  分步渐进rehash
  什么时候进行分步渐进rehash,在每一个add,get操作中:dictEntrydictAddRaw(dictd,voidkey,dictEntryexisting){longindex;dictEntryentry;dicththt;进行分步rehashif(dictIsRehashing(d))dictRehashStep(d);Gettheindexofthenewelement,or1iftheelementalreadyexists。if((indexdictKeyIndex(d,key,dictHashKey(d,key),existing))1)returnNULL;Allocatethememoryandstorethenewentry。Inserttheelementintop,withtheassumptionthatinadatabasesystemitismorelikelythatrecentlyaddedentriesareaccessedmorefrequently。rehash时,数据存入ht〔1〕htdictIsRehashing(d)?dht〔1〕:dht〔0〕;entryzmalloc(sizeof(entry));entrynexthttable〔index〕;httable〔index〕entry;htused;设置key,返回entrydictSetKey(d,entry,key);returnentry;}
  分步rehash过程staticvoiddictRehashStep(dictd){if(diterators0)dictRehash(d,1);}intdictRehash(dictd,intn){intemptyvisitsn10;Maxnumberofemptybucketstovisit。if(!dictIsRehashing(d))return0;while(ndht〔0〕。used!0){dictEntryde,nextde;Notethatrehashidxcantoverflowaswearesuretherearemoreelementsbecauseht〔0〕。used!0assert(dht〔0〕。size(unsignedlong)drehashidx);如果table中存在bucket为空时,检查10个bucket后都为空则返回,等下一次再处理while(dht〔0〕。table〔drehashidx〕NULL){drehashidx;if(emptyvisits0)return1;}dedht〔0〕。table〔drehashidx〕;一次处理table中的一个bucketMoveallthekeysinthisbucketfromtheoldtothenewhashHTwhile(de){uint64th;nextdedenext;GettheindexinthenewhashtablehdictHashKey(d,dekey)dht〔1〕。sizemask;denextdht〔1〕。table〔h〕;dht〔1〕。table〔h〕de;dht〔0〕。used;dht〔1〕。used;denextde;}dht〔0〕。table〔drehashidx〕NULL;drehashidx;}Checkifwealreadyrehashedthewholetable。。。if(dht〔0〕。used0){zfree(dht〔0〕。table);dht〔0〕dht〔1〕;dictReset(dht〔1〕);drehashidx1;return0;}Moretorehash。。。return1;}
  redisdict的缩小的触发存在两种情况hash,set,zset这三类数据使用的dict,在删除元素的时候会尝试rehash另一种是定时尝试进行rehash
  尝试触发的代码如下当usedsize10时进行开启缩容rehash。inthtNeedsResize(dictdict){longlongsize,used;sizedictSlots(dict);useddictSize(dict);return(sizeDICTHTINITIALSIZE(used100sizeHASHTABLEMINFILL));}IfthepercentageofusedslotsintheHTreachesHASHTABLEMINFILLweresizethehashtabletosavememoryvoidtryResizeHashTables(intdbid){if(htNeedsResize(server。db〔dbid〕。dict))dictResize(server。db〔dbid〕。dict);if(htNeedsResize(server。db〔dbid〕。expires))dictResize(server。db〔dbid〕。expires);}
  由于rehash时需要分配内存,此部分内存是从redismaxmemory中的一部分,因此当bucket比较大时,rehash生成ht〔1〕时会使用大量内存。如果ht〔1〕占用的内存原来的内存超过maxmeory,则会发生key逐出。由于sizeof(dictEntry)8byte,因此当ht〔0〕size为4时,ht〔1〕扩展2倍,ht〔1〕占用内存为84264byte,当size2n时,ht〔1〕占用的内存为822n。
  三、dictscan
  redis中dict是有状态的,dict存在四种状态:
  1正常状态
  2正在rehash状态
  3rehash扩容完成状态
  4rehash缩容完成状态
  由于redis中dict是有状态的,只有在正常状态下可以完整的扫描字典中的所有的key。当dict在进行扩容和缩容时可能会存在扫描key的遗漏或者重复扫描。由于redis的dict中存在两个hashtable,分别为ht〔0〕,ht〔1〕。rehash的过程是将ht〔0〕的key重新hash到ht〔1〕。这时redis的所有key其实存在于两个hashtable中。redis针对四种情况的方案如下:Dicttablesize保持不变,稳定的状态下,直接顺序遍历即可DictResize,dict扩大了,如果还是按照顺序遍历,就会导致扫描大量重复Key。比如tablesize从8变成了16,假设之前访问的是3号桶,那么表扩展后则是继续访问415号桶;但是,原先的03号桶中的数据在Dict长度变大后被迁移到811号桶中,因此,遍历811号桶的时候会有大量的重复Key被返回。DictResize,dict缩小了,如果还是按照顺序遍历,就会导致大量的Key被遗漏。比如tablesize从8变成了4,假设当前访问的是3号桶,那么下一次则会直接返回遍历结束了;但是之前47号桶中的数据在缩容后迁移带可03号桶中,因此这部分Key就无法扫描到。字典正在rehash,这种情况如(2)和(3)情况一下,要么大量重复扫描、要么遗漏很多Key。
  在redis正在rehash时,采用hash桶掩码的高位序访问来解决。如下图
  hash桶掩码的高位序访问
  高位序访问即按照dictsizemask(掩码),在有效位(上图中dictsizemask为00000111)上从高位开始加一枚举(右边图的);低位则按照有效位的低位逐步加一枚举(左边的图)。
  高位序遍历路径:04261537
  低位序遍历路径:01234567
  redis采用掩码高位序访问的原因是在rehash时尽量少的重复扫描key。为什么从高位掩码访问会减少重复扫描。由于rehash最终计算bucket位置时是取模操作(vsizemask),举个例子:
  针对v00101011,原来sizemask为00000111,取模000001110010101100000011
  当sizemask为00001111,取模000011110010101100001011
  可以看出取模后的两个值存在相同的后缀011。sizemask变大后,生成的结果只是高位发生变化。原来落到bucket〔00000011〕的key,tablesize从816时,sizemask从0000011100001111时会落到〔00000011〕和〔00001011〕中,高位变化,低位不变,此时再看一遍从高位序访问的图
  看一个redis进行scan操作的例子,如下图。
  redis从左边hashtablebucket0开始遍历,到遍历bucket6时发生了rehash。bucket长度为8扩展到16,redis的scan路径就会按照61419513311715来完成,这里存在一个隐藏逻辑,一定是先遍历小hashtable,再变了大的hashtable。
  hashtablebucket
  从图上可以看出高位序scan在dictrehash时即可以避免重复遍历,又能完整返回原始的所有Key。同理,字典缩容时也一样,字典缩容可以看出是反向扩容。
  来看一下redisscan源码unsignedlongdictScan(dictd,unsignedlongv,dictScanFunctionfn,dictScanBucketFunctionbucketfn,voidprivdata){dicthtt0,t1;constdictEntryde,next;unsignedlongm0,m1;if(dictSize(d)0)return0;没有进行rehash的情况if(!dictIsRehashing(d)){t0(dht〔0〕);m0t0sizemask;Emitentriesatcursorif(bucketfn)bucketfn(privdata,t0table〔vm0〕);det0table〔vm0〕;while(de){nextdenext;fn(privdata,de);denext;}高位序遍历计算出下一个cursorvm0;Incrementthereversecursorvrev(v);v;vrev(v);}else{进行rehash的情况t0dht〔0〕;t1dht〔1〕;确保t0为小hashtable,t1为大的hashtableif(t0sizet1size){t0dht〔1〕;t1dht〔0〕;}m0t0sizemask;m1t1sizemask;先根据cursor遍历小的hashtableif(bucketfn)bucketfn(privdata,t0table〔vm0〕);det0table〔vm0〕;while(de){nextdenext;fn(privdata,de);denext;}再变了大的hashtable,大的hashtable会遍历多个bucketdo{Emitentriesatcursorif(bucketfn)bucketfn(privdata,t1table〔vm1〕);det1table〔vm1〕;while(de){nextdenext;fn(privdata,de);denext;}反向二进制迭代算法来计算出下一个cursorvm1;vrev(v);v;vrev(v);Continuewhilebitscoveredbymaskdifferenceisnonzero}while(v(m0m1));}returnv;}反向二进制迭代算法1。对游标进行二进制翻转(原来的高位变成低位)2。低位1(对原来的高位进行1,进位等操作)3。再进行一次二进制翻转(恢复原来的高位,此时完成高位1迭代)
  作者介绍:互联网十年交易支付搜索等架构和研发经验,擅长架构设计百亿级流量系统和高并发性能调优,目前专注做卫星通信卫星遥感的研发工作,努力拥抱商业航天的星辰大海。公众号:无量云颠
  欢迎一起交流技术!

拜登夫妇穿着丧服登机回国,葬礼上坐得太靠后,遭到特朗普的嘲笑当地时间9月19日,美国总统乔拜登和夫人吉尔拜登,在参加完已故英国女王伊丽莎白二世在威斯敏斯特大教堂的国葬仪式后,就快速地赶到了机场。据报道称,从仪式结束到他们登上飞机,不过才2个小米新款人脸识别智能门锁开启预售3D结构光,到手价1999元IT之家9月20日消息,2021年10月9日,小米发布了人脸识别智能门锁X,该门锁采用3D结构光人脸识别,配备一块AMOLED屏,售价3299元。今日,小米新款人脸识别智能门锁开启大乐透头奖2注981万1注追加奖池余额7。96亿元北京时间9月19日晚,体彩大乐透第22108期开奖。当期开奖号码为前区0407132628,后区0711。本期一等奖开出2注,单注奖金981。7万余元,其中1注选择追加,多得785埃安首款超跑进入超跑俱乐部!这些自主品牌的超跑最贵千万级9月16日,埃安与国内两大超跑俱乐部SCC和19号公路完成签约,宣告HyperSSR成为俱乐部的会员准入车型。作为埃安Hyper系列的首款车型,HyperSSR正式成为首款被两家顶碳酸锂两破50万工信部三提锂资源保供稳价!锂价碳酸锂近日,由于碳酸锂现货报价重回50万元吨的高位,市场上对碳酸锂的探讨甚嚣尘上。而工信部更是在9月份接连两次提及碳酸锂保供稳价的话题,足以可见对其之重视。据SMM最新现货报价显示,今日手游现在都太烧钱,怀念以前的传奇世界最早玩网络游戏是2003年时接触了热血传奇和传奇世界,特别是传奇世界满满的回忆,高中时,几个同学夜修一结束就约一起翻墙去学校外面的网吧组队打传奇世界,那时传奇世界还是按点卡收费的,马耳他护照是骗人的吗?免签哪些国家?马耳他护照如何办理?马耳他是位于南欧的一个迷你小岛国,虽然国土面积小,不过岛屿的风景十分秀美,气候也温和舒适,可以说是世界上最宜居的国家之一。除此之外,马耳他还是全球仅有的四位一体国,持有该国护照后可他害死50万华人,贪污国家7成财政,花天酒地活到87岁社会对于一个人来说改变是非常大的,每一个人都是一张白纸,进入了社会之后,在这形形色色当中,不知不觉就染上了其余的颜色,有一些人迷途知返,浪子回头,但是往往就有一些为了手中的利益,宁1955年,蒋介石看着十大元帅名单,指着陈毅苦笑这个人太厉害了1955年9月27日,蒋介石在台湾的府邸中看报纸,此时距离他败退台湾已经过去了6年。这一天注定是不平凡的一天,千里之外的北京正在举行开国十大元帅的授衔仪式。蒋介石看到十大元帅的名单成分党必知忍冬花想要有效护肤,当然不可避免地要具有一定的成分观念,不单单看产品的宣传,更要看产品的成分,今天我们来唠一下护肤品成分之忍冬花。忍冬花是什么忍冬花,忍冬属忍冬科多年生半常绿藤本植物,其不管女人多少岁,谨记护肤3不要,皮肤更水润,颜值变高不管多少岁的女人,都想拥有一副水润健康的肌肤状态,年轻的时候虽然皮肤相对紧致水润,但是如果护肤不当是很容易长黑头闭口以及黑眼圈这种情况的。到了一定的年龄的女人,如果护肤不当,不仅会
女篮国手李梦婚内出轨,与张隆交往细节曝光,私密照流出尺度之大文丨体娱新视线编辑丨体娱新视线前言中国篮球在今年似乎比较犯冲,前有新疆队与篮协对簿公堂,现有女篮主力球员被爆插足别人婚姻,后者更是霸占热搜榜三天之久。即便到了今天,这起事件依旧还在首轮对阵掘金?湖人队勇士不敢打的对手我们来打!球迷希望把勇士不敢打的掘金灰熊交给湖人,把湖人不敢打的快船太阳交给勇士,难道是寄希望于湖人勇士一起进西部决赛吗?季后赛首轮想打约老师?现在的湖人还没有资格!原本以为有的队为附加赛都国足输512万人口小国!被别人使劲推不敢还手,12惨败赛后还在笑北京时间3月26日,中国足球再创史诗大耻辱,输给世界排名第105名的新西兰,而且被对手创造了新纪录。今日国足迎来了和新西兰的热身赛,第2场的比赛上一场批评新西兰,所有人都觉得国足绝女排名帅陈忠和近况二婚娶女排弟子为妻,今已退休携妻回归家庭在阅读此文前,诚邀您点击一下关注,既方便您进行讨论与分享,又给您带来不一样的参与感,感谢您的支持。时隔12年的冠军一代天骄陈忠和和郎平的冠军时代接力2016年,在巴西里约热内卢的马斋月坑惨独行侠,抉择难题给到库班一年一度的欧文奇葩秀又开始上演了,自3月24日至4月23日又进入了斋月期间,每日3001600不能进食,看过NBA球员日常的都知道,就算当日没有比赛,球员白天也会有高强度训练,即使比永博17135帽太阳大胜76人终结3连败!恩比德2810NBA常规赛3月26日继续进行,本场比赛哈登杜兰特和艾顿都继续缺阵,最终,太阳以125105战胜76人,太阳终结了3连败。首节开始,布克打成31让太阳开局97稍稍领先,马克西三分恩5000名跑步爱好者相约齐河用脚步丈量黄河岸边最美赛道马拉松参赛者在比赛中。刘磊摄中新网山东齐河3月26日电(康石磊韩菲)2023全国马拉松比赛26日在德州市齐河县体育公园鸣枪起跑。来自全国各地的5000名跑步爱好者相约齐河,用脚步丈76人105125太阳,可怕的不是输球,而是恩比德赛后这番话20222023赛季NBA常规赛正在如火如荼的进行中,3月26日比赛结束后,来看下今日赛况以及东部最新排名步行者130143不敌老鹰,篮网以129100击败热火,雄鹿106129不参照谢亚龙南勇陆军的刑期,受贿2。7亿的李铁要坐几年牢?中国足坛反腐行动不断深入,犯罪分子利益链越来越清晰凸现,越来越多的大鱼浮出水面。2022年11月26日,国家男子足球队原主教练李铁因严重违法被抓获归案。李铁主动交代贪污和其他违法行国乒3消息马龙3巨头退赛,孙颖莎获大奖,刘国梁恩师陷反腐风波最近国乒正在北京备战比赛,接下来将会在国内进行德班直通赛的第二站的赛事。在完成这一次的赛事之后,将会奔赴河南新乡开始4月9号的冠军赛。在这期间国乒发生三个非常值得关注的消息,接下来中国足球被这两个人玩坏了头条创作挑战赛中国足球被这些人玩坏了,可怜我曾经那么爱她。以前写的一组七律留作纪念球迷宅男寂寞少人陪,幸有欢情世界杯。一碟花生熬半夜,数瓶啤酒饮多回。膀胱告急身难起,形势揪心意不颓
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网