Sao chép của Redis là bất đồng bộ, và mọi tài liệu đều cảnh báo rằng điều đó có nghĩa mất dữ liệu. Bài này đo xem mất bao nhiêu — và câu trả lời phụ thuộc hoàn toàn vào một thứ.
Ba lần giết máy chính
Tôi cho một khách ghi liên tục vào máy chính, đếm số lệnh đã được xác nhận, rồi SIGKILL máy chính và đếm lại trên bản sao.
| Tình huống | Xác nhận | Bản sao có | Mất |
|---|---|---|---|
| Liên kết khoẻ, ghi từng lệnh | 87.972 | 87.972 | 0 |
| Liên kết khoẻ, đường ống 500 | 2.782.500 | 2.782.500 | 0 |
| Bản sao bị dừng trước khi giết | 100.719 | 21.127 | 79.592 (79%) |
Hai dòng đầu không mất gì — kể cả với 2,78 triệu lệnh ghi trong tám giây.
Dòng thứ ba mất gần bốn phần năm.
Bất đồng bộ không có nghĩa "mất một ít"
Đây là chỗ trực giác hay sai. Người ta hình dung sao chép bất đồng bộ như một khoảng trễ nhỏ cố định, mất "vài lệnh cuối".
Thực tế: nó mất đúng bằng phần bản sao đang chậm. Bình thường phần đó bằng không; khi có sự cố mạng, nó có thể là gần hết.
Lý do hai dòng đầu không mất gì: Redis đẩy dữ liệu vào bộ đệm sao chép trong cùng vòng lặp sự kiện với lệnh, và gửi đi trước khi khách nhận được trả lời. Với liên kết nhanh và bản sao theo kịp, dữ liệu đã ở bên kia trước khi khách biết lệnh thành công.
Đo độ trễ sao chép khi khoẻ mạnh, 300 lần:
p50 0,120 ms | p99 0,290 ms | max 0,428 ms
Con số này còn gồm cả thời gian khách hỏi bản sao, nên độ trễ thật còn thấp hơn.
Nhưng bảo đảm đó hoàn toàn phụ thuộc vào liên kết. Bản sao chậm — vì mạng nghẽn, vì nó đang bận lưu RDB, vì nó ở vùng khác — thì khoảng cách nới ra ngay, và mọi thứ trong khoảng đó là thứ bạn sẽ mất.
Theo dõi khoảng cách đó
Vì lượng mất bằng khoảng cách, hãy đo khoảng cách:
redis-cli info replication | grep -E 'master_repl_offset|slave0'
slave0:ip=...,port=6379,state=online,offset=123456,lag=0
master_repl_offset:123456
Hiệu giữa master_repl_offset và offset của bản sao là số byte đang chậm — chính là lượng sẽ mất nếu máy chính chết ngay lúc đó.
lag là số giây kể từ lần bản sao báo cáo gần nhất, không phải khoảng cách dữ liệu. Đừng nhầm hai cái.
Cảnh báo nên đặt trên hiệu số byte, không trên lag.
WAIT: đổi tốc độ lấy bảo đảm
WAIT numreplicas timeout chặn cho tới khi đủ số bản sao xác nhận đã nhận.
không WAIT 20.905 ops/s
có WAIT 1 100 sau mỗi SET 835 ops/s
Chậm 25 lần.
Mỗi WAIT là một lượt đi về nữa, cộng thời gian chờ bản sao xác nhận. Với hệ thống ghi nhiều, cái giá đó thường không chấp nhận được.
Và WAIT bảo đảm ít hơn người ta tưởng: nó chỉ cho biết bản sao đã nhận được dữ liệu vào bộ nhớ, không cho biết đã ghi xuống đĩa. Bản sao chết ngay sau đó vẫn có thể mất.
Cách dùng thực tế: đừng gọi WAIT sau mỗi lệnh. Gọi nó ở những điểm thật sự cần — sau khi ghi xong một giao dịch quan trọng, trước khi trả lời người dùng rằng "đã lưu".
Bản sao chỉ đọc
SET trên bản sao -> READONLY You can't write against a read only replica.
Mặc định replica-read-only yes, và nên giữ. Ghi vào bản sao tạo ra dữ liệu chỉ tồn tại ở đó, và nó biến mất im lặng ở lần đồng bộ toàn phần tiếp theo.
Điều này cũng nghĩa là bản sao không dùng để san tải ghi. Nó dùng để:
- San tải đọc — nhưng nhớ dữ liệu có thể cũ hơn máy chính một chút.
- Chạy các lệnh nặng như
BGSAVEhoặc quét toàn bộ mà không ảnh hưởng máy chính. - Sẵn sàng thay thế khi máy chính chết.
Ba mục đích đó khác nhau và có thể xung đột: bản sao đang chạy BGSAVE là bản sao đang chậm lại, và đó chính là lúc nó mất nhiều nhất nếu máy chính chết.
Đồng bộ toàn phần rất đắt
Khi bản sao mất kết nối quá lâu, nó không thể đồng bộ từng phần và phải làm đồng bộ toàn phần: máy chính BGSAVE rồi gửi cả tệp RDB.
Với dữ liệu 338 MB đo ở phần 18, đó là một lần fork cộng chi phí copy-on-write, cộng 47 MB qua mạng — trên máy chính, đúng lúc nó đang phục vụ.
Cách giảm: nới bộ đệm quay lui.
repl-backlog-size 64mb
repl-backlog-ttl 3600
Bộ đệm này giữ các thay đổi gần đây để bản sao nối lại có thể lấy phần thiếu thay vì đồng bộ toàn phần. Mặc định 1 MB — quá nhỏ cho hệ thống ghi nhiều; một giây gián đoạn mạng cũng đủ để tràn nó.
Sao chép không phải sao lưu
Nhắc lại điều đã nói ở phần trước, vì nó là hiểu nhầm phổ biến nhất:
FLUSHALL gõ nhầm trên máy chính được nhân bản sang bản sao trong vài mili giây. Bản sao không cứu bạn khỏi lỗi con người, chỉ cứu khỏi hỏng phần cứng.
Cho lỗi con người, cần bản sao lưu ở nơi khác, tại một thời điểm trong quá khứ.
Thử ba mươi giây
Đo lượng dữ liệu bạn sẽ mất nếu máy chính chết ngay bây giờ:
redis-cli info replication | python3 -c '
import sys, re
d = sys.stdin.read()
m = int(re.search(r"master_repl_offset:(\d+)", d).group(1))
for s in re.finditer(r"slave\d+:.*?offset=(\d+).*?lag=(\d+)", d):
off, lag = int(s.group(1)), int(s.group(2))
print("cham %d byte, lag %d giay" % (m - off, lag))
'
Con số byte đó là câu trả lời trực tiếp. Bình thường nó gần 0 — như đo được ở trên. Thấy nó lên tới hàng megabyte và ở đó là bạn đang chạy với một bản sao không còn giá trị bảo vệ nhiều.
Phần sau: Sentinel — đo thời gian phát hiện máy chính chết và chuyển đổi.