分布式ロックの原理といくつかの実現方式

5997 ワード

ぶんさんロック
通常のプロセス・ロックの呼び出し元は、そのプロセス内のみです.(またはそのプロセスのスレッド内)ため、リソースの使用調整が容易になります.分散環境では、異なるマシンの異なるプロセスが同じリソースを使用/競合します.では、どのようにリソースの使用を調整しますか?この場合、同じ時点で1つのプロセスしかそのリソースを占有できないように、分散ロックが進行間の調整を行う必要があります.
分散ロックは、分散システム間の共有リソースへの同期アクセスを制御する方法です.分散システムでは、動作を調整する必要があることが多い.異なるシステムまたは同じシステムの異なるホスト間で1つまたは複数のリソースが共有されている場合、これらのリソースにアクセスするときは、互いに干渉しないように反発して一貫性を保証する必要があります.この場合、分散ロックを使用する必要があります.『ウィキペディア』
分散ロックの特徴
  • 原子性:同じ時点で、1つの機械の1つのスレッドしかロックされません.
  • 再入力可能:同じオブジェクト(スレッド、クラスなど)は、デッドロックが発生することなく、そのロックを繰り返し、再帰的に呼び出すことができる;
  • .
  • ブロック可能:ロックが取得されない前に、ロックが取得されるまで待つしかブロックできない.
  • 高可用性:プログラムの故障、機械の破損が発生しても、ロックは取得され、解放されます.
  • 高性能:ロックの取得、解放の操作消費が小さい.

  • 上記の特徴と要求は、業務ニーズ、シーンによって取捨選択される.
    一般的なデータベース/キャッシュに基づいて実装される分散ロックについて説明します.
    データベース#データベース#
    データベースが分散ロックを実装するには、一般的に2つの方法があります.一意のインデックスまたはプライマリ・キーを使用します.データベースに付属するロックを使用して行います.
    ユニークなインデックスまたはプライマリ・キー
    表の定義例:
    create table distributed_lock(
        id int not null auto_increment primay key,
        method varchar(255) not null defult '' comment '   ,    ,           ',
        expire timestamp not null default current_timestamp()+60 comment '    ,       ',
        unique key(method)
    );
  • method一意インデックスは、反発呼び出しが必要なメソッド
  • を表す.
  • expireは、タグロックの最長保持時間に使用される.定期的にexpireをチェックし、nowより大きいレコードを削除し、呼び出し者がロックを解除しない(例えば、呼び出し者が意外に終了し、ロックを解除しない)
  • を長時間解放しないようにする必要がある.
    ロック操作:
    insert into distributed_lock(method, expire) values("the name of your method", current_timestamp+30s);

    ロック解除操作
    delete from distributed_lock where method="the name of your method"
    // or
    delete from distributed_lock where id=$id

    上記のような一意のインデックスまたはプライマリ・キーに基づく実装メカニズムの特徴は以下の通りである.
  • 理解と使用、デバッグが容易である.
  • データベースは、単一のポイントの場合、ダウンタイムがすべてのロック情報を失う場合.(プライマリ・データベース形式で可用性を向上できる)
  • 非再入;(belong_toなどのデータベースにフィールドを追加して解決することができ、ロックを取得すると、呼び出し元がロックを取得したことを発見するとすぐに成功する)
  • は非ブロックロックではありません.つまり、取得しようとしたが失敗した場合、エラーが直接返されます.(ポーリングをブロックすることで解決可能)
  • 性能:従来のデータベースは同時量が高い時にこの問題(例えば淘宝の注文シーン、一致性ハッシュ?)を解決すれば?思考の中で
  • データベースに基づいてロックを実装するには、別の方法があります.排他ロックです.ここではしばらく紹介しません.
    メモリデータベースRedis
    方式一
    setnx:ロックが存在しない場合はロックに成功し、そうでない場合はfalseに戻り、ロックの取得に失敗します.redis障害を防止するために、expireを増やしてロックの最大保持時間を設定できます.呼び出し者がロックを解除しないことを防止します(例えば、呼び出し者がロックを解除せずに予期せずに終了するなど).
    setnx method user // user    ,       
    expire method 30

    この方法の欠点はsetnxとexpireの原子を保証できないことである.想像してみてください.setnxが成功した後、expireを設定する前に、呼び出し者が意外に終了してロックを解放できないと、ロックが正しく解放されず、デッドロック現象になります.このため、次のようなコマンドを実行できます.
    setex method 30

    setexはredis 2である.6で提供される機能.ロック解除操作は、ロックがタイムアウトしたかどうかを判断し、タイムアウトしていない場合はロックを削除し、タイムアウトしている場合は処理しません(他のスレッドのロックの削除を防止します).
    方法2
  • スレッド/プロセスA setnx、keyはmethod、値はタイムアウトのタイムスタンプ(t 1)であり、trueを返すとロックが得られる.//このステップはロックの初期化と理解できますか?
  • スレッドBはgetコマンドでt 1を取得し、現在のタイムスタンプと比較する、タイムアウトしているか否かを判断し、falseがタイムアウトしていない場合、ステップ3
  • を実行する.
  • 新しいタイムアウト時間t 2を計算し、「getset method t 2」コマンドを使用してt 3を返します(この値は他のスレッドが変更された可能性があります).t 1=t 3の場合、t 1!=t 3は、ロックが他のスレッドによって取得する
  • を示す.
  • ロックを取得した後、業務ロジックを処理した後、ロックがタイムアウトかどうかを判断し、タイムアウトがなければロックを削除し、タイムアウトがあれば処理(他のスレッドのロックの削除を防止する)しない
  • .
    この方法は再入力できないが,再入力を必要としないシーンに対してもこのような実装方法は可能であることがわかる.
    方法3
    可用性を向上させるために、redisの著者らは、5つ以上のredisノードを使用することを提案し、上述の方法の1つを使用してロックを取得/解放し、3つ以上のノードのロックを成功に取得すると、ロックを成功に保持すると考えられる.5つのノードのうち3つ以上を取得しなければならないため、取得ロックの競合が発生する可能性があります.つまり、みんなが1-2個のロックを取得した結果、誰もロックを取得できなくなり、この場合はランダムにしばらく待ってからロックを再試行することができます.
    zookeeper
    zookeeperの内部構造はファイルシステムに似ており、同じディレクトリの下のファイルは同名ではありません.つまり、ファイル(ノードとも呼ばれます)の作成が原子的な操作であることを保証します.
    zookeeperデータモデル:
  • 永続ノード:ノードが作成されると、セッションが無効になるために
  • が消えることはありません.
  • テンポラリノード:永続ノードとは逆に、クライアント接続が無効になった場合は、すぐにノードを削除します.(タイムアウト制御としてもよい)
  • シーケンスノード:上記の2つのノードの特性と同様に、このようなノードの作成を指定すると、zkはノード名の後に自動的にデジタル接尾辞を追加し、インクリメントします.
  • モニタ(watcher):ノードを作成すると、そのノードのモニタを登録することができ、ノードの状態が変化するとwatchがトリガーされると、ZooKeeperはクライアントに通知を送信し、watchは1回しかトリガーできないため、通知を送信します.
  • .
    zookeeperのこれらの特性に基づいて、これらの特性を利用して分散ロックを実現する方法を見て、まずロックディレクトリlockを作成します.
    ロックの取得:
  • lockディレクトリに新しいノードを作成します.タイプは一時順序ノードで、ノード名はmethod_です.xx,xxはノードのシーケンス番号である.
  • lockディレクトリの下にxxより小さいノードがあるかどうかを確認し、ない場合、ノードが正常に取得され、返されます.そうしない場合、リスナーを使用してlockディレクトリの下のシーケンス番号がxxより小さいノードの変更をリスニングします.
  • lockディレクトリの変更通知を受信し、変更通知を受信すると成功する.

  • ロックの解除:
  • 削除ノードmethod_xx

  • etcd
    etcdは、オープンソースで分散されたキー値対データストレージシステムであり、共有構成、サービスの登録、発見を提供する.etcdはzookeeperに比べて軽量級システムであり,両者の整合性プロトコルも同様であり,etcd設計当初からサービス登録に対して物事メカニズムもあったため,etcdベースの分散ロックがより簡単であった.
    etcdプロパティ:
  • etcd v 3はlease(リース)の概念を導入し、concurrencyパッケージはleaseに基づいてsessionをカプセル化し、各クライアントには独自のleaseがあり、つまり各クライアントには唯一の64ビット整形値がある.leaseは期限切れ時間を設定することができる.
  • etcdv 3で新しく導入されたマルチキー条件トランザクションは、v 2のCompare-And-put操作に代わっています.etcdv 3のマルチキー条件トランザクションの意味はC言語の3つの演算子に似ています:condition?action_1:action_2
  • etcdストレージを変更するたびにこのシーケンス番号が割り当てられ、v 2ではindexと呼ばれ、createRevisionはこのkeyの作成時に割り当てられるシーケンス番号を表す.keyが存在しない場合、createRivisionは0です.zookeeperに似たノード番号.
  • //     key createRevision
    cmp := v3.Compare(v3.CreateRevision(method), "=", 0)
    //     key
    put := v3.OpPut(method, "", v3.WithLease(lease_id))
    //     key
    get := v3.OpGet(method)
    //   revision 0,   ,    
    resp, err := client.Txn(ctx).If(cmp).Then(put).Else(get).Commit()
    if err != nil {
        return err
    }
    //      revision
    myRev = resp.Header.Revision
    //     ,   else    ,    revision
    if !resp.Succeeded {
        myRev = resp.Responses[0].GetResponseRange().Kvs[0].CreateRevision
    }
    ownerKey := resp.Responses[1].GetResponseRange().Kvs
        if len(ownerKey) == 0 || ownerKey[0].CreateRevision == myRev {
                 
    }
    err = waitDeletes(ctx, client, m.pfx, myRev-1)
    if err!=nil{
        
    }
      

    上記コードはgithubから来る.com/etcd-io/etcd
    ロックの取得:
  • leaseに基づいてロックを取得し、keyはmethodであり、取得に成功すると
  • に戻る.
  • 取得に失敗した場合、keyの変化を傍受し、keyが削除されたことを傍受した後、ロックの取得を再試行する.

  • ロックの解除:
  • 業務ロジックを処理し、ロック
  • を削除する.
    Ref
  • 分散ロック
  • について