2つのノード間の相互接続中に分散デッドロックを回避するにはどうすればよいですか?


11

2つのピアノードがあるとします。最初のノードは2番目のノードに接続要求を送信できますが、2番目のノードは最初のノードに接続要求を送信できます。2つのノード間の二重接続を回避する方法は?この問題を解決するには、インバウンドまたはアウトバウンドTCP接続を作成するために実行される操作を連続して行うだけで十分です。

つまり、各ノードは、着信接続と発信接続の両方で、新しい接続作成操作を順番に処理する必要があります。この方法では、接続されたノードのリストを維持し、ノードからの新しい着信接続を受け入れる前、またはノードに接続要求を送信する前に、このノードがリストに既に存在するかどうかを確認するだけで十分です。

接続を作成する操作を順番に行うには、接続ノードのリストをロックするだけで十分です。実際、新しい接続ごとに、新しい接続ノードの識別子がこのリストに追加されます。このアプローチは、原因となることができればしかし、私は疑問に思う分散デッドロックを

  • 最初のノードは、2番目のノードに接続要求を送信できます。
  • 2番目のノードは最初のノードに接続要求を送信できます。
  • 2つの接続要求が非同期でないと仮定すると、両方のノードが着信接続要求をロックします。

どうすればこの問題を解決できますか?

更新:ただし、新しい(着信または発信)接続が作成されるたびにリストをロックする必要があります。他のスレッドがこのリストにアクセスする可能性があるため、デッドロックの問題が残ることになります。

更新2:あなたのアドバイスに基づいて、ログイン要求の相互受け入れを防ぐアルゴリズムを書きました。各ノードはピアであるため、新しい接続要求を送信するクライアントルーチンと、着信接続を受け入れるサーバールーチンを持つことができます

ClientSideLoginRoutine() {
    for each (address in cache) {
        lock (neighbors_table) {
            if (neighbors_table.contains(address)) {
                // there is already a neighbor with the same address
                continue;
            }
            neighbors_table.add(address, status: CONNECTING);

        } // end lock

        // ...
        // The node tries to establish a TCP connection with the remote address
        // and perform the login procedure by sending its listening address (IP and port).
        boolean login_result = // ...
        // ...

        if (login_result)
            lock (neighbors_table)
                neighbors_table.add(address, status: CONNECTED);

    } // end for
}

ServerSideLoginRoutine(remoteListeningAddress) {
    // ...
    // initialization of data structures needed for communication (queues, etc)
    // ...

    lock(neighbors_table) {
        if(neighbors_table.contains(remoteAddress) && its status is CONNECTING) {
            // In this case, the client-side on the same node has already
            // initiated the procedure of logging in to the remote node.

            if (myListeningAddress < remoteListeningAddress) {
                refusesLogin();
                return;
            }
        }
        neighbors_table.add(remoteListeningAddress, status: CONNECTED);

    } // end lock
}

例: ノードAのIP:ポートはA:7001-ノードBのIP:ポートはB:8001です。

ノードAがログイン要求をノードB:8001に送信したとします。この場合、ノードAは、独自のリスニングアドレス(A:7001)を送信して送信することにより、ログインルーチンを呼び出します。結果として、ノードAのneighbors_tableにはリモートノードのアドレス(B:8001)が含まれます。このアドレスはCONNECTING状態に関連付けられています。ノードAは、ノードBがログイン要求を受け入れるか拒否するのを待っています。

一方、ノードBもノードAのアドレスに接続要求を送信した可能性があり(A:7001)、ノードAはノードBの要求を処理している可能性があります。したがって、ノードBのneighbors_tableにはリモートのアドレスが含まれますノード(A:7001):このアドレスはCONNECTING状態に関連付けられています。ノードBは、ノードAがログイン要求を受け入れるか拒否するのを待っています。

ノードAのサーバー側がB:8001からの要求を拒否する場合、ノードBのサーバー側がA:7001からの要求を受け入れることを確認する必要があります。同様に、ノードBのサーバー側がA:7001からの要求を拒否する場合、ノードAのサーバー側がB:8001からの要求を受け入れることを確認する必要があります。

「小さなアドレス」ルールに従って、この場合、ノードAはノードBによるログイン要求を拒否しますが、ノードBはノードAからの要求を受け入れます。

あれについてどう思う?


これらの種類のアルゴリズムは、分析と証明が非常に困難です。ただし、分散コンピューティングの非常に多くの側面に精通している研究者がいます。Leslie Lamportの出版物ページをご覧ください: research.microsoft.com/en-us/um/people/lamport/pubs/pubs.html
DeveloperDon

