Triển khai cơ chế đồng thuận Raft cho Database
Nguyên lý hoạt động và vai trò của Raft trong Database phân tán
Thuật toán Raft là cơ chế đồng thuận (Consensus Algorithm) giúp các node trong cụm (cluster) đạt được sự đồng thuận về trạng thái dữ liệu.
Trong Database phân tán, Raft đảm bảo tính nhất quán (Consistency) theo mô hình Strong Consistency: mọi ghi (write) phải được chấp nhận bởi đa số (majority) node trước khi xác nhận thành công.
Raft phân chia các node thành 3 vai trò: Leader (xử lý mọi yêu cầu client), Follower (bình chọn và nhận log từ Leader), và Candidate (trạng thái chuyển tiếp khi tìm Leader mới).
Chúng ta sẽ triển khai một cụm Raft giả lập trên 3 node Linux để minh họa cơ chế này trước khi áp dụng vào Database thực tế.
Cấu hình file Raft cho cụm 3 Node
Bước này định danh mỗi node và thiết lập danh sách các node tham gia cụm để hình thành mạng lưới đồng thuận.
Chúng ta sử dụng thư mục /etc/raft-cluster để chứa cấu hình. Mỗi node cần một file cấu hình riêng biệt với node_id duy nhất.
File cấu hình sẽ chứa thông tin về cluster_id (nhận diện cụm), node_id (nhận diện bản thân), data_dir (lưu log và snapshot), và peers (danh sách các node khác).
Áp dụng cho Node 1 (Leader tiềm năng):
Đường dẫn file: /etc/raft-cluster/node1.conf
[cluster]
cluster_id = "db-prod-01"
node_id = 1
data_dir = "/var/lib/raft/node1"
peers = "1:192.168.1.10:9001,2:192.168.1.11:9001,3:192.168.1.12:9001"
snapshot_interval = 10000
heartbeat_interval = 100
election_timeout_min = 100
election_timeout_max = 300
Kết quả mong đợi: File cấu hình được tạo, các tham số heartbeat và election timeout được thiết lập để phát hiện lỗi mạng nhanh chóng.
Áp dụng cho Node 2 (Follower):
Đường dẫn file: /etc/raft-cluster/node2.conf
[cluster]
cluster_id = "db-prod-01"
node_id = 2
data_dir = "/var/lib/raft/node2"
peers = "1:192.168.1.10:9001,2:192.168.1.11:9001,3:192.168.1.12:9001"
snapshot_interval = 10000
heartbeat_interval = 100
election_timeout_min = 100
election_timeout_max = 300
Áp dụng cho Node 3 (Follower):
Đường dẫn file: /etc/raft-cluster/node3.conf
[cluster]
cluster_id = "db-prod-01"
node_id = 3
data_dir = "/var/lib/raft/node3"
peers = "1:192.168.1.10:9001,2:192.168.1.11:9001,3:192.168.1.12:9001"
snapshot_interval = 10000
heartbeat_interval = 100
election_timeout_min = 100
election_timeout_max = 300
Verify kết quả cấu hình:
cat /etc/raft-cluster/node1.conf | grep node_id
Kết quả mong đợi: Output trả về node_id = 1 trên Node 1, tương tự cho Node 2 và Node 3.
Khởi động cụm và xác minh trạng thái Leader/Follower
Trước khi chạy tiến trình Raft, cần đảm bảo thư mục dữ liệu tồn tại và quyền truy cập đúng đắn để tránh lỗi permission.
mkdir -p /var/lib/raft/node{1,2,3}
chmod 750 /var/lib/raft/node{1,2,3}
chown -R raftuser:raftuser /var/lib/raft/node{1,2,3}
Kết quả mong đợi: Thư mục được tạo và thuộc quyền sở hữu của user raftuser.
Khởi động tiến trình Raft engine trên từng node. Giả sử chúng ta sử dụng một binary raft-engine (hoặc service tương đương như etcd/consul với cấu hình trên).
Trên Node 1 (khởi động trước để ưu tiên làm Leader):
sudo -u raftuser /usr/bin/raft-engine -config /etc/raft-cluster/node1.conf &
Kết quả mong đợi: Log xuất hiện dòng Starting Raft node 1, state: Follower rồi chuyển sang Becoming Candidate và cuối cùng Elected Leader.
Trên Node 2 và Node 3 (khởi động sau):
sudo -u raftuser /usr/bin/raft-engine -config /etc/raft-cluster/node2.conf &
sudo -u raftuser /usr/bin/raft-engine -config /etc/raft-cluster/node3.conf &
Kết quả mong đợi: Log xuất hiện dòng Joining cluster, state: Follower và nhận Heartbeat từ Node 1.
Xác minh trạng thái của cụm sau khi khởi động xong (chạy trên bất kỳ node nào):
curl -s http://192.168.1.10:2379/v2/keys/?recursive=true | jq '.nodes' || echo "Check status via CLI: raft-cli status --node 1"
Kết quả mong đợi: Có thể thấy Node 1 đang ở trạng thái Leader và Node 2, 3 ở trạng thái Follower.
Để kiểm tra chính xác hơn thông qua log của tiến trình:
grep "Leader" /var/log/raft/node1.log | tail -1
Kết quả mong đợi: Dòng log xác nhận Node 1 là Leader.
Xử lý tình huống mất kết nối mạng (Split-Brain)
Tình huống Split-Brain xảy ra khi mạng bị chia cắt khiến các node không thể liên lạc với nhau, dẫn đến nguy cơ có nhiều Leader cùng hoạt động.
Raft giải quyết vấn đề này bằng cơ chế Quorum (đa số). Với cụm 3 node, cần ít nhất 2 node (50% + 1) để bầu Leader.
Mô phỏng tình huống: Tách biệt Node 1 khỏi Node 2 và Node 3 bằng cách chặn mạng (iptables).
Trên Node 1, thực hiện lệnh chặn kết nối ra ngoài (giả lập mất mạng với 2 node còn lại):
iptables -A OUTPUT -d 192.168.1.11 -j DROP
iptables -A OUTPUT -d 192.168.1.12 -j DROP
iptables -A INPUT -s 192.168.1.11 -j DROP
iptables -A INPUT -s 192.168.1.12 -j DROP
Kết quả mong đợi: Node 1 mất liên lạc với Node 2 và Node 3. Sau khoảng thời gian election_timeout_max (300ms), Node 1 sẽ mất quyền Leader vì không thể gửi Heartbeat.
Quan sát log trên Node 1:
tail -f /var/log/raft/node1.log | grep -E "Leader|Follower|Candidate"
Kết quả mong đợi: Node 1 chuyển trạng thái từ Leader sang Follower (vì nó cô lập, không đủ quorum để giữ quyền Leader, hoặc nếu nó tự bầu lại thì chỉ là "Candidate" nhưng không thể trở thành Leader hợp lệ vì không có quorum).
Quan sát log trên Node 2 và Node 3 (vẫn liên lạc được với nhau):
tail -f /var/log/raft/node2.log | grep "Elected Leader"
Kết quả mong đợi: Một trong hai Node 2 hoặc Node 3 sẽ được bầu làm Leader mới sau khi không nhận được tín hiệu từ Node 1. Cụm vẫn hoạt động bình thường với 2 node còn lại.
Khôi phục mạng (Xóa rule iptables):
iptables -F
Kết quả mong đợi: Node 1 nối lại, nhận log từ Leader mới (Node 2 hoặc 3) và đồng bộ lại state. Không có 2 Leader cùng tồn tại.
Thử nghiệm khả năng chịu lỗi khi Node bị sập
Thử nghiệm này kiểm tra tính sẵn sàng cao (High Availability) khi một node vật lý bị tắt hoặc tiến trình bị kill đột ngột.
Giả sử Node 1 đang là Leader. Chúng ta sẽ giết tiến trình của Node 1.
pkill -f "raft-engine.*node1"
Kết quả mong đợi: Tiến trình Node 1 dừng hoạt động. Hệ thống mất 1 node, còn lại 2 node (2/3 vẫn đạt Quorum).
Quan sát log trên Node 2 và Node 3 để xem phản ứng của hệ thống:
grep "Leader" /var/log/raft/node2.log | tail -5
grep "Leader" /var/log/raft/node3.log | tail -5
Kết quả mong đợi: Node 2 hoặc Node 3 sẽ nhận ra Node 1 mất, và sau khoảng thời gian election timeout, một trong hai sẽ trở thành Leader mới.
Thử nghiệm ghi dữ liệu ngay sau khi Node 1 sập (chạy trên Node 2):
raft-cli write --key "test_key" --value "survived_crash" --node 2
Kết quả mong đợi: Lệnh ghi thành công (Success) vì cụm vẫn có đủ Quorum (Node 2 và Node 3).
Khởi động lại Node 1:
sudo -u raftuser /usr/bin/raft-engine -config /etc/raft-cluster/node1.conf &
Kết quả mong đợi: Node 1 khởi động, nhận ra mình không phải Leader, chuyển sang trạng thái Follower, và tự động đồng bộ (Snapshot/Log replication) dữ liệu mới "survived_crash" từ Leader hiện tại.
Verify dữ liệu đã được đồng bộ trên Node 1:
raft-cli read --key "test_key" --node 1
Kết quả mong đợi: Output trả về giá trị survived_crash, xác nhận dữ liệu đã được đồng bộ hoàn chỉnh sau khi node hồi phục.
Điều hướng series:
Mục lục: Series: Triển khai Database phân tán an toàn với Raft, TDE và Linux Kernel Audit
« Phần 1: Chuẩn bị môi trường và kiến trúc tổng quan
Phần 3: Cấu hình mã hóa dữ liệu tại chỗ (TDE) »