0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Xây Dựng Hệ Thống Cache Linh Hoạt: Hướng Dẫn Thiết Kế Chi Tiết

Đăng vào 1 tháng trước

• 4 phút đọc

Giới thiệu

Caching là một kỹ thuật không thể thiếu để xây dựng các hệ thống hiệu suất caomở rộng. Tuy nhiên, việc triển khai một hệ thống cache hỗ trợ nhiều chiến lược ghi, chính sách loại bỏ, và cơ chế vô hiệu hóa không phải là điều đơn giản. Bài viết này sẽ hướng dẫn bạn qua một thiết kế Cấp Thấp (LLD) hoàn chỉnh của một hệ thống cache trong Java, sử dụng các mẫu thiết kế đã được chứng minh để làm cho nó trở nên mô-đun, có thể mở rộng và sẵn sàng cho sản xuất.

Mục Tiêu Thiết Kế

Chúng ta cần một hệ thống cache hỗ trợ:

  • Nhiều chiến lược ghi: write-through, write-back, write-around
  • Nhiều chính sách loại bỏ: LRU, LFU, TTL
  • Cơ chế vô hiệu hóa: thông báo dựa trên listener
  • Trừu tượng hóa lưu trữ: bộ nhớ tạm, cơ sở dữ liệu hoặc kho lưu trữ bên ngoài
  • Thống kê: hit và miss của cache
  • Khả năng mở rộng thông qua các giao diện và mẫu thiết kế sạch

Các Mẫu Thiết Kế Sử Dụng

  1. Mẫu Chiến Lược → cho các chiến lược loại bỏ và ghi
  2. Mẫu Factory + Builder → cho việc tạo cache với cấu hình linh hoạt
  3. Mẫu Observer → cho các listener vô hiệu hóa
  4. Mẫu Singleton → cho thống kê
  5. Mẫu Decorator → cho việc loại bỏ dựa trên TTL và bao quanh các chính sách khác

Các Giao Diện Chính

Chúng ta định nghĩa các hợp đồng chính:

  • Cache<K, V> → các thao tác chính của cache
  • EvictionPolicy<K> → cách các mục bị loại bỏ
  • WriteStrategy<K, V> → cách ghi được duy trì
  • Persistence<K, V> → trừu tượng cho lưu trữ cơ bản
  • InvalidationListener<K> → observer cho các sự kiện loại bỏ/vô hiệu hóa

Chính Sách Loại Bỏ

  • LRU (Least Recently Used): Loại bỏ các mục ít được truy cập nhất trước.
  • LFU (Least Frequently Used): Loại bỏ các mục ít được sử dụng nhất.
  • TTL Decorator: Bao quanh chính sách khác và thực thi thời gian sống.

Chính sách này sử dụng Mẫu Chiến Lược để bạn có thể thay đổi chính sách mà không cần thay đổi logic chính.

Chiến Lược Ghi

  • Write-Through: Ghi đồng thời vào cache và lưu trữ.
  • Write-Back (Write-Behind): Ghi vào cache trước, lưu trữ không đồng bộ.
  • Write-Around: Ghi trực tiếp vào lưu trữ; cache chỉ được cập nhật khi đọc.

Điều này cho phép bạn điều chỉnh giữa tính nhất quán và hiệu suất.

Vô Hiệu Hóa & Người Quan Sát

Chúng tôi hỗ trợ listeners để thông báo khi các khóa bị vô hiệu hóa (ví dụ, bị loại bỏ). Điều này hữu ích cho việc giữ cho nhiều cache đồng bộ hoặc cho ghi log bên ngoài.

Lớp Lưu Trữ

Chúng tôi trừu tượng hóa lưu trữ để bạn có thể cắm vào:

  • Bản đồ trong bộ nhớ (cho demo/testing)
  • Cơ sở dữ liệu
  • Cache phân phối như Redis

Điểm Nổi Bật Trong Triển Khai

Dưới đây là mã đã được cấu trúc (tổng quan đơn giản):

java Copy
interface Cache<K, V> { V get(K key); void put(K key, V value); void remove(K key); }
interface EvictionPolicy<K> { void recordAccess(K key); K evictKey(); }
interface WriteStrategy<K, V> { void write(K key, V value, Persistence<K, V> p); }
interface Persistence<K, V> { void write(K k, V v); Optional<V> read(K k); }

class SimpleCache<K, V> implements Cache<K, V> { /* kết nối các chiến lược & chính sách */ }

class LruEvictionPolicy<K> implements EvictionPolicy<K> { /* sử dụng linked hash set */ }
class TtlEvictionDecorator<K> implements EvictionPolicy<K> { /* thêm thời gian hết hạn */ }

class WriteThroughStrategy<K, V> implements WriteStrategy<K, V> { /* đồng bộ */ }
class WriteBackStrategy<K, V> implements WriteStrategy<K, V> { /* không đồng bộ */ }

class InMemoryPersistence<K, V> implements Persistence<K, V> { /* sử dụng bản đồ */ }

Factory & Cấu Hình

Chúng tôi thêm một CacheFactory với một builder CacheConfig để đơn giản hóa việc tạo:

java Copy
CacheConfig cfg = new CacheConfig();
cfg.capacity = 1000;
cfg.policy = new LruEvictionPolicy<>(cfg.capacity);
cfg.writeStrategy = new WriteThroughStrategy<>();
cfg.persistence = new InMemoryPersistence<>();

Cache<String, String> cache = CacheFactory.create(cfg);

Điều này đảm bảo một API sạch cho khách hàng.

Ví Dụ Sử Dụng

java Copy
cache.put("a", "1");
cache.put("b", "2");
System.out.println(cache.get("a")); // hit
System.out.println(cache.get("c")); // miss → tải từ lưu trữ

Chúng ta cũng có thể gán listeners:

java Copy
((SimpleCache<String, String>) cache).addInvalidationListener(key ->
    System.out.println("Vô hiệu hóa: " + key));

Thống Kê

Chúng tôi theo dõi hits và misses với một lớp Singleton CacheMetrics:

java Copy
System.out.println("Hits=" + CacheMetrics.get().hits());
System.out.println("Misses=" + CacheMetrics.get().misses());

Tổng Hợp

  • Sử dụng LRU + Write-Through cho tính nhất quán mạnh.
  • Sử dụng LFU + Write-Back cho hiệu suất trong khối lượng đọc nặng.
  • Sử dụng TTL + Write-Around cho dữ liệu nhạy cảm với độ mới.

Kết Luận

Bằng cách áp dụng các mẫu thiết kế và trừu tượng hóa mô-đun, chúng ta đã xây dựng một hệ thống cache có thể cắm vào và mở rộng. Hệ thống này cho phép bạn:

  • Thay đổi các chính sách loại bỏ
  • Chọn các chiến lược ghi dựa trên tính nhất quán và hiệu suất
  • Thêm các listener cho việc vô hiệu hóa
  • Mở rộng các lớp lưu trữ

Thiết kế này có thể mở rộng từ một cache trong bộ nhớ đơn giản đến một hệ thống phân phối với lưu trữ trong cơ sở dữ liệu hoặc Redis.

👉 Bước tiếp theo: chúng ta có thể mở rộng điều này với hỗ trợ cache phân phối, triển khai LFU thực sự, và tích hợp Spring Boot với Redis.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào