Bagaimana saya bisa menghindari kebuntuan terdistribusi selama hubungan timbal balik antara dua node?

11

Misalkan kita memiliki dua node peer: node pertama dapat mengirim permintaan koneksi ke yang kedua, tetapi juga yang kedua dapat mengirim permintaan koneksi ke yang pertama. Bagaimana cara menghindari koneksi ganda antara kedua node? Untuk mengatasi masalah ini, cukup berurutan untuk operasi yang dilakukan untuk membuat koneksi TCP masuk atau keluar.

Ini berarti bahwa setiap node harus memproses secara berurutan setiap operasi pembuatan koneksi baru, baik untuk koneksi masuk maupun untuk koneksi keluar. Dengan cara ini, mempertahankan daftar node yang terhubung, sebelum menerima koneksi masuk baru dari suatu node atau sebelum mengirim permintaan koneksi ke sebuah node, itu akan cukup untuk memeriksa apakah node ini sudah ada dalam daftar.

Untuk membuat sekuensial operasi pembuatan koneksi, cukup dengan melakukan kunci pada daftar node yang terhubung: pada kenyataannya, untuk setiap koneksi baru, pengidentifikasi dari node yang terhubung baru ditambahkan ke daftar ini. Namun, saya bertanya-tanya apakah pendekatan ini dapat menyebabkan kebuntuan terdistribusi :

  • simpul pertama dapat mengirim permintaan koneksi ke simpul kedua;
  • simpul kedua bisa mengirim permintaan koneksi ke yang pertama;
  • dengan asumsi bahwa dua permintaan koneksi tidak sinkron, kedua node mengunci setiap permintaan koneksi yang masuk.

Bagaimana saya bisa menyelesaikan masalah ini?

UPDATE: Namun, saya masih harus mengunci daftar setiap kali koneksi baru (masuk atau keluar) dibuat, karena utas lain dapat mengakses daftar ini, maka masalah kebuntuan masih akan tetap ada.

UPDATE 2: Berdasarkan saran Anda, saya menulis sebuah algoritma untuk mencegah saling menerima permintaan login. Karena setiap node adalah peer, itu bisa memiliki rutin klien untuk mengirim permintaan koneksi baru dan rutin server untuk menerima koneksi masuk.

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
}

Contoh: IP: port node A adalah A: 7001 - IP: port node B adalah B: 8001.

Misalkan node A telah mengirim permintaan login ke node B: 8001. Dalam hal ini, simpul A memanggil rutin masuk dengan mengirim dengan mengirimkan alamat pendengarannya sendiri (A: 7001). Sebagai konsekuensinya, neighbor_table dari node A berisi alamat dari remote node (B: 8001): alamat ini dikaitkan dengan keadaan CONNECTING. Node A sedang menunggu node B menerima atau menolak permintaan login.

Sementara itu, simpul B juga mungkin telah mengirim permintaan koneksi ke alamat simpul A (A: 7001), kemudian simpul A dapat memproses permintaan dari simpul B. Jadi, neighbor_table dari simpul B berisi alamat dari jarak jauh simpul (A: 7001): alamat ini dikaitkan dengan keadaan CONNECTING. Node B sedang menunggu simpul A menerima atau menolak permintaan masuk.

Jika sisi server dari simpul A menolak permintaan dari B: 8001, maka saya harus yakin bahwa sisi server dari simpul B akan menerima permintaan dari A: 7001. Demikian pula, jika sisi server dari simpul B menolak permintaan dari A: 7001, maka saya harus yakin bahwa sisi server dari simpul A akan menerima permintaan dari B: 8001.

Menurut aturan "alamat kecil" , dalam hal ini simpul A akan menolak permintaan masuk oleh simpul B, sedangkan simpul B akan menerima permintaan dari simpul A.

Apa pendapatmu tentang itu?

enzom83
sumber
Algoritma semacam ini cukup sulit untuk dianalisis dan dibuktikan. Namun, ada seorang peneliti yang ahli dalam banyak aspek komputasi terdistribusi. Lihatlah halaman publikasi Leslie Lamport di: research.microsoft.com/en-us/um/people/lamport/pubs/pubs.html
DeveloperDon

Jawaban:

3

Anda dapat mencoba pendekatan "optimis": sambungkan terlebih dahulu, lalu putuskan sambungan jika Anda mendeteksi koneksi bersama secara bersamaan. Dengan cara ini Anda tidak perlu menahan permintaan koneksi saat Anda membuat koneksi baru: ketika koneksi masuk dibuat, kunci daftar, dan lihat apakah Anda memiliki koneksi keluar ke host yang sama. Jika ya, periksa alamat host. Jika lebih kecil dari milik Anda, putuskan koneksi keluar Anda; jika tidak, putuskan sambungan yang masuk. Rekan host Anda akan melakukan yang sebaliknya, karena alamat akan membandingkan secara berbeda, dan salah satu dari dua koneksi akan terputus. Pendekatan ini memungkinkan Anda menghindari mencoba kembali koneksi Anda, dan berpotensi membantu Anda menerima lebih banyak permintaan koneksi per unit waktu.

