您好,登錄后才能下訂單哦!
這篇文章主要講解了“什么是Redis-LFU”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“什么是Redis-LFU”吧!
LFU是redis中被使用的一個(gè)淘汰策略,當(dāng)然redis實(shí)現(xiàn)的是非常的巧妙,它的全稱是Least Frequently Used,即用的次數(shù)少的被淘汰。它相比于LRU(大家可以自行了解,后續(xù)博文會(huì)對(duì)它進(jìn)行補(bǔ)充)更精確通過讀redis相關(guān)源碼(version:6.2)發(fā)現(xiàn),每次操作db的時(shí)候都會(huì)調(diào)用 robj *lookupKey(redisDb *db, robj *key, int flags)函數(shù),這個(gè)函數(shù)是這樣實(shí)現(xiàn)的
robj *lookupKey(redisDb *db, robj *key, int flags) { dictEntry *de = dictFind(db->dict,key->ptr); if (de) { robj *val = dictGetVal(de); /* Update the access time for the ageing algorithm. * Don't do it if we have a saving child, as this will trigger * a copy on write madness. */ if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { updateLFU(val); } else { val->lru = LRU_CLOCK(); } } return val; } else { return NULL; } }
當(dāng)在字典中發(fā)現(xiàn)存在這個(gè)key的時(shí)候&它沒有開啟一個(gè)子線程去做saving操作的時(shí)候&配了最大內(nèi)存策略&MAXMEMORY_FLAG_LFU 它都會(huì)updateLFU,讓我們看看updateLFU函數(shù)是怎么實(shí)現(xiàn)的吧,先上代碼:
void updateLFU(robj *val) { unsigned long counter = LFUDecrAndReturn(val); counter = LFULogIncr(counter); val->lru = (LFUGetTimeInMinutes()<<8) | counter; }
先講下LFU的結(jié)構(gòu),這樣對(duì)理解代碼有一定的幫助。LFU像LRU一樣都用了24bit去表示,不同的是,LFU用前16bit表示時(shí)間,剩余的8bit表示被訪問次數(shù),即counter,而LRU用全部的24bit表示時(shí)間。表象是結(jié)構(gòu)的區(qū)別,其實(shí)是內(nèi)在算法實(shí)現(xiàn)上的區(qū)別。LRU僅根據(jù)時(shí)間去判斷這個(gè)key是否該刪除,而LFU是根據(jù)時(shí)間和訪問次數(shù)兩個(gè)因子去確定該key是否被刪,從概率上來判斷,LFU是更準(zhǔn)確的。updateLFU中,最后會(huì)算出val的真正的lru,time<<8位是為了給counter留出8bit的位置,然后|c(diǎn)ounter,得出正確的var的lfu。這個(gè)時(shí)候你會(huì)問了,那明明是lru,其實(shí)在源碼中它既可以表示lru,又可以表示lfu,其實(shí)它就是24bit的int類型字段。它的定義是這樣的
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount; void *ptr; } robj;
下面我們介紹LFU中的幾個(gè)重要的概念
1.LFU的counter(訪問次數(shù))不是一直遞增的,它有一個(gè)遞減策略,它的實(shí)現(xiàn),上源碼:
unsigned long LFUDecrAndReturn(robj *o) { unsigned long ldt = o->lru >> 8; unsigned long counter = o->lru & 255; unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0; if (num_periods) counter = (num_periods > counter) ? 0 : counter - num_periods; return counter; }
方法中的第1行,
unsigned long ldt = o -> lru >> 8 ;
這個(gè)是算出最近一次的訪問時(shí)間,可能這里有人會(huì)問,為什么會(huì)右移8bit,是因?yàn)橛疫叺?位是counter,必須被移出去,才能得到時(shí)間。如果你現(xiàn)在左右移都弄不清的話,那么就先復(fù)習(xí)復(fù)習(xí)左右移。
第2行
unsigned long counter = o -> lru & 255 ;
這個(gè)會(huì)算出counter,&255正好把前邊的16bit都變成0,同樣的話,如果你現(xiàn)在不懂&,請(qǐng)復(fù)習(xí)復(fù)習(xí)&。
第3行
unsigned long num_periods = server . lfu_decay_time ? LFUTimeElapsed ( ldt ) / server . lfu_decay_time : 0 ;
它的作用是算出遞減num,lfu_decay_time表示lfu遞減因子,LFUTimeElaspsed這個(gè)函數(shù)的作用是算出,當(dāng)前now和最近一次訪問的差,即算出多久沒有訪問了。然后/遞減因子,算出需要counter遞減的多少。
最后
if ( num_periods )
counter = ( num_periods > counter ) ? 0 : counter - num_periods ;
return counter ;
算出counter。
2.counter先做遞減,然后再做遞增,上代碼,具體講解,就看我的注釋吧
void updateLFU(robj *val) { unsigned long counter = LFUDecrAndReturn(val); // 遞減操作 counter = LFULogIncr(counter); // 遞增操作 val->lru = (LFUGetTimeInMinutes()<<8) | counter; // 生成真正的LFU }
uint8_t LFULogIncr(uint8_t counter) { if (counter == 255) return 255; // counter的最大值是255 double r = (double)rand()/RAND_MAX; double baseval = counter - LFU_INIT_VAL; if (baseval < 0) baseval = 0; double p = 1.0/(baseval*server.lfu_log_factor+1); if (r < p) counter++; // 經(jīng)過算法運(yùn)算,r < p才做counter++ return counter; }
3.redis每次創(chuàng)建obj的時(shí)候都會(huì)設(shè)置LFU或者LRU,當(dāng)然默認(rèn)是LRU
robj *createObject(int type, void *ptr) { robj *o = zmalloc(sizeof(*o)); o->type = type; o->encoding = OBJ_ENCODING_RAW; o->ptr = ptr; o->refcount = 1; /* Set the LRU to the current lruclock (minutes resolution), or * alternatively the LFU counter. */ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { // 當(dāng)設(shè)置了最大內(nèi)存策略和LFU的時(shí)候走LFU o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL; } else { o->lru = LRU_CLOCK(); } return o; }
當(dāng)設(shè)置了最大內(nèi)存策略和LFU的時(shí)候走LFU,else走LRU
感謝各位的閱讀,以上就是“什么是Redis-LFU”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)什么是Redis-LFU這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。