1. Tại sao Redis lại sử dụng kiến trúc đơn luồng?
Theo tài liệu chính thức, Redis hoạt động chủ yếu trên bộ nhớ (RAM), do đó CPU hiếm khi là nút thắt cổ chai về hiệu năng. Hiệu suất của Redis thường bị giới hạn bởi dung lượng bộ nhớ vật lý và băng thông mạng. Việc thiết kế đơn luồng giúp Redis:
- Dễ dàng triển khai và bảo trì.
- Tránh được các vấn đề phức tạp về đồng bộ hóa dữ liệu (race conditions).
- Không tốn chi phí tài nguyên cho việc tranh chấp khóa (locking) hoặc chuyển ngữ cảnh (context switching) giữa các luồng.
Nếu muốn tận dụng tối đa sức mạnh của CPU nhiều nhân, giải pháp phổ biến là triển khai nhiều instance Redis trên cùng một server hoặc sử dụng mô hình Cluster để phân tải.
2. Bí quyết giúp Redis đạt tốc độ cực cao
- Lưu trữ In-memory: Mọi thao tác đọc ghi đều diễn ra trên RAM, loại bỏ độ trễ của ổ đĩa.
- Cấu trúc dữ liệu tối ưu: Các kiểu dữ liệu như String, Hash, List, Set, Sorted Set được thiết kế đặc biệt để thao tác nhanh nhất có thể.
- I/O Multiplexing: Redis sử dụng cơ chế epoll (trên Linux) để xử lý hàng ngàn kết nối đồng thời chỉ với một luồng duy nhất, giúp tối ưu hóa việc chờ đợi dữ liệu từ mạng.
3. Phân tích select, poll và epoll
Đây là các cơ chế mà hệ điều hành sử dụng để quản lý các kết nối mạng (socket). Khi dữ liệu đến card mạng, CPU sẽ nhận tín hiệu ngắt và xử lý để đưa dữ liệu vào bộ đệm của socket.
- Select: Duyệt qua toàn bộ danh sách các file descriptor (FD) để tìm xem cái nào có dữ liệu. Giới hạn mặc định thường là 1024 FD. Mỗi lần kiểm tra đều phải duyệt lại từ đầu, rất tốn kém khi số lượng kết nối lớn.
- Poll: Tương tự select nhưng không giới hạn số lượng FD (sử dụng danh sách liên kết hoặc mảng động). Tuy nhiên, nó vẫn mắc lỗi phải duyệt tuần tự toàn bộ danh sách.
- Epoll: Đây là bước đột phá. Thay vì duyệt toàn bộ, epoll đăng ký các FD vào một cấu trúc cây Đỏ-Đen (Red-Black Tree) trong nhân hệ điều hành. Khi một socket có dữ liệu, nó sẽ được đưa vào một "Ready List" (danh sách sẵn sàng). Ứng dụng chỉ cần xử lý các socket trong danh sách này, giúp hiệu năng không bị giảm sút khi số lượng kết nối tăng lên hàng triệu.
4. Giao dịch (Transaction) trong Redis
Transaction trong Redis là một tập hợp các lệnh được thực thi tuần tự và cách ly. Tuy nhiên, nó khác với cơ sở dữ liệu quan hệ ở chỗ không hỗ trợ Rollback hoàn toàn nếu xảy ra lỗi trong lúc thực thi lệnh (runtime error).
# Bắt đầu giao dịch
MULTI
# Đưa các lệnh vào hàng chờ (Queue)
SET user_balance 500
INCRBY user_balance 100
# Thực thi toàn bộ
EXEC
# Hủy giao dịch nếu cần
DISCARD
Nếu một lệnh bị lỗi cú pháp ngay từ đầu (compile error), toàn bộ giao dịch sẽ bị từ chối. Nếu lệnh bị lỗi khi đang chạy (ví dụ tăng giá trị của một chuỗi), các lệnh khác vẫn sẽ tiếp tục thực hiện.
5. Cơ chế giám sát với WATCH
Redis sử dụng WATCH để thực hiện cơ chế "Optimistic Locking" (Khóa lạc quan). Nó giám sát một hoặc nhiều key, nếu các key này bị thay đổi bởi một client khác trước khi lệnh EXEC được gọi, toàn bộ giao dịch sẽ thất bại.
WATCH account_stock
MULTI
DECRBY account_stock 1
EXEC
# Nếu account_stock bị thay đổi giữa lúc WATCH và EXEC, kết quả trả về sẽ là nil
6. Các chiến lược lưu trữ bền vững (Persistence)
Cơ chế Fork()
Để tránh việc ghi file làm treo luồng chính, Redis sử dụng lời gọi hệ thống fork() để tạo ra một tiến trình con. Tiến trình con này chia sẻ bộ nhớ với tiến trình cha thông qua cơ chế Copy-on-Write (COW), cho phép nó ghi dữ liệu ra đĩa mà không ảnh hưởng đến hiệu suất xử lý yêu cầu của tiến trình cha.
RDB (Redis Database)
Tạo bản chụp dữ liệu (snapshot) tại một thời điểm nhất định vào file nhị phân (ví dụ dump.rdb).
- Ưu điểm: File gọn nhẹ, khôi phục dữ liệu lớn rất nhanh.
- Nhược điểm: Dễ mất dữ liệu nếu sự cố xảy ra giữa hai lần snapshot.
AOF (Append Only File)
Ghi lại mọi lệnh làm thay đổi dữ liệu vào một file nhật ký.
- Ưu điểm: Bảo mật dữ liệu cao, gần như không mất dữ liệu (nếu cấu hình ghi mỗi giây).
- Nhược điểm: File thường lớn hơn RDB và tốc độ khôi phục chậm hơn.
# Cấu hình AOF cơ bản
appendonly yes
appendfilename "data_logs.aof"
appendfsync everysec
7. Mô hình Master-Slave và Sentinel
Master-Slave Replication: Master xử lý các lệnh ghi, sau đó đồng bộ dữ liệu xuống các Slave. Các Slave có nhiệm vụ xử lý lệnh đọc để giảm tải cho Master.
Redis Sentinel: Là hệ thống giám sát tính sẵn sàng cao. Nếu Master gặp sự cố, các Sentinel sẽ cùng bầu chọn một Slave lên làm Master mới một cách tự động.
# Cấu hình Sentinel đơn giản
sentinel monitor redis_master_node 127.0.0.1 6379 2
sentinel down-after-milliseconds redis_master_node 5000
sentinel failover-timeout redis_master_node 15000
8. Giải quyết các bài toán về Cache
Cache Penetration (Xuyên thấu)
Xảy ra khi truy vấn các key không tồn tại cả trong Cache lẫn Database, khiến mọi yêu cầu đều đập thẳng vào Database.
- Giải pháp: Sử dụng Bloom Filter để chặn các yêu cầu không hợp lệ ngay từ lớp ngoài, hoặc cache cả các kết quả rỗng (null) với thời gian hết hạn ngắn.
Cache Breakdown (Đánh thủng)
Xảy ra khi một key có lượng truy cập cực lớn (hot key) đột ngột hết hạn, khiến hàng triệu yêu cầu cùng lúc truy cập vào Database để lấy lại dữ liệu.
- Giải pháp: Thiết lập key không bao giờ hết hạn hoặc sử dụng Mutex Lock để chỉ cho phép một luồng đi lấy dữ liệu mới, các luồng khác phải chờ.
Cache Avalanche (Tuyết lở)
Xảy ra khi một lượng lớn key cùng hết hạn vào một thời điểm, hoặc khi hệ thống Redis bị sập, khiến Database bị quá tải đột ngột.
- Giải pháp: Thêm một khoảng thời gian ngẫu nhiên (random salt) vào thời gian hết hạn của mỗi key để tránh việc hết hạn tập trung. Triển khai cụm Redis Cluster để tăng tính chịu lỗi.