Các Chiến lược Bộ nhớ Đệm Phân tán (Distributed Caching)
Toàn tập về kiến trúc bộ nhớ đệm đa tầng, các chiến lược đọc/ghi (Cache-Aside, Read-Through, Write-Through, Write-Behind), chính sách trục xuất khóa và giám sát tỷ lệ trúng đệm.
Kiến trúc Bộ nhớ Đệm Đa tầng
Nguyên lý tối ưu hóa bậc cao nhất đối với tầng trạng thái là tránh truy cập vào cơ sở dữ liệu gốc ngay từ đầu. Một hệ sinh thái web hiện đại triển khai bộ nhớ đệm xuyên suốt qua các tầng:
- Client Cache: HTTP Cache trình duyệt, Service Worker, localStorage, IndexedDB.
- Mạng Phân phối Biên (CDN): Đệm nội dung tĩnh (ảnh, video, JS/CSS) và trang HTML tĩnh gần vị trí người dùng nhất (Cloudflare, Akamai).
- Reverse Proxy Caching: Varnish, Nginx, Squid nằm trước cụm ứng dụng, phục vụ hàng chục nghìn RPS từ RAM.
- Application Distributed Cache: Cụm Redis, Memcached, Hazelcast, Oracle Coherence lưu trữ cấu trúc đối tượng, token phiên và kết quả truy vấn.
- Database Internal Cache: Buffer pool, Query plan cache của chính hệ quản trị RDBMS.
- Hệ điều hành: OS Page Cache trong nhân Linux.
Năm Chiến lược Đọc/Ghi Bộ nhớ Đệm Phổ biến
Jonas Bonér phân tích chi tiết cơ chế tương tác giữa Ứng dụng, Bộ nhớ đệm và Cơ sở dữ liệu gốc (Service of Record):
| Chiến lược | Cơ chế Hoạt động | Ưu điểm | Sự Đánh đổi & Rủi ro |
|---|---|---|---|
| 1. Cache-Aside (Lazy Loading) | Ứng dụng tự kiểm tra Cache; trượt thì đọc DB, ghi vào Cache kèm TTL rồi trả kết quả. | Linh hoạt, đơn giản, chỉ đệm dữ liệu thực sự được yêu cầu. | 3 vòng truyền thông khi miss; có nguy cơ đọc dữ liệu cũ nếu DB bị cập nhật trực tiếp. |
| 2. Read-Through | Ứng dụng chỉ gọi tới Cache; module Cache Loader tự động kéo dữ liệu từ DB khi miss. | Gọn mã nguồn ứng dụng, chuẩn hóa logic nạp dữ liệu. | Đòi hỏi thư viện cache hoặc proxy hỗ trợ custom data provider. |
| 3. Write-Through | Ghi trực tiếp qua Cache; Cache ghi xuống DB đồng bộ như 1 giao dịch nguyên tử. | Dữ liệu trong Cache luôn tươi mới; đảm bảo nhất quán mạnh giữa Cache và DB. | Độ trễ ghi cao (slower writes) vì phải hoàn tất ghi trên cả hai hệ thống. |
| 4. Write-Behind (Write-Back) | Ghi vào Cache và báo thành công ngay; Cache gom lô (batch) và ghi bất đồng bộ xuống DB. | Tốc độ ghi cực nhanh; hấp thụ hoàn hảo các đợt tăng vọt lưu lượng ghi (write spikes). | Rủi ro mất dữ liệu nếu nút Cache bị sập trước khi dữ liệu kịp ghi bền vững xuống DB. |
| 5. Refresh-Ahead | Tự động nạp mới các khóa dữ liệu nóng ở dưới nền ngay trước khi TTL hết hạn. | Triệt tiêu độ trễ đột biến do trượt cache đối với các khóa có tần suất truy cập cao. | Lãng phí tài nguyên nếu dự đoán sai các khóa không còn được dùng tiếp. |
Tác động Định lượng của Cache Hit Ratio
Mối quan hệ giữa Cache Hit Ratio và lưu lượng thực tế đè nặng lên cơ sở dữ liệu gốc:
Phương trình Tải Cơ sở Dữ liệu:
Nếu hệ thống tiếp nhận 100,000 RPS và tầng Cache đạt tỷ lệ trúng 95%, cơ sở dữ liệu chỉ còn phải gánh 5,000 RPS — giải phóng tới 95% năng lực hệ thống mà không cần nâng cấp phần cứng DB!
Tải Cơ sở Dữ liệu Còn lại (%) theo Tỷ lệ Trúng Bộ nhớ Đệm (%):
Một tỷ lệ trúng trung bình 99% toàn cục (do đệm hàng triệu ảnh tĩnh) có thể che giấu việc truy vấn kiểm tra quyền thanh toán bị trượt 100%, làm tê liệt hoàn toàn cơ sở dữ liệu. Do đó, phải đo lường Cache Hit/Miss riêng biệt cho từng luồng nghiệp vụ trọng yếu.
Chính sách Trục xuất và Vô hiệu hóa Bộ nhớ Đệm
Vì bộ nhớ RAM có giới hạn, việc quản lý vòng đời khóa đòi hỏi các chiến lược chặt chẽ:
- TTL (Time-To-Live): Tự động hết hạn sau một khoảng thời gian cố định. Thích hợp cho dữ liệu có tính biến động thấp.
- Trục xuất khi đầy RAM (Eviction Policies):
- LRU (Least Recently Used): Trục xuất phần tử lâu nhất không được truy cập.
- LFU (Least Frequently Used): Trục xuất phần tử có tần suất truy cập thấp nhất.
- Bounded FIFO / LIFO: Trục xuất theo hàng đợi hoặc ngăn xếp có giới hạn kích thước.
- Vô hiệu hóa Tường minh (Explicit Invalidation):
- Version-based Keys: Đổi tên khóa dạng
post:123:v1sangpost:123:v2khi có cập nhật, đặc biệt hiệu quả trên CDN vì tránh phải purge cache. - Event-based: Lắng nghe sự kiện từ hàng đợi EDA để xóa khóa ngay khi bản ghi tương ứng bị sửa.
- Version-based Keys: Đổi tên khóa dạng
Triển khai Cache-Aside và Chống Dog-piling
Hiện tượng Cache Stampede / Dog-piling xảy ra khi một khóa nóng (hot key) hết hạn đột ngột, khiến hàng nghìn yêu cầu đồng thời cùng chọc thẳng vào database để tính toán. Đoạn mã dưới đây sử dụng khóa phân tán nguyên tử (atomic lock) để đảm bảo chỉ 1 tiến trình được nạp dữ liệu:
import redis
import json
import time
r = redis.Redis(host='localhost', port=6379, db=0)
def get_user_profile(user_id):
cache_key = f"user:profile:{user_id}"
# 1. Kiểm tra trong Cache
cached_data = r.get(cache_key)
if cached_data:
return json.loads(cached_data) # Cache Hit!
# 2. Cache Miss: Khóa phân tán chống Cache Stampede (Dog-piling)
lock_key = f"lock:{cache_key}"
if r.set(lock_key, "1", nx=True, ex=5):
try:
# 3. Đọc từ Database gốc
profile = db_query_user(user_id)
# 4. Ghi ngược vào Cache kèm TTL (Time To Live = 15 phút)
r.setex(cache_key, 900, json.dumps(profile))
return profile
finally:
r.delete(lock_key)
else:
# Chờ luồng đầu tiên nạp xong và thử lại từ cache
time.sleep(0.05)
return get_user_profile(user_id)Các Trang Liên quan
- Nền tảng Thiết kế Hệ thống và Các Đánh đổi Cốt lõi — Đánh đổi giữa Độ trễ và Thông lượng.
- Định lý CAP, Mô hình ACID và BASE — Mô hình nhất quán Read-Your-Writes và PACELC.
- Phân mảnh RDBMS, DHT và Hệ sinh thái NoSQL — Tầng lưu trữ gốc (Service of Record) phía sau lớp đệm.
- Các Mẫu Thiết kế Độ Ổn định Hệ thống — Mẫu Steady State giúp tự động giới hạn dung lượng bộ nhớ đệm, chống tràn RAM (OOM).