Table of contents
- 1. Tại sao Distributed Locking lại quan trọng trong kiến trúc Microservices?
- 2. Tại sao nên sử dụng Redis cho Distributed Locking?
- 3. Cách triển khai Distributed Lock cơ bản với Redis
- 4. Triển khai cải tiến: Dùng lệnh SET với các tùy chọn NX và EX
- 5. Redlock: Giải pháp phân tán an toàn
- 6. So sánh các cách tiếp cận
- 7. Best practice
- 8. Tổng kết
- 9. Tham khảo
Distributed Lock với Redis là một cơ chế khóa phân tán sử dụng Redis như một kho lưu trữ để điều phối và quản lý trạng thái khóa giữa nhiều client trong môi trường phân tán. Cơ chế này đảm bảo rằng tại một thời điểm, chỉ một client duy nhất có thể giữ khóa để thực thi tác vụ cụ thể.
Trong bài viết này, chúng ta sẽ tìm hiểu về tầm quan trọng của Distributed Locking trong kiến trúc Microservices và cách sử dụng Redis để triển khai một cơ chế Distributed Locking hiệu quả.
1. Tại sao Distributed Locking lại quan trọng trong kiến trúc Microservices?
Trong kiến trúc Microservices, ứng dụng được chia thành nhiều service độc lập, mỗi service có thể được triển khai trên nhiều instance khác nhau. Khi nhiều instance cùng truy cập vào một tài nguyên dùng chung, có thể xảy ra các vấn đề như race condition và data inconsistency.
Distributed Locking là một kỹ thuật giúp đảm bảo tính nhất quán và toàn vẹn dữ liệu bằng cách cho phép chỉ một instance được truy cập vào tài nguyên ở một thời điểm.
Một số usecase thường sử dụng Distributed Lock như:
Job Scheduling: Hệ thống phân tán thường bao gồm nhiều cron job, các cron job không nên được thực thi trên nhiều instance của service.
Ngăn chặn update đổng thời vào Database: Khi nhiều tiến trình update một bản ghi cơ sở dữ liệu cùng lúc, Distributed Lock sẽ đảm bảo rằng chỉ có một thao tác ghi xảy ra tại một thời điểm.
Ngăn chặn duplicate request: Vì nhiều lý do khác nhau như lỗi lập trình hoặc lỗi mạng mà request bị gửi duplicate. Nếu hai request giống nhau cùng gửi tới hệ thống thì Distributed Lock giúp reject request và chỉ xử lý một request còn lại.
2. Tại sao nên sử dụng Redis cho Distributed Locking?
Redis là một công cụ lưu trữ dữ liệu in-memory nhanh chóng và nhẹ, rất phổ biến trong việc triển khai Distributed Lock. Redis cung cấp cơ chế Distributed Lock giúp đảm bảo rằng chỉ một tiến trình có thể thiết lập giá trị cho một khóa nhất định nếu khóa đó chưa tồn tại. Redis rất thích hợp cho các hệ thống yêu cầu tốc độ cao, với độ trễ thấp và dễ dàng cấu hình. Tuy nhiên, cần phải xử lý cẩn thận các trường hợp đặc biệt như deadlock hoặc khóa bị hết hạn.
3. Cách triển khai Distributed Lock cơ bản với Redis
Redis hỗ trợ các câu lệnh giúp thiết lập và giải phóng khóa, ví dụ: SETNX và DEL.
SETNX: Đặt khóa nếu nó chưa tồn tại.
SETNX key value
Trả về:
1
nếu thiết lập khóa thành công.0
nếu khóa đã tồn tại.
TTL (Time-To-Live): Đặt thời gian tồn tại của khóa để tránh trường hợp client bị lỗi không giải phóng khóa.
EXPIRE key timeout
Ví dụ:
// Thiết lập khóa
boolean lockAcquired = jedis.setnx("lock_key", "lock_value") == 1;
if (lockAcquired) {
jedis.expire("lock_key", 30); // Đặt TTL là 30 giây
try {
// Thực hiện tác vụ quan trọng
} finally {
jedis.del("lock_key"); // Giải phóng khóa
}
} else {
System.out.println("Lock already held by another client.");
}
Vấn đề của cách triển khai cơ bản
Cách tiếp cận trên tồn tại một số vấn đề:
Race Condition (Điều kiện tranh chấp):
- Nếu client bị lỗi ngay sau khi gọi
SETNX
nhưng trước khi gọiEXPIRE
, khóa sẽ không có thời gian tồn tại và không bao giờ được giải phóng.
- Nếu client bị lỗi ngay sau khi gọi
Không đảm bảo tính an toàn trong môi trường phân tán:
- Khóa không được nhân rộng (replicated) hoặc kiểm tra nhất quán trên nhiều Redis instances
4. Triển khai cải tiến: Dùng lệnh SET với các tùy chọn NX và EX
Redis cung cấp lệnh SET với các tùy chọn NX và EX để thiết lập khóa một cách an toàn và hiệu quả.
Cú pháp:
bashCopy codeSET key value NX EX timeout
NX: Chỉ thiết lập nếu khóa chưa tồn tại.
EX timeout: Thiết lập TTL cho khóa.
Ví dụ:
String result = jedis.set("lock_key", "lock_value", "NX", "EX", 30); // TTL là 30 giây
if ("OK".equals(result)) {
try {
// Thực hiện tác vụ quan trọng
} finally {
jedis.del("lock_key"); // Giải phóng khóa
}
} else {
System.out.println("Lock already held by another client.");
}
Pros
- Đảm bảo khóa được giải phóng nếu được lấy thành công
Cons
- Không đảm bảo tính an toàn trong môi trường phân tán trong trường hợp có nhiều redis instances
5. Redlock: Giải pháp phân tán an toàn
Redlock là một thuật toán phân phối khóa (distributed lock) được thiết kế bởi Antirez (Salvatore Sanfilippo), người tạo ra Redis. Mục tiêu của Redlock là cung cấp một cơ chế khóa an toàn, đáng tin cậy trong môi trường phân tán, nơi có nhiều Redis instances hoạt động đồng thời.
Vấn đề Redlock giải quyết
Trong môi trường phân tán, việc triển khai khóa không chỉ yêu cầu tính đúng đắn mà còn phải đảm bảo:
Khóa không bị giữ vô thời hạn (deadlock): Nếu một nút giữ khóa gặp sự cố, khóa phải tự động được giải phóng.
Tính đồng bộ giữa các instances Redis: Đảm bảo rằng khóa thực sự "được giữ" trên đa số instances Redis.
Khả năng chịu lỗi (fault-tolerance): Nếu một số instances Redis gặp sự cố, hệ thống vẫn hoạt động bình thường.
Nguyên tắc hoạt động của Redlock
Redlock triển khai một cơ chế khóa phân phối dựa trên nhiều Redis instances. Các bước thực hiện như sau:
Bước 1: Yêu cầu khóa từ nhiều Redis instances
Giả sử có N Redis instances (thường là số lẻ, như 5).
Client gửi yêu cầu thiết lập khóa (SET key value NX PX timeout) đến từng instance Redis.
Bước 2: Thiết lập khóa với thời gian chờ (timeout)
Nếu một Redis instance đồng ý thiết lập khóa, nó lưu trữ khóa với một thời gian hết hạn (TTL) được xác định trước.
TTL đảm bảo rằng nếu Client không giải phóng khóa, khóa sẽ tự động hết hạn.
Bước 3: Kiểm tra điều kiện "đa số" (quorum)
Để khóa được xem là thành công, Client cần nhận được phản hồi đồng ý từ ít nhất (N/2 + 1) Redis instances (đa số).
Điều này giúp đảm bảo rằng hệ thống vẫn hoạt động ổn định ngay cả khi một số Redis instances không khả dụng.
Bước 4: Xác minh thời gian
Client tính toán tổng thời gian cần thiết để thiết lập khóa từ các instances.
Nếu thời gian thiết lập khóa vượt quá TTL của khóa, Client coi khóa không hợp lệ và hủy bỏ.
Bước 5: Sử dụng khóa
Nếu đạt được đa số (quorum) và thời gian hợp lệ, Client được coi là giữ khóa và có thể thực hiện các hành động cần thiết.
Sau khi hoàn thành, Client giải phóng khóa bằng cách gửi yêu cầu DEL key đến tất cả các Redis instances.
Bước 6: Xử lý lỗi
- Nếu không đạt được quorum hoặc thời gian không hợp lệ, Client sẽ giải phóng các khóa đã thiết lập (nếu có) và báo lỗi.
Yêu cầu để Redlock hoạt động đúng
Đồng hồ trên các instances Redis phải đáng tin cậy:
- TTL của khóa phụ thuộc vào độ chính xác của thời gian trên từng Redis instance.
Mỗi Redis instance hoạt động độc lập:
- Không yêu cầu đồng bộ giữa các Redis instances để tránh phức tạp hóa.
Client chịu trách nhiệm tái thiết lập khóa:
- Nếu Client mất kết nối hoặc gặp sự cố, nó cần đảm bảo rằng khóa không bị giữ vô thời hạn.
Pros
Khả năng chịu lỗi cao: Vẫn hoạt động ổn định nếu một số Redis instances gặp sự cố.
Khả năng mở rộng: Có thể thêm hoặc giảm số lượng Redis instances để phù hợp với quy mô hệ thống.
Hỗ trợ phân tán: Thích hợp cho các hệ thống lớn, phân tán trên nhiều khu vực.
Cons
Phức tạp hơn so với khóa đơn giản:
- Đòi hỏi Client phải thực hiện nhiều bước để kiểm tra và duy trì khóa.
Không hoàn toàn đảm bảo tính an toàn trong mọi tình huống:
- Trong một số điều kiện nhất định, Redlock có thể không hoàn toàn đảm bảo tính Mutual Exclusion (không hai Client nào có thể giữ cùng một khóa đồng thời).
Phụ thuộc vào đồng hồ hệ thống:
- Nếu các Redis instances có sự lệch thời gian đáng kể, có thể dẫn đến các vấn đề về TTL không chính xác.
Ví dụ hoạt động Redlock
Giả sử hệ thống có 5 Redis instances: Redis1
, Redis2
, Redis3
, Redis4
, Redis5
.
Client gửi yêu cầu khóa:
- Gửi yêu cầu SET key value NX PX timeout đến cả 5 instances.
Nhận phản hồi:
Redis1
vàRedis2
: Đồng ý.Redis3
: Không phản hồi.Redis4
: Đồng ý.Redis5
: Không phản hồi.
Quorum đạt được:
- Client nhận được đồng ý từ 3/5 instances (đa số).
Xác minh thời gian:
- Tổng thời gian yêu cầu khóa không vượt quá TTL, khóa được coi là hợp lệ.
Sử dụng khóa:
- Client thực hiện công việc của mình, sau đó gửi lệnh DEL key đến tất cả Redis instances để giải phóng khóa.
Dưới đây là một ví dụ triển khai Redlock sử dụng Redis để đảm bảo Distributed Lock hoạt động nhất quán trong môi trường phân tán. Chúng ta sẽ sử dụng thư viện Redisson, một thư viện phổ biến giúp quản lý Redis dễ dàng hơn.
Đầu tiên, thêm dependency của Redisson vào file pom.xml
:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.0</version>
</dependency>
Triển khai Redlock
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedisRedlockExample {
public static void main(String[] args) {
// Cấu hình Redisson Client
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// Tạo khóa Redlock
String lockKey = "distributed_lock";
RLock lock = redisson.getLock(lockKey);
try {
// Thử lấy khóa với thời gian chờ và TTL
boolean isLockAcquired = lock.tryLock(5, 10, java.util.concurrent.TimeUnit.SECONDS);
if (isLockAcquired) {
System.out.println("Lock acquired! Processing critical section...");
// Thực hiện tác vụ quan trọng
Thread.sleep(8000); // Giả sử tác vụ mất 8 giây
System.out.println("Task completed!");
} else {
System.out.println("Could not acquire the lock!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Giải phóng khóa
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Lock released!");
}
}
// Đóng kết nối Redisson
redisson.shutdown();
}
}
Kết quả khi chạy:
- TH tiến trình lấy được khóa
Lock acquired! Processing critical section...
Task completed!
Lock released!
- TH tiến trình không lấy được khóa
Could not acquire the lock!
Lưu ý:
Redlock đặc biệt hữu ích trong các hệ thống phân tán hoặc khi cần đảm bảo tính đồng bộ chặt chẽ giữa các tiến trình trên nhiều máy.
Redis nên được triển khai trong Cluster Mode để đảm bảo tính sẵn sàng cao và tránh tình trạng một điểm lỗi duy nhất (Single Point of Failure).
6. So sánh các cách tiếp cận
Cách tiếp cận | Ưu điểm | Nhược điểm |
Sử dụng SETNX và EXPIRE | Đơn giản, dễ triển khai. | Dễ gặp race condition, không đảm bảo tính phân tán. |
Sử dụng SET với NX và EX | An toàn hơn, không gặp race condition. | Không tự động mở rộng cho môi trường phân tán. |
Redlock | Đảm bảo tính phân tán và an toàn. | Phức tạp hơn, phụ thuộc vào đồng bộ thời gian giữa các Redis. |
Redisson (hoặc thư viện khác) | Dễ sử dụng, hỗ trợ nhiều loại khóa khác nhau (reentrant, fair lock, ...). | Phụ thuộc vào thư viện, hiệu suất có thể thấp hơn triển khai trực tiếp. |
7. Best practice
Để tối ưu hóa hiệu suất của hệ thống, đảm bảo tính nhất quán dữ liệu và khả năng chịu lỗi trong môi trường phân tán cũng như tránh các rủi ro của locking như deadlock, người dùng cần:
Đặt thời gian hết hạn (TTL) hợp lý cho khóa (không nên đặt quá lâu).
Luôn unlock khóa khi kết thúc logic xử lý hoặc có bất kỳ lỗi nào xảy ra.
Nên sử dụng distributed lock đúng nơi, đúng chỗ, tránh lạm dụng distributed lock vì có thể khiến hệ thống tăng độ trễ khi xử lý.
Nên start một transaction bên trong distributed lock.
Triển khai Redis Cluster để đảm bảo tính sẵn sàng (high availability) và tăng độ tin cậy (reliability).
8. Tổng kết
Distributed Lock là giải pháp thiết yếu để đảm bảo tính toàn vẹn dữ liệu, tránh race condition và thường được sử dụng trong các bài toán như job scheduling, ngăn chặn duplicate request và xử lý tuần tự logic cập nhật dữ liệu.
Redis-based Locking là lựa chọn phổ biến nhờ tốc độ nhanh và khả năng xử lý các thao tác dữ liệu mạnh mẽ. Có hai cách tiếp cận chính là sử dụng lệnh SET với tham số NX và thư viện Redisson. Mỗi phương pháp đều có sự đánh đổi riêng và chọn phương pháp nào tùy thuộc vào yêu cầu cụ thể của hệ thống.
Sử dụng distributed lock đúng chỗ và luôn giải phóng khóa ngay khi kết thúc logic hoặc có lỗi xảy ra.
Đến đây là kết thúc bài viết rồi. Dù nội dung khá dài, mình hy vọng những chia sẻ trên đây sẽ mang đến cho bạn những kiến thức mới mẻ và giá trị, giúp bạn áp dụng hiệu quả vào các dự án thực tế.
Hẹn gặp lại các bạn trong các bài viết sắp tới nhé.
Happy reading! 🍻