dasblinkenlight
sumber
Namun, saya masih harus mengunci daftar setiap kali koneksi baru (masuk atau keluar) dibuat, karena utas lain dapat mengakses daftar ini, maka masalah kebuntuan masih tetap ada.
enzom83
@ enzom83 Tidak, jalan buntu tidak dimungkinkan dalam skema ini, karena rekan tidak perlu menunggu Anda untuk menyelesaikan operasi yang memerlukan penguncian. Mutex adalah untuk melindungi bagian dalam daftar Anda; setelah Anda memperolehnya, Anda pergi dalam jumlah waktu yang diketahui, karena Anda tidak perlu menunggu sumber daya lain di dalam bagian kritis.
dasblinkenlight
Ok, tetapi kebuntuan dapat terjadi jika permintaan koneksi tidak sinkron , dan jika dilakukan di dalam bagian kritis: dalam kasus ini, node tidak dapat meninggalkan mutex sampai permintaan koneksi telah diterima. Kalau tidak, saya harus melakukan kunci pada daftar hanya untuk menambah (atau menghapus) sebuah node: dalam hal ini saya harus memeriksa koneksi duplikat, dll. Akhirnya, opsi lain adalah mengirim permintaan koneksi asinkron.
enzom83
1
@ enzom83 Selama Anda tidak meminta koneksi dari dalam bagian kritis, Anda tidak akan mendapatkan kebuntuan yang didistribusikan. Itulah ide di balik pendekatan optimis - Anda melakukan kunci pada daftar hanya untuk menambah atau menghapus node, dan jika Anda menemukan koneksi timbal balik saat menambahkan node, Anda memutuskannya setelah meninggalkan bagian kritis, menggunakan "alamat lebih kecil" aturan (dari jawaban).
dasblinkenlight
4

Ketika satu node mengirim permintaan ke yang lain, itu bisa menyertakan integer 64-bit acak. Ketika sebuah node mendapat permintaan koneksi, jika sudah mengirimkan salah satu dari itu sendiri, itu membuat satu dengan nomor tertinggi dan menjatuhkan yang lain. Sebagai contoh:

Waktu 1: A mencoba terhubung ke B, mengirimkan nomor 123.

Waktu 2: B mencoba untuk terhubung ke A, mengirimkan nomor 234.

Waktu 3: B menerima permintaan A. Karena permintaan B sendiri memiliki angka yang lebih tinggi, permintaan B menolak.

Waktu 4: A menerima permintaan B. Karena permintaan B memiliki angka yang lebih tinggi, A menerimanya dan menjatuhkan permintaannya sendiri.

Untuk menghasilkan angka acak, pastikan Anda menaburkan generator nomor acak Anda dengan / dev / urandom, daripada menggunakan seeding default generator nomor acak Anda, yang sering didasarkan pada waktu jam dinding: ada peluang yang tidak dapat diabaikan bahwa dua node akan mendapatkan benih yang sama.

Alih-alih angka acak, Anda juga dapat mendistribusikan angka sebelumnya (yaitu hanya memberi nomor semua mesin dari 1 ke n), atau menggunakan alamat MAC, atau cara lain untuk menemukan nomor di mana probabilitas tabrakan sangat kecil sehingga menjadi dapat diabaikan.

Martin C. Martin
sumber
3

Jika saya mengerti, masalah yang ingin Anda hindari adalah seperti ini:

  • Node1 meminta koneksi dari node 2
  • Node1 mengunci daftar koneksi
  • Node2 meminta koneksi dari node 1
  • Node2 mengunci daftar koneksi
  • Node2 menerima permintaan koneksi dari node1, menolak karena daftar dikunci
  • Node1 menerima permintaan koneksi dari node2, menolak karena daftar dikunci
  • Tidak satu pun akhirnya terhubung satu sama lain.

Saya dapat memikirkan beberapa cara berbeda untuk menangani hal ini.

  1. Jika Anda mencoba untuk terhubung ke sebuah simpul, dan itu menolak permintaan Anda dengan pesan "daftar terkunci", tunggu beberapa milidetik acak sebelum mencoba lagi. (Keacakan sangat penting. Ini membuat jauh lebih kecil kemungkinan bahwa keduanya akan menunggu untuk jumlah waktu yang sama persis dan mengulangi masalah yang sama ad infinitum .)
  2. Sinkronkan kedua jam sistem dengan server waktu, dan kirim stempel waktu bersama dengan permintaan koneksi. Jika permintaan koneksi datang dari sebuah node yang saat ini Anda coba sambungkan, maka kedua node setuju bahwa yang mana yang dicoba untuk terhubung akan menang, dan koneksi lainnya akan ditutup.
Mason Wheeler
sumber
Masalahnya adalah bahwa node yang menerima permintaan tidak dapat menolak koneksi, tetapi akan tetap menunggu tanpa batas waktu untuk mendapatkan kunci pada daftar.
enzom83