回答:


3

「楽観的な」アプローチを試すことができます。最初に接続し、同時相互接続を検出した場合は切断します。この方法では、新しい接続を確立している間、接続要求を排除する必要はありません。着信接続が確立されたら、リストをロックし、同じホストへの発信接続があるかどうかを確認します。その場合、ホストのアドレスを確認してください。自分よりも小さい場合は、発信接続を切断します。そうでない場合は、着信を切断します。ピアホストは反対の動作をします。アドレスの比較方法が異なり、2つの接続のいずれかがドロップされるためです。このアプローチにより、接続の再試行を回避でき、単位時間あたりにより多くの接続要求を受け入れることができます。


ただし、新しい(着信または発信)接続が作成されるたびにリストをロックする必要があります。他のスレッドがこのリストにアクセスする可能性があるため、デッドロックの問題が残ることになります。
enzom83

@ enzom83いいえ、ピアはロックを必要とする操作を完了するのを待つ必要がないため、このスキームではデッドロックは不可能です。ミューテックスは、リストの内部を保護するためのものです。クリティカルセクション内で他のリソースを待機する必要がないため、取得すると、既知の時間内に終了します。
dasblinkenlight

ただし、接続要求が非同期ない場合、およびクリティカルセクション内で実行されると、デッドロックが発生する可能性があります。この場合、ノードは、接続要求が受け入れられるまでミューテックスを終了できません。それ以外の場合、ノードを追加(または削除)するためだけにリストのロックを実行する必要があります。この場合、重複した接続などを確認する必要があります。最後に、別のオプションは非同期接続要求を送信します。
enzom83

1
@ enzom83クリティカルセクション内から接続を要求しない限り、分散デッドロックは発生しません。それは楽観的なアプローチの背後にある考え方です-ノードを追加または削除するためだけにリストでロックを実行し、ノードを追加するときに相互接続を見つけた場合は、「より小さいアドレス」を使用してクリティカルセクションを離れてから切断しますルール(回答から)。
-dasblinkenlight

4

あるノードが別のノードにリクエストを送信するとき、ランダムな64ビット整数を含めることができます。ノードが接続要求を取得するときに、すでに自分のノードのいずれかを送信している場合、ノードは最も高い番号のノードを保持し、他のノードをドロップします。例えば:

時間1:AはBへの接続を試み、番号123を送信します。

時間2:BはAへの接続を試み、番号234を送信します。

時間3:BはAの要求を受け取ります。B自身のリクエストの数が多いため、Aのリクエストは拒否されます。

時間4:AはBの要求を受け取ります。Bのリクエストの方が大きいため、Aはそれを受け入れ、独自のリクエストをドロップします。

乱数を生成するには、乱数ジェネレーターのデフォルトのシードを使用するのではなく、乱数ジェネレーターに/ dev / urandomをシードするようにしてください。これは、多くの場合、壁時計時間に基づいています。2つのノードが無視できない可能性があります同じ種を取得します。

乱数の代わりに、事前に番号を配布する(つまり、1からnまでのすべてのマシンに番号を付ける)か、MACアドレスを使用するか、衝突の確率が非常に小さい番号を見つける他の方法を使用することもできます。無視できます。


3

私が理解すれば、あなたが回避しようとしている問題は次のようになります:

  • Node1はノード2からの接続を要求します
  • Node1は接続リストをロックします
  • Node2はノード1からの接続を要求します
  • Node2は接続リストをロックします
  • Node2はnode1から接続要求を受信し、リストがロックされているため拒否します
  • Node1はnode2から接続要求を受信し、リストがロックされているため拒否します
  • どちらも互いに接続することはありません。

これに対処するいくつかの異なる方法を考えることができます。

  1. ノードに接続しようとして、「list locked」メッセージでリクエストが拒否された場合は、ランダムなミリ秒数待ってから再試行してください。(ランダム性は重要です。これにより、両方がまったく同じ時間待機して、同じ問題を無限に繰り返す可能性が非常に低くなります。)
  2. 両方のシステムの時計をタイムサーバーと同期し、接続要求とともにタイムスタンプを送信します。現在接続しようとしているノードから接続要求が着信した場合、両方のノードは、最初に接続しようとした方が勝ち、もう一方の接続が閉じられることに同意します。

問題は、要求を受信するノードが接続を拒否できないことですが、リストのロックを取得するために無期限に待機することです。
enzom83
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.