0

Process Pauses

Tạm Dừng Tiến Trình (Process Pauses)

Hãy xem xét một ví dụ khác về việc sử dụng đồng hồ nguy hiểm trong một hệ thống phân tán. Giả sử bạn có một cơ sở dữ liệu với một leader duy nhất trên mỗi phân vùng. Chỉ leader mới được phép chấp nhận ghi dữ liệu. Nhưng làm thế nào để một node biết rằng nó vẫn là leader (rằng nó chưa bị các node khác tuyên bố là đã chết) và có thể an toàn chấp nhận các yêu cầu ghi?

Một cách tiếp cận là leader có thể nhận được một lease từ các node khác, hoạt động giống như một khóa có thời gian hết hạn. Chỉ một node có thể giữ lease tại một thời điểm, do đó, khi một node nhận được lease, nó biết rằng nó là leader trong một khoảng thời gian nhất định, cho đến khi lease hết hạn. Để duy trì vai trò leader, node phải gia hạn lease định kỳ trước khi nó hết hạn. Nếu node gặp lỗi và không gia hạn lease, một node khác có thể tiếp quản sau khi lease hết hạn.

Bạn có thể tưởng tượng một vòng lặp xử lý yêu cầu hoạt động như sau:

while (true) {
    request = getIncomingRequest();
    // Đảm bảo lease luôn còn ít nhất 10 giây trước khi hết hạn
    if (lease.expiryTimeMillis - System.currentTimeMillis() < 10000) {
        lease = lease.renew();
    }
    if (lease.isValid()) {
        process(request);
    }
}

Vấn đề với đoạn code trên là gì?

Thứ nhất, nó dựa vào đồng hồ được đồng bộ giữa các máy: thời gian hết hạn của lease được thiết lập bởi một máy khác (ví dụ: tính bằng thời gian hiện tại cộng 30 giây) và nó được so sánh với đồng hồ hệ thống cục bộ. Nếu các đồng hồ bị lệch nhau vài giây, đoạn mã này có thể hoạt động sai.

Thứ hai, ngay cả khi ta thay đổi giao thức chỉ sử dụng monotonic clock cục bộ, vẫn có một vấn đề khác: đoạn mã này giả định rằng thời gian trôi qua giữa lúc kiểm tra đồng hồ (System.currentTimeMillis()) và lúc xử lý yêu cầu (process(request)) là không đáng kể.

Thông thường, đoạn mã này chạy rất nhanh, vì vậy bộ đệm 10 giây là đủ để đảm bảo lease không hết hạn giữa chừng. Nhưng nếu có một sự cố bất ngờ khiến tiến trình bị tạm dừng trong một khoảng thời gian dài thì sao?

Ví dụ, hãy tưởng tượng luồng bị dừng 15 giây ngay sau dòng lease.isValid() trước khi tiếp tục. Trong trường hợp đó, rất có thể lease đã hết hạn trước khi yêu cầu được xử lý, và một node khác đã tiếp quản vai trò leader. Tuy nhiên, không có cơ chế nào thông báo cho luồng này rằng nó đã bị dừng quá lâu, vì vậy mã này sẽ không nhận ra lease đã hết hạn cho đến lần lặp tiếp theo của vòng lặp – lúc đó nó có thể đã thực hiện một hành động nguy hiểm bằng cách xử lý một yêu cầu không hợp lệ.

Có vô lý không khi giả định rằng một luồng có thể bị tạm dừng lâu đến vậy?

