Đảm bảo tính nhất quán dữ liệu giữa Redis và MySQL bằng cơ chế cập nhật chủ động bộ nhớ đệm

Để giải quyết vấn đề nhất quán dữ liệu giữa cơ sở dữ liệu và bộ nhớ đệm, hệ thống áp dụng chiến lược cập nhật chủ động bộ nhớ đệm, kết hợp với mô hình xóa bộ nhớ đệm trước khi ghi vào cơ sở dữ liệu (dual-write with cache invalidation), đồng thời bao bọc cả hai thao tác — cập nhật MySQL và xóa Redis — trong cùng một giao dịch để đảm bảo tính nguyên tử.

Yêu cầu chức năng

  • Khi truy vấn thông tin cửa hàng theo id: nếu không tìm thấy trong bộ nhớ đệm, thực hiện truy vấn cơ sở dữ liệu, sau đó lưu kết quả vào Redis kèm thời gian sống (TTL).
  • Khi cập nhật thông tin cửa hàng: đầu tiên cập nhật bản ghi trong MySQL, sau đó vô hiệu hóa (xóa) bản ghi tương ứng trong Redis.

Cài đặt chi tiết

1. Cập nhật cửa hàng — đảm bảo tính nguyên tử qua transaction

Dưới đây là phương thức cập nhật thông tin cửa hàng, sử dụng @Transactional để ràng buộc toàn bộ thao tác vào một giao dịch duy nhất:

@Override
@Transactional
public Result updateStore(Store store) {
    if (store.getId() == null) {
        return Result.fail("Mã cửa hàng không được để trống");
    }

    // Bước 1: Cập nhật dữ liệu trong MySQL
    this.baseMapper.updateById(store);

    // Bước 2: Xóa bản ghi tương ứng trong Redis để tránh dữ liệu lỗi thời
    String cacheKey = STORE_CACHE_PREFIX + store.getId();
    stringRedisTemplate.delete(cacheKey);

    return Result.ok();
}

2. Truy vấn cửa hàng — xử lý cả cache hit, cache miss và cache penetration

Hàm sau không chỉ phục vụ truy vấn mà còn ngăn chặn cache penetration bằng cách lưu giá trị null tạm thời với TTL ngắn khi đối tượng không tồn tại:

public Store fetchStore(Long id) {
    String key = STORE_CACHE_PREFIX + id;
    String cachedJson = stringRedisTemplate.opsForValue().get(key);

    // Trường hợp cache hit: trả về dữ liệu đã deserialize
    if (StrUtil.isNotBlank(cachedJson)) {
        return JSONUtil.toBean(cachedJson, Store.class);
    }

    // Trường hợp cache chứa chuỗi rỗng → đã từng xác minh "không tồn tại"
    if (cachedJson != null && cachedJson.isEmpty()) {
        return null;
    }

    // Trường hợp cache miss: truy vấn cơ sở dữ liệu
    Store store = this.baseMapper.selectById(id);

    if (store == null) {
        // Ghi giá trị null vào Redis với TTL ngắn (ví dụ: 120s) để chống lặp lại truy vấn vô ích
        stringRedisTemplate.opsForValue().set(key, "", NULL_ENTRY_TTL, TimeUnit.SECONDS);
        return null;
    }

    // Lưu kết quả hợp lệ vào Redis với TTL dài hơn (ví dụ: 1800s)
    String json = JSONUtil.toJsonStr(store);
    stringRedisTemplate.opsForValue().set(key, json, VALID_ENTRY_TTL, TimeUnit.SECONDS);

    return store;
}

Các hằng số được định nghĩa như sau:

  • STORE_CACHE_PREFIX = "store:"
  • NULL_ENTRY_TTL = 120L (2 phút)
  • VALID_ENTRY_TTL = 1800L (30 phút)

Thẻ: Redis mysql spring-transactions cache-invalidation cache-penetration

Đăng vào ngày 2 tháng 7 lúc 14:18