でRedisのLRUアルゴリズムでは、テキストに来るLRU
次の場合に欠陥があります:
~~~~~A~~~~~A~~~~~A~~~~A~~~~~A~~~~~A~~|
~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~|
~~~~~~~~~~C~~~~~~~~~C~~~~~~~~~C~~~~~~|
~~~~~D~~~~~~~~~~D~~~~~~~~~D~~~~~~~~~D|
ほとんどのデータは、将来的にアクセスすることができるためDデータが誤解されることになります。
Redis
著者は改善したかったLRU
アルゴリズムを、しかし見つかったアルゴリズムは、ランダムサンプリング数の対象となる、に等しいケース10は、理想的に非常に近いされていますつまり、アルゴリズムの性能アルゴリズム自体はさらに上がることは困難でした。Redis
LRU
maxmemory_samples
maxmemory_samples
LRU
LRU
だから、アルゴリズムは再度アクセスされる可能性が最も高い、将来のデータ、およびいることを保持することを意図していたうち広場に戻ってのアイデアLRU
アルゴリズムつい最近アクセスしたデータは、将来を予測するためには、訪問される可能性が最も高いです。私たちは、それが使用する、アイデアを変更することができLFU(Least Frequently Used)
、最も頻繁にアクセスされるデータは、将来的にアクセスされる可能性が最も高いですアルゴリズムを、。B> A> C = D:上記の場合、頻繁にアクセスされる状況に応じてリテンション優先順位を決定することができます 。
RedisののLFUのアイデア
ではLFU
、アルゴリズムは、各キーのカウンターを維持することができます。カウンタが増加した場合、すべてのキーは、アクセスされました。カウンタ大きく、それはより頻繁に等しくアクセスについてことができます。
単純なアルゴリズム上記2つの問題があります。
- では
LRU
、アルゴリズム、単にリストの最初に訪問したノードを入れ、二重リンクリストを維持することができますが、LFU
現実的ではありません、ノードは厳密に注文カウンターに従ってください、新しいノードまたはノードが場所を更新する、時間の複雑さあなたはO(N)に達することがあります。 - 単にカウンタ方式は完璧ではありません増します。アクセスモードが頻繁に変更され、アクセスされるアクセス頻度の低い期間内の時間の重要な期間の後、唯一のカウンタの増加は、この傾向を反映していません。
解決最初の問題は、から学ぶことができるLRU
達成し、プールの外に鍵を維持するために経験。第二の問題を解決するには、最後の時間は、キーレコードにアクセスし、その後、時間をかけて、カウンターを削減すべきです。
Redis
次のようにオブジェクトの構造は、次のとおりです。
typedefは 構造体 redisObject { 符号なしのタイプ:4。 符号なしエンコーディング:4。 符号なし LRU:LRU_BITS。/ * LRU時間(グローバルlru_clockに対して)または * LFUデータ(最下位8ビット周波数 *最上位16ビットは時間にアクセスします)。* / int型参照カウント。 無効 PTR *; } robj。
LRU
アルゴリズム、24ビットであるlru
記録するために使用LRU time
中のLFU
この分野で使用されてもよいが、16ビットに8ビットを使用して分割されています。
16 bits 8 bits
+----------------+--------+
+ Last decr time | LOG_C |
+----------------+--------+
減少前回のカウンタを記録するために使用される上位16ビットはldt
、分単位で、下位8ビットは、カウンタ値を記録しましたcounter
。
LFU設定
Redis
4.0の後でmaxmemory_policy
、戦略のうち2の追加LFU
のモード:
volatile-lfu
:キーはの有効期限があるLFU
消去アルゴリズムをallkeys-lfu
:すべてのキーの採用LFU
アルゴリズムアウト
調整するための2つの設定がありますLFU
アルゴリズムは:
lfu-log-factor 10
lfu-decay-time 1
lfu-log-factor
カウンタを調整することができるcounter
成長率、lfu-log-factor
より大きなcounter
成長も遅いし。
lfu-decay-time
分単位の値は、調整することができるされているcounter
の速度を減少させます
ソースコードの実装
ではlookupKey
ミドル:
robj * lookupKey(redisDb * DB、robj *キー、int型フラグ){ dictEntry *デ= dictFind(DB-> 辞書、KEY-> PTR)。 もし(デ){ robj *ヴァル= dictGetVal(デ)。/ *高齢化アルゴリズムのためのアクセス時間を更新します。 *私たちは節約の子を持っている場合、これが引き金となるよう、それをしないでください *書き込み狂気のコピーを。* /場合(サーバーrdb_child_pid == - 1台の && サーバー。aof_child_pid == - 1 && !(フラグ&LOOKUP_NOTOUCH)) { 場合(サーバー。maxmemory_policy&MAXMEMORY_FLAG_LFU){ updateLFU(val)で、 } 他 { val-> LRU = LRU_CLOCK()。 } } 戻りヴァルと、 } 他 { 戻りNULL。 } }
使用する場合はLFU
タイム戦略のupdateLFU
アップデートをlru
:
/ *オブジェクトがアクセスされた更新LFU。 減少時間に達した場合*まず、カウンタをデクリメントします。 *その後、対数カウンタをインクリメントし、アクセス時間を更新します。* / ボイド updateLFU(robj *ヴァル){ 符号なしの ロングカウンタ= LFUDecrAndReturn(ヴァル)。 カウンタ= LFULogIncr(カウンタ) val-> LRU =( LFUGetTimeInMinutes()<< 8)| カウンタ; }
LFUDecrAndReturnを削減
まず第一に、LFUDecrAndReturn
するcounter
削減策を実行します。
/ *オブジェクトデクリメント時間がLFUカウンタ減少に達しているが、場合 *オブジェクトのLFUフィールドを更新していない、我々は、アクセス時間の更新 オブジェクトが実際にアクセスされたときに、明示的な方法で*とカウンターを。 *そして、我々は時間がの倍に応じてカウンタを半減します server.lfu_decay_timeより*経過時間。 *対象の周波数カウンタを返します。 * *この機能は、最高のオブジェクトのためのデータセットをスキャンするために使用されている 収まるように*:私たちは候補者をチェックするように、我々は、増分デクリメント 、必要に応じてスキャンしたオブジェクトの*カウンターを。* / 符号なしの ロング LFUDecrAndReturn(robjの* 0){ 符号なしの ロングLDT = O-> LRU >> 8 ; 符号なしの 長いカウンター= O-> LRU&255 ; 符号なしの 長い num_periods =サーバー。lfu_decay_time?LFUTimeElapsed(LDT)/サーバー。lfu_decay_time:0 ; もし(num_periods) カウンタ=(num_periods>カウンタ)?0:カウンタ- num_periods。 リターン・カウンタ; }
関数は、最初の最近低下時間の上位16ビットを得るためにldt
カウンタの下位8ビットをcounter
、その後の構成に応じてlfu_decay_time
減少させなければならないどのくらいの計算。
LFUTimeElapsed
そして、現在の時間を計算するために使用さldt
の違いを:
/ *ちょうど最下位取って、分単位で現在の時刻を返します * 16ビットを。返された時間は、LDT(最後の減少として格納するのに適している LFU実装用*時間)。* / 符号なしの ロング LFUGetTimeInMinutes(ボイド){ リターン(サーバ。 unixtime / 60)& 65535。 } / *オブジェクト最後のアクセス時間を与え、分の最小数の計算の最後のアクセスからの経過*を。オーバーフロー(よりLDT大きい扱う現在の16ビット分の時間*)包装などの時間考慮*正確度を。* / 符号なしの ロング LFUTimeElapsed(符号なし ロング LDT){ 符号なしの 長い今= LFUGetTimeInMinutes()。 もし(今> = LDT)のリターンになりまし-LDT。 戻る 65535今-ldt +を。 }
特に、現在の時間が経過した後下位16ビットと分数に、次いで計算ldt
差をnow-ldt
。場合ldt > now
かけデフォルト場合(65535まで16ビット)値65535-ldt+now
。
[構成と相違lfu_decay_time
分割は、LFUTimeElapsed(ldt) / server.lfu_decay_time
過去のNとなっているlfu_decay_time
、その後、counter
nは減少しますcounter - num_periods
。
成長LFULogIncr
成長関数LFULogIncr
は次のよう:
/ *対数カウンタをインクリメントします。大きい現在のカウンタ値がある 可能性が低い*それが本当に実現してしまうことです。255でそれを飽和* / uint8_t LFULogIncr( uint8_tのカウンタ){ 場合(カウンタ== 255)を返す 255。 ダブル R =(ダブル)のrand()/ RAND_MAX。 ダブル BASEVAL =カウンタ- LFU_INIT_VAL。 もし(BASEVAL < 0)BASEVAL = 0。 ダブル P = 1.0 /(BASEVAL *サーバー。 lfu_log_factor+ 1)。 もし(R <P)カウンタ++; リターン・カウンタ; }
counter
単に代わりにPに制御成長の間に0-1の係数を使用しての、1一度訪問していません。counter
最大値は255です。乱数r及びpはのように、0-1の間の比較をとりr<p
、それが増加するcounter
ビットクレジット出力制御ポリシーと同様です。pは電流に依存するcounter
値とlfu_log_factor
係数counter
値とlfu_log_factor
大きな因子、小さいP、r<p
小さい確率、counter
小さいの成長の確率。成長次のように:
+--------+------------+------------+------------+------------+------------+
| factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits |
+--------+------------+------------+------------+------------+------------+
| 0 | 104 | 255 | 255 | 255 | 255 |
+--------+------------+------------+------------+------------+------------+
| 1 | 18 | 49 | 255 | 255 | 255 |
+--------+------------+------------+------------+------------+------------+
| 10 | 10 | 18 | 142 | 255 | 255 |
+--------+------------+------------+------------+------------+------------+
| 100 | 8 | 11 | 49 | 143 | 255 |
+--------+------------+------------+------------+------------+------------+
目に見えるcounter
成長と訪問数の推移のプレゼンテーションは、成長しているセッション数を増やすcounter
よりゆっくりと成長しています。
新入生キー戦略
もう一つの問題は、新しいオブジェクトを作成するときに、オブジェクトがあるということcounter
、それが0であれば、それは容易に除去することができ、また、新しいキーの初期設定をする必要がありcounter
、createObject
:
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) {
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
o->lru = LRU_CLOCK();
}
return o;
}
counter
それはに初期化されますLFU_INIT_VAL
デフォルト5。
プール
アルゴリズムをプールするLRU
のと同じアルゴリズム:
もし(server.maxmemory_policy&(MAXMEMORY_FLAG_LRU | MAXMEMORY_FLAG_LFU)||
server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
計算idle
は異なるとき:
} そう であれば(server.maxmemory_policy&MAXMEMORY_FLAG_LFU){ / *我々はLRUポリシーを使用する場合、我々は、アイドル時間によってキーをソート 我々が大きいアイドル時間から始まるキーを期限切れにするように*。 ポリシーはLFU 1のとき*しかし、我々は周波数持ち *推定を、私たちは、より低い周波数でキーを立ち退かしたい 最初の*。そうプール内では、反転使用してオブジェクトを置く 最大に実際の周波数を減算*周波数を 255 *周波数* / アイドル= 255 - LFUDecrAndReturn(O)
使用255-LFUDecrAndReturn(o)
命じたとして基礎を。