Làm thế nào tôi có thể tránh một bế tắc phân tán trong một kết nối lẫn nhau giữa hai nút?


11

Giả sử chúng ta có hai nút ngang hàng: nút đầu tiên có thể gửi yêu cầu kết nối đến nút thứ hai, nhưng nút thứ hai có thể gửi yêu cầu kết nối đến nút đầu tiên. Làm thế nào để tránh kết nối kép giữa hai nút? Để giải quyết vấn đề này, sẽ đủ để thực hiện tuần tự các hoạt động được thực hiện để tạo kết nối TCP trong hoặc ngoài nước.

Điều này có nghĩa là mỗi nút nên xử lý tuần tự từng thao tác tạo kết nối mới, cho cả kết nối đến và kết nối đi. Theo cách này, việc duy trì danh sách các nút được kết nối, trước khi chấp nhận kết nối mới đến từ một nút hoặc trước khi gửi yêu cầu kết nối đến một nút, sẽ đủ để kiểm tra xem nút này đã có trong danh sách chưa.

Để thực hiện tuần tự các hoạt động tạo kết nối, việc thực hiện khóa trong danh sách các nút được kết nối là đủ : trên thực tế, đối với mỗi kết nối mới, mã định danh của nút được kết nối mới được thêm vào danh sách này. Tuy nhiên, tôi tự hỏi liệu cách tiếp cận này có thể gây ra bế tắc phân tán :

  • nút đầu tiên có thể gửi yêu cầu kết nối đến nút thứ hai;
  • nút thứ hai có thể gửi yêu cầu kết nối đến nút đầu tiên;
  • giả sử rằng hai yêu cầu kết nối không đồng bộ, cả hai nút sẽ khóa mọi yêu cầu kết nối đến.

Làm thế nào tôi có thể giải quyết vấn đề này?

CẬP NHẬT: Tuy nhiên, tôi vẫn phải khóa danh sách mỗi khi kết nối mới (đến hoặc đi) được tạo, vì các luồng khác có thể truy cập danh sách này, sau đó vấn đề bế tắc sẽ vẫn còn.

CẬP NHẬT 2: Dựa trên lời khuyên của bạn, tôi đã viết một thuật toán để ngăn chặn sự chấp nhận lẫn nhau của một yêu cầu đăng nhập. Vì mỗi nút là một mạng ngang hàng, nên nó có thể có một thói quen máy khách để gửi các yêu cầu kết nối mới và một thói quen máy chủ để chấp nhận các kết nối đến.

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
}

Ví dụ: Cổng IP: của nút A là A: 7001 - Cổng IP: của nút B là B: 8001.

Giả sử rằng nút A đã gửi yêu cầu đăng nhập đến nút B: 8001. Trong trường hợp này, nút A gọi thủ tục đăng nhập bằng cách gửi bằng cách gửi địa chỉ nghe của chính nó (A: 7001). Kết quả là, hàng xóm_table của nút A chứa địa chỉ của nút từ xa (B: 8001): địa chỉ này được liên kết với trạng thái KẾT NỐI. Nút A đang chờ nút B chấp nhận hoặc từ chối yêu cầu đăng nhập.

Trong khi đó, nút B cũng có thể đã gửi yêu cầu kết nối đến địa chỉ của nút A (A: 7001), sau đó nút A có thể đang xử lý yêu cầu của nút B. Vì vậy, hàng xóm_table của nút B chứa địa chỉ của điều khiển từ xa nút (A: 7001): địa chỉ này được liên kết với trạng thái KẾT NỐI. Nút B đang chờ nút A chấp nhận hoặc từ chối yêu cầu đăng nhập.

Nếu phía máy chủ của nút A từ chối yêu cầu từ B: 8001, thì tôi phải chắc chắn rằng phía máy chủ của nút B sẽ chấp nhận yêu cầu từ A: 7001. Tương tự, nếu phía máy chủ của nút B từ chối yêu cầu từ A: 7001, thì tôi phải chắc chắn rằng phía máy chủ của nút A sẽ chấp nhận yêu cầu từ B: 8001.

Theo quy tắc "địa chỉ nhỏ" , trong trường hợp này, nút A sẽ từ chối yêu cầu đăng nhập của nút B, trong khi nút B sẽ chấp nhận yêu cầu từ nút A.

Bạn nghĩ gì về điều này?


Những loại thuật toán này khá khó để phân tích và chứng minh. Tuy nhiên, có một nhà nghiên cứu là chuyên gia trong rất nhiều khía cạnh của điện toán phân tán. Kiểm tra trang ấn phẩm của Leslie Lamport tại: Research.microsoft.com/en-us/um/people/lamport/pub/pub.html
DeveloperDon

Câu trả lời:


3

Bạn có thể thử một cách tiếp cận "lạc quan": kết nối trước, sau đó ngắt kết nối nếu bạn phát hiện kết nối lẫn nhau đồng thời. Bằng cách này, bạn sẽ không cần phải tránh các yêu cầu kết nối trong khi bạn đang thực hiện các kết nối mới: khi kết nối đến được thiết lập, khóa danh sách và xem bạn có kết nối gửi đến cùng một máy chủ không. Nếu bạn làm thế, hãy kiểm tra địa chỉ của chủ nhà. Nếu nó nhỏ hơn của bạn, ngắt kết nối đi của bạn; mặt khác, ngắt kết nối cái đến Máy chủ ngang hàng của bạn sẽ làm điều ngược lại, bởi vì các địa chỉ sẽ so sánh khác nhau và một trong hai kết nối sẽ bị hủy. Cách tiếp cận này cho phép bạn tránh thử lại các kết nối của mình và có khả năng giúp bạn chấp nhận nhiều yêu cầu kết nối hơn trên mỗi đơn vị thời gian.