Không hề! Có nhiều lý do khiến một luồng có thể bị tạm dừng ngoài dự kiến:

  • Trình thu gom rác (Garbage Collection - GC):

    • Nhiều runtime ngôn ngữ lập trình (như Java Virtual Machine - JVM) có GC "stop-the-world", có thể dừng toàn bộ luồng đang chạy.

    • Những lần pause này đôi khi kéo dài đến vài phút!

    • Ngay cả bộ thu gom rác song song (concurrent GC) như HotSpot JVM’s CMS cũng cần phải "stop the world" định kỳ.

  • Tạm dừng hoặc di chuyển máy ảo (VM Suspension & Live Migration):

    • Trong môi trường ảo hóa, một máy ảo có thể bị suspend (tạm dừng tất cả tiến trình và lưu trạng thái vào đĩa) và tiếp tục chạy lại sau đó.

    • Việc này có thể xảy ra bất kỳ lúc nào trong quá trình thực thi, và có thể kéo dài trong khoảng thời gian không xác định.

    • Một số hệ thống sử dụng tính năng này để di chuyển máy ảo giữa các máy chủ mà không cần khởi động lại (live migration).

  • Thiết bị người dùng bị suspend:

    • Trên laptop hoặc các thiết bị cá nhân, tiến trình có thể bị tạm dừng bất ngờ, chẳng hạn như khi người dùng đóng nắp laptop.
  • Chuyển đổi ngữ cảnh của hệ điều hành (OS Context Switching):

    • Khi hệ điều hành chuyển sang thực thi một luồng khác, hoặc khi hypervisor chuyển sang một máy ảo khác, luồng hiện tại có thể bị tạm dừng tại bất kỳ điểm nào trong mã.

    • Trong môi trường máy ảo, CPU thời gian bị chiếm dụng bởi các VM khác được gọi là steal time.

    • Nếu máy chủ bị quá tải, thời gian đợi có thể kéo dài đáng kể.

  • Truy cập I/O đồng bộ (Synchronous Disk I/O):

    • Một luồng có thể bị tạm dừng nếu đang chờ một thao tác I/O chậm.

    • Trong nhiều ngôn ngữ, truy cập đĩa có thể xảy ra ngoài mong đợi, chẳng hạn như Java classloader có thể tải lớp khi nó được sử dụng lần đầu tiên, điều này có thể diễn ra bất cứ lúc nào trong chương trình.

    • Nếu đĩa thực tế là hệ thống tệp mạng (NFS) hoặc ổ đĩa block qua mạng (Amazon EBS), độ trễ sẽ còn phụ thuộc vào tình trạng mạng.

  • Hoán đổi bộ nhớ (Memory Swapping - Paging):

    • Nếu hệ điều hành sử dụng swapping, một luồng có thể bị tạm dừng trong lúc chờ dữ liệu từ đĩa được tải vào RAM.

    • Khi bộ nhớ bị quá tải, hệ điều hành có thể bị thrashing (liên tục swap giữa RAM và đĩa, làm giảm hiệu suất nghiêm trọng).

    • Vì lý do này, các máy chủ thường vô hiệu hóa swap để tránh giảm hiệu suất.

  • Tín hiệu SIGSTOP:

    • Một tiến trình có thể bị tạm dừng bởi tín hiệu SIGSTOP, ví dụ: khi người dùng bấm Ctrl+Z trong terminal.

    • Ngay cả khi hệ thống không thường xuyên sử dụng SIGSTOP, nó vẫn có thể được gửi do nhầm lẫn bởi kỹ sư vận hành.

Hậu quả & Giải pháp

Tất cả các yếu tố trên có thể tạm dừng luồng tại bất kỳ điểm nào và tiếp tục sau đó mà luồng không hề hay biết. Điều này tương tự như lập trình đa luồng trên một máy, nơi bạn không thể giả định về thời gian thực thi vì context switching có thể xảy ra bất cứ lúc nào.

Trong lập trình đa luồng, chúng ta có mutex, semaphore, atomic counters, lock-free data structures... để đảm bảo thread safety.Tuy nhiên, các công cụ này không thể áp dụng trực tiếp vào hệ thống phân tán, vì trong hệ thống phân tán không có shared memory, chỉ có tin nhắn gửi qua mạng không đáng tin cậy.

Một node trong hệ thống phân tán phải luôn giả định rằng tiến trình của nó có thể bị tạm dừng bất cứ lúc nào. Trong khi bị tạm dừng, các node khác có thể tuyên bố nó đã chết vì không phản hồi. Khi node tiếp tục chạy, nó có thể không nhận ra rằng nó đã bị ngủ đông, cho đến khi kiểm tra đồng hồ và phát hiện ra rằng nó đã bị bỏ lại phía sau. 🚀


All rights reserved

Bình luận

Đăng nhập để bình luận
Avatar
0
Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí