So sánh ba phương pháp triển khai khóa phân tán phổ biến

Khi xây dựng hệ thống phần mềm hiện đại, đặc biệt là các ứng dụng có kiến trúc phân tán, việc đảm bảo tính toàn vẹn dữ liệu trong môi trường đa tiến trình và đa nút là một thách thức không thể bỏ qua. Khóa phân tán chính là cơ chế then chốt giúp giải quyết bài toán đồng bộ hóa truy cập tài nguyên chung giữa các dịch vụ chạy trên các máy chủ khác nhau.

1. Khóa phân tán là gì?

Khóa phân tán là một cơ chế đồng bộ cho phép nhiều tiến trình — có thể nằm trên các máy vật lý hoặc container riêng biệt — phối hợp để độc chiếm quyền truy cập vào một tài nguyên chung (ví dụ: bản ghi cơ sở dữ liệu, cấu hình hệ thống, trạng thái thanh toán). Khác với khóa cục bộ (như synchronized trong Java hay threading.Lock trong Python), khóa phân tán phải tồn tại bên ngoài phạm vi của một tiến trình duy nhất và chịu được lỗi mạng, gián đoạn hoặc khởi động lại.

2. Ba cách triển khai tiêu biểu

a) Dựa trên cơ sở dữ liệu quan hệ

Có hai chiến lược chính: khóa bi quan (pessimistic locking) và khóa lạc quan (optimistic locking).

  • Khóa lạc quan với cột phiên bản: Mỗi bảng chứa một cột revision (hoặc version). Khi cập nhật, câu lệnh UPDATE kiểm tra giá trị hiện tại của revision trước khi ghi đè:
    UPDATE accounts 
    SET balance = balance - 2000, revision = revision + 1 
    WHERE id = 123 AND revision = 1;
    Nếu dòng bị ảnh hưởng bằng 0, nghĩa là đã có tiến trình khác cập nhật thành công trước đó — yêu cầu phải tải lại dữ liệu và thử lại.
  • Khóa bi quan với SELECT ... FOR UPDATE: Thực thi truy vấn khóa hàng ngay từ đầu giao dịch:
    BEGIN TRANSACTION;
    SELECT balance FROM accounts WHERE id = 123 FOR UPDATE;
    -- Xử lý logic nghiệp vụ
    UPDATE accounts SET balance = balance - 2000 WHERE id = 123;
    COMMIT;
    Phương pháp này hiệu quả với thời gian xử lý ngắn, nhưng dễ gây nghẽn nếu giao dịch kéo dài.

b) Dựa trên Redis với nguyên tắc atomicity

Redis hỗ trợ các thao tác nguyên tử như SETNX (set if not exists) và khả năng đặt thời gian sống (expiration) cho khóa. Một triển khai an toàn cần đáp ứng ba điều kiện: độc nhất, tự động hết hạn, và giải phóng an toàn.

Dưới đây là ví dụ minh họa sử dụng Lua script để đảm bảo tính nguyên tử khi xóa khóa:

-- Đặt khóa với TTL = 30s và giá trị ngẫu nhiên (để tránh xóa nhầm khóa của tiến trình khác)
SET lock:order:789 "a1b2c3" NX PX 30000

-- Script Lua để giải phóng khóa chỉ khi giá trị khớp
if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
else
  return 0
end

Giá trị ngẫu nhiên (ví dụ "a1b2c3") đóng vai trò như "token sở hữu", ngăn chặn tình huống tiến trình A hết thời gian chờ nhưng vẫn vô tình xóa khóa do tiến trình B tạo ra.

c) Dựa trên ZooKeeper với mô hình node tạm thời tuần tự

ZooKeeper cung cấp hệ thống phân cấp node theo dạng cây, trong đó các node có thể được tạo dưới dạng ephemeral (tạm thời) và sequential (có thứ tự tăng dần). Quy trình giành khóa gồm các bước sau:

  1. Tạo node con mang tiền tố /locks/order-req- dưới một node cha cố định (ví dụ /locks). ZooKeeper tự động gắn số thứ tự vào tên node (ví dụ: /locks/order-req-0000000001).
  2. Lấy danh sách tất cả node con và sắp xếp theo tên. Nếu node vừa tạo là nhỏ nhất → đã giành được khóa.
  3. Nếu không phải nhỏ nhất, đăng ký watcher lên node liền trước nó. Khi node đó bị xóa (do tiến trình sở hữu nó chết hoặc chủ động giải phóng), watcher sẽ đánh thức tiến trình hiện tại để kiểm tra lại.

Cơ chế này đảm bảo tính công bằng (fairness) và tự phục hồi khi tiến trình crash — vì node tạm thời sẽ biến mất khi session kết thúc.

Thẻ: Redis zookeeper database-locking distributed-systems concurrency-control

Đăng vào ngày 6 tháng 7 lúc 20:39