LFUアルゴリズムでのRedis

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は、理想的に非常に近いされていますつまり、アルゴリズムの性能アルゴリズム自体はさらに上がることは困難でした。RedisLRUmaxmemory_samplesmaxmemory_samplesLRULRU

だから、アルゴリズムは再度アクセスされる可能性が最も高い、将来のデータ、およびいることを保持することを意図していたうち広場に戻ってのアイデア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設定

Redis4.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-> LRU255 ;
    符号なしの 長い num_periods =サーバー。lfu_decay_timeLFUTimeElapsed(LDT)/サーバー。lfu_decay_time0 ;
    もし(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、その後、counternは減少します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であれば、それは容易に除去することができ、また、新しいキーの初期設定をする必要がありcountercreateObject

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)命じたとして基礎を。

参考リンク

おすすめ

転載: www.linuxidc.com/Linux/2019-07/159655.htm