Sử dụng Guava Cache trong dự án thực tế

Để tích hợp Guava Cache vào dự án, trước tiên cần thêm phụ thuộc sau vào file cấu hình:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>${version.guava}</version>
</dependency>

Trong Guava Cache, có hai cách chính để xử lý việc tải dữ liệu: thông qua CacheLoader hoặc Callable.

Sử dụng CacheLoader

LoadingCache được xây dựng từ CacheLoader và hỗ trợ tự động tải giá trị khi không tìm thấy trong bộ nhớ đệm. Để tạo một instance LoadingCache, bạn cần triển khai phương thức V load(K key) như ví dụ dưới đây:

LoadingCache<String, User> userCache = CacheBuilder.newBuilder()
        .maximumSize(500)
        .expireAfterWrite(15, TimeUnit.MINUTES)
        .removalListener(notification -> {
            // Xử lý sự kiện xóa
        })
        .build(new CacheLoader<String, User>() {
            @Override
            public User load(String userId) throws Exception {
                return fetchUserDataFromDatabase(userId);
            }
        });

Việc truy xuất và lưu trữ dữ liệu sẽ được thực hiện như sau:

public static User getUser(String id) {
    try {
        return userCache.get(id);
    } catch (ExecutionException e) {
        throw new RuntimeException("Lỗi khi lấy dữ liệu", e.getCause());
    }
}

public static void saveUser(String id, User user) {
    userCache.put(id, user);
}

Sử dụng Callable

Khi không sử dụng CacheLoader, bạn có thể cung cấp một Callable để tải dữ liệu theo yêu cầu khi khóa không tồn tại:

Cache<String, String> dataCache = CacheBuilder.newBuilder()
        .maximumSize(200)
        .build();

try {
    String result = dataCache.get("key", () -> {
        return performComplexComputation("key");
    });
} catch (ExecutionException e) {
    throw new RuntimeException("Thao tác thất bại", e.getCause());
}

Chèn dữ liệu thủ công

Dữ liệu có thể được chèn trực tiếp vào cache bằng cache.put(key, value), thay thế giá trị cũ nếu tồn tại. Ngoài ra, bạn cũng có thể dùng cache.asMap() để thao tác với bản đồ nội bộ. Tuy nhiên, các thao tác trên asMap() không đảm bảo tính nguyên tử khi tải dữ liệu, do đó nên ưu tiên sử dụng get(key, Callable) thay vì asMap().putIfAbsent(key, value) khi cần đảm bảo logic tải dữ liệu đúng.

Quản lý bộ nhớ đệm

1. Hạn chế theo kích thước

Đặt giới hạn số lượng mục trong cache bằng maximumSize(long) để tránh bùng nổ bộ nhớ. Cache sẽ tự động loại bỏ các mục ít được truy cập nhất.

Nếu các mục có kích thước khác nhau, có thể định nghĩa trọng số riêng bằng weigher(Weigher) và giới hạn tổng trọng số bằng maximumWeight(long):

LoadingCache<String, DataItem> itemCache = CacheBuilder.newBuilder()
        .maximumWeight(10_000)
        .weigher((String key, DataItem item) -> item.getSizeInBytes())
        .build(new CacheLoader<String, DataItem>() {
            @Override
            public DataItem load(String key) {
                return fetchDataFromRemote(key);
            }
        });

Lưu ý: Trọng số được tính tại thời điểm thêm vào cache, do đó hàm weigher cần hiệu quả về mặt thời gian.

2. Hạn chế theo thời gian

Guava hỗ trợ hai cơ chế thu hồi theo thời gian:

  • expireAfterAccess(long, TimeUnit): Các mục bị loại bỏ nếu không được truy cập trong khoảng thời gian đã thiết lập.
  • expireAfterWrite(long, TimeUnit): Các mục bị loại bỏ nếu không được ghi (tạo hoặc cập nhật) trong khoảng thời gian nhất định — phù hợp với trường hợp dữ liệu trở nên lỗi thời sau một thời gian cố định.

Ví dụ:

Cache<String, Object> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(30, TimeUnit.MINUTES)
        .expireAfterAccess(10, TimeUnit.MINUTES)
        .maximumSize(1000)
        .build();

Cả hai cơ chế này đều hoạt động độc lập và có thể kết hợp để tối ưu hóa hiệu suất và độ chính xác của dữ liệu trong cache.

Thẻ: Guava Cache LoadingCache CacheLoader Callable

Đăng vào ngày 16 tháng 9 lúc 22:31