← Bài trước | Series hub | Tiếp theo →
Chương 8: Đồng Bộ Hóa Clusters Bằng Distributed Locks
Bên trong một ứng dụng Go chạy cô lập (standalone), để tránh chuyện hai Goroutines giành giật dẫm đạp lên nhau ghi đè chung một cục dữ liệu (Race Condition), người ta hay viện đến gã gác cửa sync.Mutex. Xui thay, lúc hệ thống của bạn phình to rải quân ra tận 10 servers nấp sau lưng một gã Load Balancer, cái sync.Mutex bỗng hóa thứ đồ bỏ vì nó chỉ khóa được mỗi cục RAM nội bộ của nhà nó. Bạn sẽ phải mưu cầu một thứ lớn lao hơn: Distributed Lock (Khóa phân tán).
1. Món Redis Locks Cơ Bản Nhất
Answer-first: Bộ lock Redis căn bản nhất xài ngàm lệnh SET resource id NX PX ttl. Đồ chơi này chạy êm ru với các tác vụ caching đơn giản nhưng lại dính chí mạng cái lỗi Single Point of Failure (Điểm Gãy Cục Bộ) rủi như cái server Redis Master lăn quay ra chết trước khi kịp chép đồng bộ cho đàn em.
Một chốt chặn distributed lock dạng bọt bèo nhất lợi dụng độc 1 cái node Redis kèm một tuyệt chiêu nổ nguyên tử:
SET resource_name my_unique_id NX PX 30000
NX: Lệnh bảo chứng chỉ người gõ cửa đầu tiên mới qua ải (nắm chốt khóa).PX 30000: Cái khóa sẽ tự động bốc hơi sau đúng 30 giây (Lease Expiration - Hết hạn thuê) nhằm tránh kịch bản bế tắc Deadlocks rủi như cái server đang ngậm khóa lăn ra chết.
Lỗ Hổng Chí Mạng: Lỡ như cái node Redis đó tử ẹo ngay tắp lự sau lúc vừa trao khóa cho Server A, mà chưa kịp chép cái khóa đó rỉ tai cho node Slave thì sao? Node Slave tự cướp ngôi lên làm Master mà não hoàn toàn trắng trơn không hề hay biết Server A đang cầm khóa. Server B xông tới đòi khóa, và Master mới hồn nhiên cấp luôn. Hai con servers giờ đây cùng nắm khóa xông vào thao tác chung lúc $\to$ Dữ liệu bị vò nát bét.
2. Giải Thuật Redlock
Answer-first: Thuật toán Redlock tiễn đưa điểm yếu Single Point of Failure của Redis bằng cách gõ cửa hỏi thăm nhiều con Redis Masters hoàn toàn độc lập với nhau. Một cái khóa chỉ được coi là cấp thành công nếu nhận được cái gật đầu của một đa số (quorum) lượng nodes.
Để gỡ dớp cho lỗ hổng sao chép của Redis, đích thân Salvatore Sanfilippo (cha đẻ của Redis) đã phác thảo ra một giải thuật mang tên Redlock.
Redlock xài hẳn một bầy cluster gồm $N$ (đa số là 5) cỗ máy Redis Masters hoàn toàn không chơi với nhau. Để giành giật được chốt khóa, cái Go Server của bạn phải:
- Chạy đôn chạy đáo gõ cửa xin khóa ở khắp cả 5 nodes.
- Nếu như nó may mắn lụm được khóa của lượng đa số (Quorum: $\ge 3/5$ nodes) nội trong một mốc khung giờ bị ép cứng, cái khóa đó mới chính thức được hợp pháp hóa.
- Nếu đếm không đủ đa số (quorum), nó phải vội vàng quay xe đi xóa sạch dấu vết cái khóa ở khắp mọi nodes.
Làm trong Golang, bạn cứ việc thảnh thơi import bộ thư viện github.com/go-redsync/redsync. Nó cực kỳ lẹ làng và hợp cạ vô cùng với đám Microservices cùng hội nhóm Caching.
3. Khóa ZooKeeper / etcd: Đánh Đổi Hiệu Năng
Answer-first: Mặc cho tốc độ bạt gió của Redlock, nó lại dính tử huyệt Clock Drift (Trôi dạt thời gian). Đám hệ thống tải mảng tài chính đòi hỏi độ nhất quán cực mạnh (Strong Consistency) đành phải bấu víu xài Apache ZooKeeper hoặc etcd để hưởng sái cơ chế khóa chạy bằng event-driven (hướng sự kiện).
Bất chấp độ phủ sóng của Redlock, đám cao thủ chuyên trị hệ thống phân tán (điển hình như Martin Kleppmann) cứ hay càm ràm chuyện nó quá ỷ lại vô nhịp đồng bộ của đám đồng hồ vật lý. Lỡ có cái server nào dính bệnh Clock Drift (đồng hồ chạy lệch), mấy cái khóa có thể bị bay màu vô chừng không lường trước.
Nhằm bảo vệ hệ thống Core Banking cất giấu độ nhất quán vẹn toàn (Strong Consistency), dân kỹ sư xách đồ nghề Apache ZooKeeper hay etcd ra thi triển.
- Cơ Chế Mật: ZK xài cấu trúc mô hình thư mục cây. Một Go server vô tạo ra cái Ephemeral Sequential Node (Node tuần tự phù du). Nếu cái mã số thứ tự của nó là nhỏ nhất, nó đớp được khóa. Chẳng may thua số, nó sẽ đăng ký cái thẻ
Watchrình mò ngay cái node đứng trước mặt. Tới hồi cái node đàn anh xử lý trót lọt rồi tự tử (xóa), ZK liền kích nổ một sự kiện bắn thông báo ngay chóc tới con Go server. - Ưu Điểm: Siêu cấp bền bỉ chạy dựa trên ngàm giao thức đồng thuận ZAB/Raft. Nó cạo bỏ cái thói xấu lặp Polling đốt CPU bởi vì chạy trên nền tảng event-driven.
- Nhược Điểm: Tốc độ rùa bò bỏ xa so với Redis, và để hầu hạ nuôi nấng bảo trì một mảng ZK cluster là chuyện trần ai khoai củ vô cùng.
4. Nguy Cơ Rình Rập Chết Người: Trượt Khóa Do Garbage Collection (GC)
Answer-first: Một đoạn khựng dài ngoằng do Garbage Collection quét dọn có thể đẩy cái khóa trôi tới thời khắc hết hạn (expire) ngay lúc một thead vẫn còn đang è cổ chạy. Bắt buộc nhồi thêm một Watchdog Goroutine chạy lùi phía sau để chầu chực xin gia hạn vòng TTL của khóa hễ tác vụ vẫn còn đang dây dưa.
Có một điểm yếu chết chóc ít ai đồn thổi: Giả dụ cái khóa Redis của bạn chốt TTL đúng 10 giây. Thằng Goroutine của bạn ôm được khóa và hì hục cày ải trong 2 giây. Đùng một cái, Golang giáng xuống một chổi quét Garbage Collection khổng lồ (hoặc OS bị đói CPU) làm nguyên hệ thống tê liệt đóng băng 9 giây.
Tới hồi thoát khỏi đóng băng, cái khóa Redis kia đã hết hạn rục xương tự đời nào, và Redis liền vui vẻ cấp phát nó lại cho một thằng Goroutine mới toe. Thằng Goroutine bản gốc tỉnh cơn mê, hoàn toàn ngây ngô hổng biết khóa của nhà nó đã bị lột, vẫn hùng hục đi ghi đè xuống DB, tạo ra một màn hủy diệt không thể cứu vãn!
Phòng Ngự: Phải xài thủ đoạn Watchdog (Chó canh). Một Goroutine nền tảng đứng canh chừng giám sát cái thread đang cày ải kia. Nếu công việc chưa gút xong mà cái khóa sắp sửa thoi thóp hết hạn, gã Watchdog này phải chủ động bắn lệnh tự giác cơi nới thời hạn (extend TTL) dưới Redis một cách tự động.