細品redis分布式ロック

5295 ワード

背景
すでに2つの節のredisの高性能データ構造を書いたので、クリックして見て、今日は味を変えて、今日はredisの分布式システムでの応用を見て、redisを使って分布式ロックをして、これはいつもの問題と言える.
redis分散ロック
分散ロックによる問題の解決
ロックといえば、最初の反応はスレッドブロックであり、ここで注意しなければならないのは、1つのサービス(プロセス)のスレッド間だけでなく、複数のサービス間の同時セキュリティの問題であり、複数のプロセス(この2つのプロセス間はそれぞれ2つのサービス上にある)間の同時問題であることである.したがって、ここでスレッド間のロックを使用すると、(reentrantLock,Sychronized,CyclicBarrier,CountDownlotch,Semaphore,volatile)のようなJUCパッケージやJDKが提供するロックメカニズムのような問題を解決することはできません.同じプロセスの異なるスレッド間の同時問題しか処理できません.したがって、異なるプロセスを決定するために、異なるserver時間の同時セキュリティの問題がredis分散ロックを作成します.ここでは、分散ロックとスレッドロック(このように呼ぶのが適切かどうか、あるいはオブジェクトロック)の違いを区別するのが主です.
図に基づいて理解してください.2つのクライアント操作データベースの同じデータを変更します.
インプリメンテーション
  • 分散ロックの本質は、異なるサービス間または同じサービス間のスレッドがredisの中でピットを奪い合い、1つのスレッドがこのピットを占有し、ドアがロックされると、他のスレッドは放棄または再試行して待たなければならないということです.スレッドがピットを占有して終了するとdel命令を使用してピット位置を解放し、すなわちトイレのドアが開いている状態になる.実際の使用方法は、このピットビットにnameを設定することであり、この値の説明があれば、占有ピットは一般的にsetnx(set if not exists)命令を使用し、1つのクライアントのみがピットを占有することを許可する.先に占拠して、使い終わったら、delコマンドを呼び出して茅坑を解放します.
  • //      :         ,     ,          ,    
    > setnx lock:codehole true
    OK
    ... do something critical ...
    > del lock:codehole
    (integer) 1
    
  • しかし、上記のスキームには問題があります.このスレッドが異常に実行され、delリリースピットコマンドが有効にならない場合、問題があります.しかし、javaでfinallyブロックを使ってピットを解放すると言われています.これは確かに一つの方法ですが、もしこの利用しているサービスが直接切られたらどうしますか?
  • //   
    try{
                
            }catch (Exception e){
                e.printStackTrace();
            }finally {
            //         ,    
                delKey;
            }
        }
    
  • それはこのロック(ピット)に時間制限を与え、時間になると自動的に解放されなければなりません.では、ロックを取得してからタイミング時間を設定する方法として、
  • がある.
    > setnx lock:codehole true
    OK
    > expire lock:codehole 5
    ... do something critical ...
    > del lock:codehole
    (integer) 1
    
  • この方式は操作過程が原子性ではない(すべて実行するか、すべて実行しないか.実行の半分は現れない)ので、ロックを受け取った後も現れる.その後、このロックのタイムアウト時間が異常になったり、サービスが停止したりする問題が発生します.これで最初の質問に戻ります.
  • では、このようにして何か良い方法があると思いますか?原子性といえば、物事の四大特性の一つではないか.では、redisのものを使ってこの問題を処理しましょう.命令setnxとexpireを同じものに入れて実行します.redis 2.8でこの問題を処理しました.それは、この2つのコマンドが一緒に実行できることです.これも分布式ロックを使用する意味です.
  • > set lock:codehole true ex 5 nx OK ... do something critical ... 
    > del lock:codehole
    
  • スレッド実行時間が長すぎてロックがタイムアウトしたらどうしますか?

  • 分散ロックの期限切れ
  • Redisの分散ロックはタイムアウトの問題を解決することができず、ロックとロックの解放の間の論理が長すぎてロックのタイムアウトの制限を超えた場合、問題が発生します.このときロックが期限切れになったため、2番目のスレッドはこのロックを再保持したが、1番目のスレッドがビジネスロジックを実行し終わると、ロックが解放され、3番目のスレッドは2番目のスレッドロジックが実行される間にロックを取得した.
  • まず、このような問題を回避しなければなりません.発生した原因は、ビジネスロジックの実行時間が長すぎることです.それでは、使用するときにできるだけ時間が短いものに使用し、ビジネスに自慢のサービス呼び出しがあることを避けなければなりません.また、できるだけ合理的な期限切れを設定し、ビジネスの実行時間をできるだけ大きくしなければなりません.
  • さらに安全な方法は、ロックごとに値を置くことです.delを行うときに、前に置いたvalueかどうかを比較することができます.これは楽観的なロックの意味があります.compare and swapを比較して置き換えます.しかし、マッチングとkey削除は原子的な操作ではなく、心配しています.redisはスクリプトの実行方法を提供し、スクリプトは原子性を有し、luaスクリプトを使用して原子性の目標を達成する.(アリクラウドのredis shardingモードを使用する場合は注意してください.彼らshardingモードはluaスクリプトのサポートに問題があります.私が踏んだ穴です.redissionのRmapCacheを使用する場合、ソースコードにluaスクリプトがあり、最終的な実行に失敗します)
  • # delifequals
    if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
    else
    return 0
    end
    

    さいにゅうせい
  • 再入性とは、ロックを保持している場合に再ロックを要求し、再ロックを要求することができる場合、このロックは再入可能であることを意味する.たとえばjavaのreentrantLockとsychronizedは再ロック可能です.
  • 彼らが実現した大まかな原理は、楽観的なロックによってロック状態を修正することであり、同じスレッドidであれば変更に成功し、ロックを取得することができ、同じスレッドIDでなければロックアップグレードに戻る.では、redisを使用してどのように実現しますか?同じ理屈ですね.このスレッドIDまたはこのタスクIDを、スレッド次元であってもタスク次元であってもよい.再入力可能にします.

  • ミドルウェアの使用
  • jedis(使用時、接続プール回収に問題があり、自分のコードに問題があるはずで、最後に解決しなかった)
  • redission(redssionを交換して接続プールを回収する問題は解決しましたが、元のredis単機版をshardingモードにアップグレードするとluaスクリプトの問題が発生しました)
  • 上は2つのredisの中間部品が使い心地がよく、括弧の中は私が踏んだ穴です.
    実例の共有
  • は、マイクロサービス間呼び出しを必要とし、インタフェースを外部に露出させ、インタフェースが短時間で同じタスクIDによって繰り返し呼び出されることを防止するため(前のタスクの実行が完了することを保証する).1つのデータを同時に操作することを防止します.
  • ソリューション:redis分散ロックを使用し、taskIdをロックとして使用します.有効期限を2 s、待機時間を2秒に設定します.
  • 2 sとは、業務実行の最長時間が2 sであることを意味する.実行が終わらないうちに釈放する.
  • 3 sとは、ロックタイムアウトが私が待っているこのタスクは必ず実行できるという意味です.使用しているのは、redssionパッケージ:
  •     @Override
        public Response discernCommon(DiscernCallBackResultTo discernCallBackResultTo) {
            //    ,     TSKID         ,     3 ,     2 
            boolean lock = RedissonLockUtils.tryLock(discernCallBackResultTo.getTaskId(), 2, 3);
            if (lock) {
                try {
                    dealDiscernResult(discernCallBackResultTo);
                } catch (Exception e) {
                    LOGGER.error(e.getMessage(), Coder.of(ErrorCode.DISCERN_DEAL_EXCEPTION), e);
                } finally {
                 RedissonLockUtils.unlock(discernCallBackResultTo.getTaskId());
                }
            } else {
                EventMessage confilct = new EventMessage(EventEnum.EVENT_CONFILC_TASKID.getEventId())
                        .addExtValue("taskId", discernCallBackResultTo.getTaskId());
                Tracker.getInstance().record(eventMessage);
            }
            return Response.ok("    !");
        }
    

    まとめ
  • redis分布式ロックの実現と進化
  • redisロックが期限切れになったため、ロック解除エラーが発生し、タスクを追加することで、
  • を削除することと比較する.
  • redis分布式ロックの再入可能ロックの実現は、楽観的ロック方式により、そのスレッドiDまたはタスクIDを再入可能に設計する.

  • リファレンス
  • 『redis極度深寒』
  • http://ifeve.com/?x=34&y=9&s=%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81