Một instance Redis sập thì mọi phiên đăng nhập, giỏ hàng và cache cũng mất theo. Redis Cluster giải quyết cả hai vấn đề cùng lúc: dữ liệu được chia cho nhiều master để vượt giới hạn RAM của một máy, và mỗi master có replica sẵn sàng thay thế. Bài này dựng cụm 4 master và 4 replica trên bốn máy Ubuntu 24.04 bằng Valkey, kèm một cách xếp replica mà hướng dẫn mặc định không làm cho bạn, rồi rút điện từng máy để xem cụm phản ứng ra sao.
Vì sao Valkey
Từ bản 7.4, Redis chuyển sang giấy phép không còn là mã nguồn mở theo nghĩa truyền thống. Valkey là bản fork từ Redis 7.2, do Linux Foundation bảo trợ, giữ giấy phép BSD và tương thích hoàn toàn về giao thức lẫn lệnh. Mọi client Redis đều kết nối được. Trên Ubuntu 24.04, cả hai đều có trong kho chính thức: redis-server 7.0 và valkey-server 7.2. Công cụ dòng lệnh là valkey-cli, dùng y hệt redis-cli.
Mô hình: xếp replica chéo máy
cong 6379 (master) cong 6380 (replica)
node1 A slot 0-4095 replica cua D
node2 B slot 4096-8191 replica cua A
node3 C slot 8192-12287 replica cua B
node4 D slot 12288-16383 replica cua C
replica cua master tren may i nam o may i+1 (vong tron)
Redis Cluster chia không gian key thành 16.384 slot, mỗi master giữ một đoạn. Mỗi máy chạy hai instance: master ở cổng 6379 và replica ở cổng 6380. Điều quan trọng nhất trong sơ đồ này: replica không bao giờ nằm chung máy với master của nó. Nếu nằm chung, máy đó sập là mất cả hai bản sao.
Bước 1: cài đặt và chuẩn bị kernel
sudo apt install -y valkey-server valkey-tools
sudo systemctl disable --now valkey-server # tra cong 6379 cho instance cluster
echo 'vm.overcommit_memory = 1' | sudo tee /etc/sysctl.d/60-valkey.conf
sudo sysctl -p /etc/sysctl.d/60-valkey.conf
vm.overcommit_memory=1 là bắt buộc. Khi ghi AOF hay RDB, Valkey fork tiến trình. Với giá trị mặc định là 0, lệnh fork có thể thất bại ngay khi RAM còn trống kha khá, và lúc đó việc ghi dữ liệu xuống đĩa dừng lại mà không báo gì.
Bước 2: hai instance mỗi máy
Gói trên Ubuntu có sẵn unit mẫu [email protected], đọc cấu hình từ /etc/valkey/valkey-%i.conf. Đây là file /etc/valkey/valkey-6379.conf trên node1:
bind 127.0.0.1 192.168.1.31
protected-mode yes
port 6379
supervised systemd
pidfile /run/valkey-6379/valkey-server.pid
logfile /var/log/valkey/valkey-6379.log
dir /var/lib/valkey/6379
# ben vung: AOF moi giay + RDB dinh ky
appendonly yes
appendfsync everysec
save 3600 1 300 100 60 10000
# xac thuc: client va replica dung chung mat khau
requirepass MAT-KHAU-DAI-NGAU-NHIEN
masterauth MAT-KHAU-DAI-NGAU-NHIEN
# bo nho: 2 instance tren may 16 GB
maxmemory 5gb
maxmemory-policy noeviction
# cluster
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000
cluster-announce-ip 192.168.1.31
cluster-require-full-coverage yes
cluster-allow-reads-when-down no
replica-read-only yes
Với instance cổng 6380, thay mọi chỗ 6379 bằng 6380. Trên từng máy, đổi bind và cluster-announce-ip thành IP của máy đó. Sau đó:
sudo install -d -o valkey -g valkey -m 750 /var/lib/valkey/6379 /var/lib/valkey/6380
sudo chown valkey:valkey /etc/valkey/valkey-63*.conf && sudo chmod 640 /etc/valkey/valkey-63*.conf
sudo systemctl enable --now valkey-server@6379 valkey-server@6380
Một vài lựa chọn trong cấu hình cần giải thích:
maxmemory-policy noeviction: khi đầy bộ nhớ, lệnh ghi báo lỗi thay vì âm thầm xoá key. Đây là lựa chọn đúng khi cụm giữ dữ liệu thật. Nếu chỉ dùng làm cache, đổi sangallkeys-lru.maxmemory 5gbcho mỗi instance trên máy 16 GB: phải chừa RAM cho fork khi ghi AOF và cho hệ điều hành.cluster-announce-ipcố định IP LAN mà các node và client nhìn thấy. Thiếu dòng này, trên máy có nhiều card mạng (Docker, VPN) node có thể quảng bá sai địa chỉ.
Bước 3: tạo cụm và gắn replica đúng chỗ
Lệnh --cluster create ... --cluster-replicas 1 tự xếp replica, nhưng không đảm bảo được cách xếp chéo như ta muốn. Vì vậy ta làm hai bước: tạo 4 master trước, sau đó gắn từng replica vào đúng master của nó:
export REDISCLI_AUTH='MAT-KHAU-DAI-NGAU-NHIEN' # tranh lo mat khau tren dong lenh
valkey-cli --cluster create 192.168.1.31:6379 192.168.1.32:6379 \
192.168.1.33:6379 192.168.1.34:6379 --cluster-replicas 0 --cluster-yes
id_of() { valkey-cli -h $1 -p 6379 cluster myid; }
# replica cua master tren may i dat o cong 6380 cua may ke tiep
valkey-cli --cluster add-node 192.168.1.32:6380 192.168.1.31:6379 --cluster-slave --cluster-master-id $(id_of 192.168.1.31)
valkey-cli --cluster add-node 192.168.1.33:6380 192.168.1.31:6379 --cluster-slave --cluster-master-id $(id_of 192.168.1.32)
valkey-cli --cluster add-node 192.168.1.34:6380 192.168.1.31:6379 --cluster-slave --cluster-master-id $(id_of 192.168.1.33)
valkey-cli --cluster add-node 192.168.1.31:6380 192.168.1.31:6379 --cluster-slave --cluster-master-id $(id_of 192.168.1.34)
valkey-cli --cluster check 192.168.1.31:6379
# [OK] All 16384 slots covered.
Dùng cụm từ ứng dụng
Client phải hỗ trợ chế độ cluster. Chỉ cần khai báo vài node, client sẽ tự tìm ra phần còn lại và tự đi theo chuyển hướng MOVED:
from redis.cluster import RedisCluster, ClusterNode
r = RedisCluster(startup_nodes=[ClusterNode("192.168.1.31", 6379),
ClusterNode("192.168.1.32", 6379)],
password="MAT-KHAU-DAI-NGAU-NHIEN")
r.set("gio:42", 3)
Cái giá của việc chia slot: lệnh nhiều key chỉ chạy được khi mọi key nằm cùng một slot. Giải pháp là hash tag: chỉ phần nằm trong {} được dùng để tính slot.
127.0.0.1:6379> MGET key:1 key:2
(error) CROSSSLOT Keys in request don't hash to the same slot
127.0.0.1:6379> MSET {user:42}:ten An {user:42}:gio 3
OK
127.0.0.1:6379> MGET {user:42}:ten {user:42}:gio
1) "An"
2) "3"
Ngoài ra, chế độ cluster chỉ có database 0 nên không dùng được SELECT. Hãy thiết kế key sao cho những dữ liệu cần xử lý chung một lệnh (giỏ hàng của một người, phiên đăng nhập của một người) dùng chung hash tag ngay từ đầu.
Giám sát: cụm “OK” chưa chắc đã an toàn
Sau một lần failover, cluster_state vẫn báo ok, nhưng master đã chuyển sang máy khác. Lúc này một máy giữ hai master, và nếu tiếp tục hỏng thêm thì hậu quả nặng hơn. Script dưới đây kiểm tra cả những trường hợp đó: master không có replica, master và replica chung máy, một máy giữ hai master. Khi cụm khoẻ, script trả exit code 0:
#!/bin/bash
# Kiem tra suc khoe Valkey cluster (nhin tu bat ky instance nao con song).
# valkey-cluster-check.sh -> in trang thai, exit 0 = khoe, 1 = co van de
# valkey-cluster-check.sh --log -> (timer goi) chi ghi log khi trang thai thay doi hoac dang loi
LOG=/var/log/valkey/cluster-check.log
STATE=/run/valkey-cluster-check.state
NODES="192.168.1.31 192.168.1.32 192.168.1.33 192.168.1.34"
export REDISCLI_AUTH=$(cat /etc/valkey/cluster.password)
C="valkey-cli --no-auth-warning"
# lay CLUSTER NODES tu instance dau tien tra loi duoc
CN=""
for h in $NODES; do for p in 6379 6380; do
CN=$(timeout 3 $C -h $h -p $p cluster nodes 2>/dev/null | tr -d '\r') && [ -n "$CN" ] && { SEEN="$h:$p"; break 2; }
done; done
PROBLEMS=()
if [ -z "$CN" ]; then PROBLEMS+=("khong ket noi duoc instance nao")
else
STATE_OK=$(timeout 3 $C -h ${SEEN%:*} -p ${SEEN#*:} cluster info | tr -d '\r' | awk -F: '/cluster_state/{print $2}')
[ "$STATE_OK" = ok ] || PROBLEMS+=("cluster_state=$STATE_OK")
# node bi danh dau fail
FAILED=$(echo "$CN" | awk '$3 ~ /fail/ && $3 !~ /pfail/ {split($2,a,"@"); print a[1]}' | paste -sd' ')
[ -n "$FAILED" ] && PROBLEMS+=("node hong: $FAILED")
# moi master co slot: phai co replica khoe, va replica khong cung may
while read -r id addr flags _ _ _ _ link slots; do
[[ "$flags" == *master* ]] && [ -n "$slots" ] || continue
mhost=${addr%%:*}
reps=$(echo "$CN" | awk -v m="$id" '$4==m && $3 !~ /fail/ {split($2,a,"@"); print a[1]}')
[ -z "$reps" ] && PROBLEMS+=("master ${addr%@*} KHONG CO replica khoe")
for r in $reps; do [ "${r%%:*}" = "$mhost" ] && PROBLEMS+=("master ${addr%@*} va replica $r CUNG MAY"); done
done <<< "$CN"
# may nao giu hon 1 master -> lech so do (chay valkey-cluster-restore.sh)
DUP=$(echo "$CN" | awk '$3 ~ /master/ && $3 !~ /fail/ && NF>=9 {split($2,a,":"); c[a[1]]++} END{for(h in c) if(c[h]>1) print h}' | paste -sd' ')
[ -n "$DUP" ] && PROBLEMS+=("may giu 2 master (lech so do): $DUP")
fi
MASTERS=$(echo "$CN" | awk '$3 ~ /master/ && $3 !~ /fail/ && NF>=9 {split($2,a,"@"); print a[1]}' | sort | paste -sd' ')
if [ ${#PROBLEMS[@]} -eq 0 ]; then MSG="OK: 4 master co replica khac may; master: $MASTERS"; OK=0
else MSG="LOI: $(IFS='; '; echo "${PROBLEMS[*]}") | master: $MASTERS"; OK=1; fi
if [ "${1:-}" = --log ]; then
KEY=$(echo "$MSG" | cut -d'|' -f1)
if [ "$OK" = 1 ] || [ "$KEY" != "$(cat $STATE 2>/dev/null)" ]; then echo "$(date '+%F %T') $MSG" >> "$LOG"; fi
echo "$KEY" > "$STATE"
else echo "$MSG"; fi
exit $OK
Chạy script bằng systemd timer mỗi phút trên mọi máy. Script chỉ ghi log khi trạng thái thay đổi hoặc đang có lỗi, nên log không phình ra.
Rút điện từng máy
Ghi 10.000 key, cho một client ghi liên tục mỗi 0,2 giây, rồi dừng cả hai instance trên node1:
10:56:03.7 dung node1
10:56:04.2 w:37 Could not connect to Valkey at 192.168.1.31:6379 <- chi slot cua node1 loi
10:56:11.7 node2:6380 Failover election won: I'm the new master.
tong=150 lenh ghi, OK=139, loi=11
tong key tren cac master = 10139 = 10000 + 139 -> khong mat key nao
Failover mất khoảng 8 giây: 5 giây cluster-node-timeout cộng thời gian bầu chọn. Trong khoảng đó, chỉ những key thuộc slot của node bị sập mới báo lỗi; ba phần tư dữ liệu còn lại vẫn đọc ghi bình thường. Giảm cluster-node-timeout thì failover nhanh hơn, nhưng dễ failover nhầm khi mạng chỉ chập chờn một chút.
Đưa sơ đồ về như cũ
node1 bật lại thì tự nhập cụm, nhưng với vai trò replica. Master A lúc này đang ở node2:6380, tức node2 giữ hai master. CLUSTER FAILOVER chạy trên replica sẽ đổi vai an toàn: replica chờ đồng bộ xong rồi mới nhận làm master, nên không mất dữ liệu. Script tự động hoá việc này:
#!/bin/bash
# Dua so do ve thiet ke: moi may giu 1 master o cong 6379, replica o 6380.
# Sau failover, instance 6379 vua bat lai se la replica -> CLUSTER FAILOVER tren no de lay lai vai
# master MOT CACH AN TOAN (cho replica dong bo xong roi moi doi vai, khong mat du lieu).
NODES="192.168.1.31 192.168.1.32 192.168.1.33 192.168.1.34"
export REDISCLI_AUTH=$(cat /etc/valkey/cluster.password)
C="valkey-cli --no-auth-warning"
for h in $NODES; do
ROLE=$(timeout 3 $C -h $h -p 6379 role 2>/dev/null | head -1 | tr -d '\r')
if [ "$ROLE" = slave ]; then
echo "$h:6379 dang la replica -> CLUSTER FAILOVER"
$C -h $h -p 6379 cluster failover | tr -d '\r'
sleep 3
fi
done
/usr/local/bin/valkey-cluster-check.sh
Giới hạn của sơ đồ
| Kịch bản | Kết quả |
|---|---|
| Mất 1 máy bất kỳ | Failover khoảng 8 giây, không mất dữ liệu |
| Reboot 1 máy | Instance, sysctl và timer tự lên; máy nhập cụm làm replica |
| Mất 2 máy đối diện (node1 + node3) | Cụm vẫn chạy: mỗi master mất đều còn replica ở máy khác |
| Mất 2 máy cạnh nhau (node1 + node2) | CLUSTERDOWN: master A mất luôn replica. Bật lại một máy thì cụm tự hồi phục, AOF giữ đủ dữ liệu |
Trường hợp cuối là lý do vì sao cluster-require-full-coverage yes làm cả cụm ngừng phục vụ khi thiếu một đoạn slot, kể cả những key thuộc slot còn khoẻ. Đặt thành no thì phần còn lại vẫn chạy, nhưng ứng dụng sẽ thấy một phần tư dữ liệu “biến mất” mà không có lỗi rõ ràng. Muốn chịu được mất hai máy bất kỳ thì cần hai replica cho mỗi master, tức 12 instance trên 4 máy.
Những điều cần nhớ
- Replica phải khác máy với master, và sau mỗi lần failover phải kiểm tra lại điều này.
- Bật AOF. Replica giúp bạn khi mất một máy, còn AOF giúp bạn khi cả cụm khởi động lại.
- Cụm không phải là backup.
FLUSHALLhay một lệnhDELchạy nhầm sẽ được replicate ngay lập tức. Hãy chép RDB ra ngoài cụm mỗi đêm. - Máy ảo cùng một host vật lý thì chưa phải là HA. Hãy rải VM ra các host khác nhau, hoặc dùng quy tắc anti-affinity của hypervisor.
- Không sửa tay
nodes-*.conf. Đó là trạng thái cụm do chính các node ghi ra.
