Kiến trúc hệ thốngBộ nhớ đệm (Cache)

Bộ nhớ đệm (Cache)

Nội dung bài

Bộ nhớ đệm (Cache)

Chủ đề16 phút đọcThe System Design Primer - mục "Cache"

Mục lục
  1. Nội dung gốc
  2. Ghi chú của người dịch

Bộ nhớ đệm (Cache)

Nội dung gốc

Sơ đồ bộ điều phối tra cache trước khi thực thi request
Nguồn: Scalable system design patterns

Bộ nhớ đệm (caching) giúp cải thiện thời gian tải trang và có thể giảm tải cho máy chủ và cơ sở dữ liệu. Trong mô hình này, bộ điều phối (dispatcher) trước hết sẽ tra xem request này đã từng được thực hiện chưa và cố tìm kết quả trước đó để trả về, nhằm tiết kiệm việc thực thi thật.

Luồng cache hit và cache miss Client Cache DB yêu cầu miss Hit – trả ngay nạp + lưu vào cache
Khi dữ liệu có sẵn trong cache (hit), trả ngay; khi không (miss), lấy từ DB rồi lưu lại vào cache để lần sau nhanh hơn.

Cơ sở dữ liệu thường hoạt động tốt nhất khi các thao tác đọc và ghi được phân bố đều trên các phân vùng (partition) của nó. Những mục phổ biến (popular items) có thể làm lệch sự phân bố này, gây ra nút thắt cổ chai. Đặt một cache phía trước cơ sở dữ liệu có thể giúp hấp thụ tải không đều và các đợt tăng đột biến lưu lượng.

Cache phía client (Client caching)

Cache có thể nằm ở phía client (hệ điều hành hoặc trình duyệt), phía máy chủ, hoặc ở một tầng cache riêng biệt.

Cache ở CDN (CDN caching)

CDN được xem là một loại cache.

Cache ở máy chủ web (Web server caching)

Reverse proxy và các cache như Varnish có thể phục vụ trực tiếp nội dung tĩnh và động. Máy chủ web cũng có thể cache các request, trả về response mà không cần liên hệ với máy chủ ứng dụng.

Cache ở cơ sở dữ liệu (Database caching)

Cơ sở dữ liệu của bạn thường đã có sẵn một mức cache nào đó trong cấu hình mặc định, được tối ưu cho trường hợp sử dụng chung chung. Tinh chỉnh các thiết lập này cho các kiểu sử dụng cụ thể có thể tăng hiệu năng thêm nữa.

Cache ở tầng ứng dụng (Application caching)

Các cache trong bộ nhớ (in-memory cache) như Memcached và Redis là các kho key-value nằm giữa ứng dụng và kho dữ liệu của bạn. Vì dữ liệu được giữ trong RAM, chúng nhanh hơn nhiều so với cơ sở dữ liệu thông thường, nơi dữ liệu được lưu trên đĩa. RAM hạn chế hơn đĩa, nên các thuật toán vô hiệu hóa cache (cache invalidation) như ít được dùng gần đây nhất (least recently used - LRU) có thể giúp loại bỏ các mục "nguội" (cold) và giữ dữ liệu "nóng" (hot) trong RAM.

TTL hết hạn theo thời gian và loại bỏ theo LRU khi cache đầy TTL — hết hạn theo thời gian ghi lúc t0 còn hạn (TTL) hết hạn đọc sau đó → miss, nạp lại Hết chỗ — loại theo LRU D vào, đẩy A ra A B C D mới lâu chưa dùng nhất
Mỗi mục cache có một 'hạn dùng' (TTL); khi cache đầy, mục lâu chưa dùng nhất (LRU) bị loại trước để nhường chỗ cho mục mới.

Redis có thêm các tính năng sau:

Có nhiều mức bạn có thể cache, chia thành hai nhóm chung: truy vấn cơ sở dữ liệu (database queries) và đối tượng (objects):

Nhìn chung, bạn nên tránh cache dựa trên file, vì nó khiến việc nhân bản (cloning) và tự động mở rộng (auto-scaling) khó khăn hơn.

Cache ở mức truy vấn cơ sở dữ liệu