Tuy nhiên, tôi vẫn phải khóa trong danh sách mỗi khi kết nối mới (đến hoặc đi) được tạo, vì các luồng khác có thể truy cập vào danh sách này, sau đó vấn đề bế tắc sẽ vẫn còn.
enzom83

@ enzom83 Không, bế tắc là không thể theo sơ đồ này, bởi vì đồng nghiệp không bao giờ cần đợi bạn hoàn thành thao tác yêu cầu khóa. Các mutex là để bảo vệ nội bộ của danh sách của bạn; một khi bạn có được nó, bạn sẽ rời đi trong một khoảng thời gian đã biết, bởi vì bạn không cần chờ đợi bất kỳ tài nguyên nào khác trong phần quan trọng.
dasblinkenlight

Ok, nhưng bế tắc có thể xảy ra nếu yêu cầu kết nối không đồng bộ và nếu nó được thực hiện trong phần quan trọng: trong trường hợp này, nút không thể rời khỏi mutex cho đến khi yêu cầu kết nối của nó được chấp nhận. Nếu không, tôi chỉ thực hiện khóa trong danh sách để thêm (hoặc xóa) một nút: trong trường hợp này tôi nên kiểm tra các kết nối trùng lặp, v.v. Cuối cùng, một tùy chọn khác sẽ là gửi yêu cầu kết nối không đồng bộ.
enzom83

1
@ enzom83 Miễn là bạn không yêu cầu kết nối từ trong phần quan trọng, bạn sẽ không gặp bế tắc phân tán. Đó là ý tưởng đằng sau cách tiếp cận lạc quan - bạn chỉ thực hiện khóa trong danh sách để thêm hoặc xóa nút và nếu bạn tìm thấy kết nối đối ứng khi thêm nút, bạn sẽ ngắt nó sau khi rời phần quan trọng, sử dụng "địa chỉ nhỏ hơn" quy tắc (từ câu trả lời).
dasblinkenlight

4

Khi một nút gửi yêu cầu đến một nút khác, nó có thể bao gồm một số nguyên 64 bit ngẫu nhiên. Khi một nút nhận được yêu cầu kết nối, nếu nó đã gửi một yêu cầu kết nối, nó sẽ giữ một nút có số lượng cao nhất và loại bỏ các nút khác. Ví dụ:

Lần 1: A cố gắng kết nối với B, gửi số 123.

Lần 2: B cố gắng kết nối với A, gửi số 234.

Lần 3: B nhận được yêu cầu của A. Vì yêu cầu riêng của B có số lượng cao hơn, nên từ chối yêu cầu của A.

Lần 4: A nhận được yêu cầu của B. Vì yêu cầu của B có số cao hơn, A chấp nhận và bỏ yêu cầu của chính mình.

Để tạo số ngẫu nhiên, hãy đảm bảo bạn chọn trình tạo số ngẫu nhiên bằng / dev / urandom, thay vì sử dụng phép tạo số mặc định của trình tạo số ngẫu nhiên, thường dựa trên thời gian đồng hồ treo tường: có một cơ hội không thể bỏ qua là hai nút sẽ nhận được cùng một hạt giống.

Thay vì số ngẫu nhiên, bạn cũng có thể phân phối số trước thời hạn (tức là chỉ đánh số tất cả các máy từ 1 đến n) hoặc sử dụng địa chỉ MAC hoặc một số cách khác để tìm số có xác suất va chạm rất nhỏ có thể bỏ qua.


3

Nếu tôi hiểu, vấn đề bạn đang cố tránh là như thế này:

  • Node1 yêu cầu kết nối từ nút 2
  • Node1 khóa danh sách kết nối
  • Node2 yêu cầu kết nối từ nút 1
  • Node2 khóa danh sách kết nối
  • Node2 nhận yêu cầu kết nối từ nút1, từ chối vì danh sách bị khóa
  • Node1 nhận yêu cầu kết nối từ nút2, từ chối vì danh sách bị khóa
  • Không ai kết thúc kết nối với nhau.

Tôi có thể nghĩ ra một vài cách khác nhau để đối phó với điều này.

  1. Nếu bạn cố gắng kết nối với một nút và nó từ chối yêu cầu của bạn bằng thông báo "danh sách bị khóa", hãy đợi một số mili giây ngẫu nhiên trước khi thử lại. (Tính ngẫu nhiên là rất quan trọng. Nó làm cho ít có khả năng cả hai sẽ chờ đợi cùng một lượng thời gian chính xác và lặp lại cùng một vấn đề quảng cáo vô cùng .)
  2. Đồng bộ hóa đồng hồ của cả hai hệ thống với máy chủ thời gian và gửi dấu thời gian cùng với yêu cầu kết nối. Nếu một yêu cầu kết nối đến từ một nút mà bạn hiện đang cố gắng kết nối, thì cả hai nút đều đồng ý rằng bất kỳ ai cố gắng kết nối trước sẽ thắng và kết nối khác sẽ bị đóng.

Vấn đề cũng là nút nhận được yêu cầu không thể từ chối kết nối, nhưng nó sẽ ở lại chờ đợi vô thời hạn để có được khóa trong danh sách.
enzom83
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.