【31-Redis分布ロック進化史】

9965 ワード

【博文総目録>>>】


Redis分布ロック進化史


ここ2年来、マイクロサービスはますます人気があり、ますます多くの応用が分布式環境に配置されている.分布式環境では、データ整合性は従来から注目され、解決しなければならない問題であり、分布式ロックも広く使用されている技術となり、よく使われる分布式実現方式はRedis、Zookeeperであり、その中でRedisベースの分布式ロックの使用はより広範である.
しかし、仕事とネット上で各バージョンのRedis分布式ロックの実現を見たことがあります.各実現にはいくつかの厳密ではないところがあります.コードに含まれている可能性もあります.分布式ロックを正しく使用できないと、深刻な生産環境の故障を引き起こす可能性があります.本文は主に現在直面している各種分布式ロックとその欠陥を整理しました.適切なRedis分散ロックの選択方法を提案した.

各バージョンのRedis分散ロック

  • V1.0
  • tryLock(){  
        SETNX Key 1
        EXPIRE Key Seconds
    }
    release(){  
      DELETE Key
    }
    

    このバージョンは最も簡単なバージョンであり、出現頻度の高いバージョンでもあるはずです.まず、ロックに期限切れの操作を追加するのは、サービスの再起動や異常によってロックが解放されなくなった後、ロックが解放されないことを避けるためです.
    このシナリオの1つの問題は、1つのRedisリクエストが発行されるたびに、1つ目のコマンドが実行された後に例外が適用されたり、再起動されたりすると、ロックが期限切れにならないことです.1つの改善策は、Luaスクリプト(SETNXとEXPIREの2つのコマンドを含む)を使用することですが、Redisが1つのコマンドのみを実行した後にcrashが発生したり、プライマリ・スレーブ・スイッチが発生したりしても、ロックが期限切れになっていないため、
    もう一つの問題は、多くの学生が分散ロックを解放する過程で、ロックが成功するかどうかにかかわらずfinallyでロックを解放することであり、これはロックの誤った使用であり、この問題は後続のV 3になる.バージョン0で解決.
    ロックが解放されない問題に対する解決策の1つは、GETSETコマンドに基づいて実現される
  • V1.1 GETSET
  • に基づく
    考え方:
  • SETNX(Key,ExpireTime)取得ロック
  • ロックの取得に失敗した場合、GET(Key)が返すタイムスタンプでロックが期限切れかどうかをチェックする
  • .
  • GETSET(Key,ExpireTime)ValueをNewExpireTime
  • に変更
  • GETSETが返す古い値をチェックし、GETが返す値に等しい場合、ロック取得に成功したと判断する
  • .
    注意:このバージョンではEXPIREコマンドを削除し、Valueタイムスタンプ値で期限切れを判断するように変更しました
    質問:
  • ロック競合が高い場合、Valueは常に上書きされますが、ロック
  • を取得するClientは1つもありません.
  • ロックを取得する過程で従来のロックのデータを絶えず修正し、シーンC 1,C 2の競合ロックを想定し、C 1はロックを取得し、C 2ロックはGETSET操作を実行してC 1ロックの期限切れ時間を修正し、C 1が正しくロックを解除しなければ、ロックの期限切れ時間が延長され、他のClientはより長い時間を待つ必要がある
  • .
  • V2.0 SETNX
  • に基づく
    tryLock(){  
        SETNX Key 1 Seconds
    }
    release(){  
      DELETE Key
    }
    

    Redis 2.6.12以降、SETNXは有効期限パラメータを追加し、2つのコマンドが原子性を保証できないという問題を解決しました.しかし、次のシーンを想定します.
  • C 1はロックを取得することに成功し、その後、C 1はGCが待機中または未知の理由でタスクの実行が長すぎるため、最後にロックが失効する前にC 1はロック
  • を自発的に解放しなかった.
  • C 2は、C 1のロックタイムアウト後にロックを取得する実行を開始し、このときC 1とC 2が同時に実行する、繰り返し実行によってデータが一致しない等の不明な状況がある
  • .
  • C 1が先に実行すると、C 2のロックが解放され、このとき、他のC 3プロセスがロック
  • を取得する可能性がある.
    大まかなフローチャート
    問題:
  • C 1の停止によりC 1とC 2の両方がロックされ、同時に実行され、トラフィック実装において間接的な要求はべき乗等性
  • を保証しなければならない.
  • C 1は、C 1に属するロック
  • を解放する.
  • V3.0
  • tryLock(){  
        SETNX Key UnixTimestamp Seconds
    }
    release(){  
        EVAL(
          //LuaScript
          if redis.call("get",KEYS[1]) == ARGV[1] then
              return redis.call("del",KEYS[1])
          else
              return 0
          end
        )
    }
    

    この方式は、Valueをタイムスタンプとして指定する、ロックを解除する際にロックのValueがロックを取得するValueであるか否かをチェックすることにより、V 2を回避する.0バージョンで述べたC 1は、C 2が保持するロックの問題を解放した.また,ロックを解除する際には複数のRedis操作が関与し,Check And Setモデルの同時問題を考慮してLuaスクリプトを用いて同時問題を回避する.
    問題:
    また、分散環境での物理クロックの一貫性が保証されていないため、UnixTimestampの重複問題も存在する可能性がありますが、まれに発生する可能性があります.
  • V3.1
  • tryLock(){  
        SET Key UniqId Seconds
    }
    release(){  
        EVAL(
          //LuaScript
          if redis.call("get",KEYS[1]) == ARGV[1] then
              return redis.call("del",KEYS[1])
          else
              return 0
          end
        )
    }
    

    Redis 2.6.12後のSETは同様に1つのNXパラメータを提供し、SETNXコマンドと同等である.公式文書では、後のバージョンがSETNX、SETEX、PSETEXを削除する可能性があることを注意し、SETコマンドで代替し、もう1つの最適化は、タイムスタンプの代わりに増加した唯一のUniqIdを使用してV 3を回避することである.0クロックの問題について説明します.
    このスキームは現在最良の分散ロックスキームであるが,Redisクラスタ環境で依然として問題がある場合:
    Redisクラスタのデータ同期は非同期であるため、Masterノードがロックを取得した後にデータ同期が完了していない場合、Masterノードcrashが新しいMasterノードでもロックを取得できると仮定し、複数のClientが同時にロックを取得する

    分散型Redisロック:Redlock


    V3.1のバージョンは単一のインスタンスのシーンでのみ安全であり、どのように分布式Redisのロックを実現するかについて、国外の分布式専門家が激しい議論をしたことがある.antirezは分布式ロックアルゴリズムRedlockを提案した.distlockの話題の下でRedlockの詳細な説明を見ることができる.以下はRedlockアルゴリズムの中国語の説明(引用)である.
    N個の独立したRedisノードがあると仮定する
  • 現在の時間(ミリ秒数)を取得します.
  • は、順次、N個のRedisノードに対してロック取得動作を実行する.この取得操作は、ランダム文字列my_を含む前の単一Redisノードに基づく取得ロックの処理と同様であるrandom_valueは、有効期限(例えばPX 30000、すなわちロックの有効時間)も含む.あるRedisノードが使用できないときにアルゴリズムが実行されることを保証するために、このロックを取得する操作には、ロックの有効時間(数十ミリ秒レベル)よりもはるかに小さいタイムアウト時間(time out)がある.クライアントは、あるRedisノードへのロックの取得に失敗した後、すぐに次のRedisノードを試行する必要があります.ここでの失敗には、Redisノードが使用できない場合や、Redisノードのロックが他のクライアントによって保持されている場合など、任意のタイプの失敗が含まれるべきです(注:Redlock原文では、Redisノードが使用できない場合のみが記載されていますが、他の失敗も含まれるべきです).
  • ロックを取得するプロセス全体にどれくらいの時間がかかったかを計算します.計算方法は、現在の時間からステップ1のレコードを減算する時間です.クライアントがほとんどのRedisノード(>=N/2+1)からロックを正常に取得し、ロックを取得するのに消費される時間がロックの有効時間を超えていない場合、クライアントは最終的にロックを取得したと判断する.そうでなければ、最終的にロックの取得に失敗したと考えられます.
  • 最終的にロックを取得することに成功した場合、このロックの有効時間は、最初のロックの有効時間から3ステップ目に計算されたロックの取得に消費された時間を差し引いたものに等しい再計算されるべきである.
  • 最終的なロックの取得に失敗した場合(ロックを取得したRedisノードの数がN/2+1未満であるか、またはロックを取得するプロセス全体で消費された時間がロックの最初の有効時間を超えている可能性がある)、クライアントは直ちにすべてのRedisノードにロックを解除する操作(すなわち、前述のRedis Luaスクリプト)を開始するべきである.
  • ロック解除:すべてのRedisノードに対してロック解除動作
  • を開始する.
    しかしMartin Kleppmannはこのアルゴリズムに疑問を提起し,fencing tokenメカニズムに基づいているべきであることを提案した(リソースを操作するたびにtoken検証が必要である).
  • Redlockは、システムモデル、特に分散クロック整合性の問題において、実際のシーンではクロック不整合とクロックホッピングの問題が存在すると仮定するが、Redlockはtimingベースの分散ロック
  • である.
  • さらにRedlockは自動失効機構に基づいているため、長時間のgc pauseなどの問題によるロックの自動失効は依然として解決されておらず、それによる安全性の問題である.

  • 次にantirezはMartin Kleppmannの疑問に答え、期限切れのメカニズムの合理性と、実際のシーンで停止問題が発生して複数のClientが同時にリソースにアクセスした場合の処理を示した.
    Redlockの問題に対して,Redisに基づく分散ロックは果たして安全なのか,詳細な中国語の説明を与え,Redlockアルゴリズムに存在する問題を解析した.

    まとめ


    SETNXバージョンに基づくRedis単一インスタンス分散ロックも、Redlock分散ロックも、次の特性を保証するために使用されます.
  • セキュリティ:同じ時間に複数のClientが同時にロック
  • を持つことを許可しない.
  • アクティブデッドロック:最終的には、クライアント側のcrashまたはネットワークパーティション(通常はタイムアウトメカニズムに基づく)の許容誤差が発生しても、ロックは、Redisノードの半数以上が利用可能であれば、
  • を正しく取得および解放することができるはずである.
    したがって、分散ロックの開発または使用中に安全性と活性を保証し、予測不可能な結果を回避しなければならない.
    また、各バージョンの分散ロックには、ロックの使用上、ロックの実用的なシーンに対して適切なロックを選択する問題があります.通常、ロックの使用シーンには次のような問題があります.
    Efficiency(効率):操作を完了するために1つのClientだけが必要で、繰り返し実行する必要はありません.これは緩やかな分布式ロックで、ロックの活性を保証するだけでいいです.
    Correctness(正確性):複数のClientは厳格な反発性を保証し、同時にロックを保持したり、同じリソースを同時に操作したりすることは許されません.このようなシーンでは、ロックの選択と使用がより厳しく、ビジネスコード上でできるだけべき乗などを行う必要があります.
    Redis分散ロックの実装にはまだ多くの問題が解決を待っています.これらの問題を認識し、Redis分散ロックを正しく実装し、作業中に分散ロックを合理的に選択し、正しく使用する方法を明確にする必要があります.