Mỗi khi truy vấn cơ sở dữ liệu, hãy băm (hash) câu truy vấn làm khóa và lưu kết quả vào cache. Cách này gặp vấn đề về hết hạn (expiration):

Cache ở mức đối tượng

Hãy nhìn dữ liệu của bạn như một đối tượng, tương tự cách bạn làm với mã ứng dụng. Để ứng dụng lắp ráp tập dữ liệu từ cơ sở dữ liệu thành một thể hiện của lớp (class instance) hoặc một (hay nhiều) cấu trúc dữ liệu:

Gợi ý những thứ nên cache:

Khi nào cập nhật cache

Vì bạn chỉ có thể lưu một lượng dữ liệu giới hạn trong cache, bạn cần xác định chiến lược cập nhật cache nào phù hợp nhất với trường hợp sử dụng của mình.

Cache-aside

Sơ đồ mẫu cache-aside
Nguồn: From cache to in-memory data grid

Ứng dụng chịu trách nhiệm đọc và ghi vào kho lưu trữ. Cache không tương tác trực tiếp với kho lưu trữ. Ứng dụng làm như sau:

def get_user(self, user_id):
    user = cache.get("user.{0}", user_id)
    if user is None:
        user = db.query("SELECT * FROM users WHERE user_id = {0}", user_id)
        if user is not None:
            key = "user.{0}".format(user_id)
            cache.set(key, json.dumps(user))
    return user

Memcached thường được dùng theo cách này.

Các lần đọc tiếp theo đối với dữ liệu đã được thêm vào cache sẽ nhanh. Cache-aside còn được gọi là tải lười (lazy loading). Chỉ dữ liệu được yêu cầu mới được cache, nhờ đó tránh làm đầy cache bằng dữ liệu không ai yêu cầu.

Nhược điểm: cache-aside

Write-through

Sơ đồ mẫu write-through
Nguồn: Scalability, availability, stability, patterns

Ứng dụng dùng cache làm kho dữ liệu chính, đọc và ghi dữ liệu vào đó, còn cache chịu trách nhiệm đọc và ghi vào cơ sở dữ liệu:

Mã ứng dụng:

set_user(12345, {"foo":"bar"})

Mã cache:

def set_user(user_id, values):
    user = db.query("UPDATE Users WHERE id = {0}", user_id, values)
    cache.set(user_id, user)

Nhìn tổng thể, write-through là một thao tác chậm do phải ghi, nhưng các lần đọc tiếp theo đối với dữ liệu vừa ghi sẽ nhanh. Người dùng thường chấp nhận độ trễ khi cập nhật dữ liệu hơn là khi đọc dữ liệu. Dữ liệu trong cache không bị cũ.

So sánh cache-aside và write-through: ai nói chuyện với DB Cache-aside — ứng dụng tự đọc/ghi cả hai Ứng dụng Cache DB đọc/ghi đọc DB khi miss · ghi DB trực tiếp Write-through — ứng dụng chỉ biết Cache Ứng dụng Cache DB đọc/ghi cache tự ghi xuống DB (đồng bộ)
Cache-aside: ứng dụng tự đọc/ghi cả cache lẫn DB. Write-through: ứng dụng chỉ nói chuyện với cache, cache tự đồng bộ xuống DB.
Nhược điểm: write-through

Write-behind (write-back)

Sơ đồ mẫu write-behind
Nguồn: Scalability, availability, stability, patterns

Với write-behind, ứng dụng làm như sau:

Nhược điểm: write-behind

Refresh-ahead

Sơ đồ mẫu refresh-ahead
Nguồn: From cache to in-memory data grid

Bạn có thể cấu hình cache để tự động làm mới bất kỳ mục cache nào vừa được truy cập gần đây trước khi nó hết hạn.

Refresh-ahead có thể giúp giảm độ trễ so với read-through nếu cache dự đoán chính xác được những mục nào có khả năng sẽ cần tới trong tương lai.

Nhược điểm: refresh-ahead

Nhược điểm: cache

Nguồn và đọc thêm


Nguồn: The System Design Primer - mục "Cache" — Donne Martin và cộng đồng đóng góp

Giấy phép: CC BY 4.0 (nguyên bản: Donne Martin, The System Design Primer)

Xem bản gốc

Bài tiếp theoBất đồng bộ (Asynchronism)