Chuyển tới nội dung chính

Cache đa tầng

Dependency cung cấp tầng cache nhiều chiến lược (embedded, external, composite) với annotation @GCacheable, @GCachePut, @GCacheEvict có TTL, cache giá trị null, lock theo key và thống kê hit/miss. Dùng khi service cần cache dữ liệu đọc nhiều mà không muốn tự viết logic cache thủ công.

Khi nào sử dụng

  • Cache kết quả truy vấn DB hoặc gọi API ngoài theo phương thức khai báo (annotation) thay vì tự quản lý map/Redis.
  • Cần TTL khác nhau cho từng method hoặc từng vùng cache mà Spring Cache tiêu chuẩn không đáp ứng.
  • Cần cache hai tầng (local + phân tán) để giảm round-trip tới Redis nhưng vẫn đồng bộ giữa các instance.
  • Cần lock theo key (tránh cache stampede) hoặc computeIfAbsent có loader tự nạp lại dữ liệu.

Cài đặt

Thêm dependency vào pom.xml; version do BOM vn.govex.cloud:dependencies quản lý nên không khai version:

<dependency>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-cache</artifactId>
</dependency>

Nếu chưa cấu hình BOM/parent, xem Cài đặt.

Cấu hình

PropertyMô tảMặc định
govex.cache.enabledBật/tắt toàn bộ cache của dependency.true
govex.cache.typeLoại cache toàn cục: EMBEDDED, EXTERNAL, COMPOSITE hoặc NONE.NONE
govex.cache.dynamicCho phép tạo cache động theo cacheNames chưa khai báo trong cache-configs.true
govex.cache.penetration-protectBảo vệ cache penetration toàn cục.false
govex.cache.embedded.typeKiểu embedded cache.CAFFEINE
govex.cache.embedded.caffeine.limitSố entry tối đa của cache local.100
govex.cache.embedded.caffeine.expire-after-write-in-millisTTL tính từ lúc ghi (ms); giá trị <= 0 là không hết hạn.-1
govex.cache.embedded.caffeine.expire-after-access-in-millisTTL tính từ lần truy cập (ms).0
govex.cache.embedded.caffeine.cache-null-valueCho phép cache giá trị null.false
govex.cache.embedded.caffeine.enable-statBật thống kê hit/miss cho cache local.false
govex.cache.external.typeKiểu external cache: REDIS hoặc REDISSON.REDISSON
govex.cache.cache-configs.<cache-name>.typeLoại cache riêng cho từng cache name.EMBEDDED
govex.cache.cache-configs.<cache-name>.expire-after-write-in-millisTTL ghi riêng cho cache name.-1
govex.cache.cache-configs.<cache-name>.cache-null-valueCho phép cache null riêng cho cache name.false
govex.cache.stat.enabledBật tổng hợp thống kê cache.true
govex.cache.stat.verbose-logLog chi tiết từng thao tác get/put/remove thay vì bản tóm tắt.false
govex.cache.stat.intervalChu kỳ tổng hợp và ghi log thống kê.1m

Backend external cache (Redis / Redisson)

Cache EXTERNAL/COMPOSITE cần thêm một implementation bên cạnh govex-cloud-cache (version do BOM quản lý):

DependencyBackendKhi dùng
govex-cloud-redisSpring Data Redis, kèm bean redisTemplate serialize JSONChỉ cần cache phân tán, không cần lock/rate limit của Redisson
govex-cloud-redissonRedissonClientCần cache phân tán kèm khoá phân tán/giới hạn tần suất trên cùng một client

Chọn backend qua govex.cache.external.type (REDIS hoặc REDISSON, mặc định REDISSON). Kết nối dùng cấu hình chuẩn spring.data.redis.* của Spring Boot:

spring:
data:
redis:
host: localhost
port: 6379

govex:
cache:
enabled: true
type: EXTERNAL
external:
type: REDIS
redis:
key-prefix: "myapp:"
sync-topic-channel: "myapp:cache-sync"
expire-after-write-in-millis: 86400000

Property riêng của backend Redis (govex.cache.external.redis.*); backend Redisson có bộ property tương ứng dưới govex.cache.external.redisson.*:

PropertyMô tảMặc định
govex.cache.typePhải là EXTERNAL hoặc COMPOSITE để backend được kích hoạt.NONE
govex.cache.external.typeREDIS hoặc REDISSON — đặt đúng backend đã thêm dependency.REDISSON
govex.cache.external.redis.key-prefixTiền tố gắn trước key của mọi cache trên Redis, tách không gian key giữa các ứng dụng.
govex.cache.external.redis.sync-topic-channelKênh pub/sub đồng bộ thay đổi cache giữa các instance.
govex.cache.external.redis.expire-after-write-in-millisTTL tính từ lúc ghi (ms); chỉ áp dụng khi lớn hơn 0.-1
govex.cache.external.redis.expire-after-access-in-millisTTL tính từ lần truy cập (ms).0
govex.cache.external.redis.cache-null-valueCho phép cache giá trị null.false
govex.cache.external.redis.cache-penetration-protectBảo vệ cache penetration.false
govex.cache.external.redis.enable-statBật thống kê hit/miss cho backend.false
govex.cache.external.redis.refresh-policy.refresh-millisChu kỳ refresh trước khi entry hết hạn (ms).0
govex.cache.external.redis.refresh-policy.refresh-lock-timeout-millisThời gian giữ lock tối đa khi load dữ liệu refresh (ms).60000

Annotation cache dùng cacheType = CacheType.EXTERNAL để trỏ đúng cache manager phân tán:

@GCacheable(cacheNames = "products", key = "#id", cacheType = CacheType.EXTERNAL,
expireTime = 1, timeUnit = TimeUnit.DAYS)
public Product getProductById(Long id) {
return productRepository.findById(id).orElse(null);
}

Lưu ý riêng của backend external:

  • govex-cloud-redis luôn đăng ký bean redisTemplate (không dùng @ConditionalOnMissingBean): nếu ứng dụng tự khai báo bean cùng tên, kiểm tra xung đột bean khi khởi động. govex-cloud-redisson chỉ tạo bean khi ứng dụng chưa có.
  • Đồng bộ cache giữa các instance chỉ hoạt động khi sync-topic-channel được cấu hình và các instance dùng chung Redis.
  • govex-cloud-redisson nạp thêm file config/redisson.yml trên classpath như property source bổ sung nếu cần tinh chỉnh RedissonClient.
  • Muốn lock/rate limit dùng Redisson, thêm govex-cloud-redisson thay vì hoặc bổ sung cho Redis thuần.
  • Cần Redis đang chạy và RedisConnectionFactory do Spring Boot auto-configuration tạo (spring-boot-starter-data-redis).

Sử dụng

Bước 1 — bật cache bằng @EnableGCaching trên một class @Configuration (bắt buộc, xem Lưu ý):

@Configuration
@EnableGCaching
public class CacheConfig {
}

Bước 2 — cấu hình loại cache. Ví dụ cache hai tầng composite (cần thêm govex-cloud-redisson hoặc govex-cloud-redis):

govex:
cache:
enabled: true
type: COMPOSITE
dynamic: true
embedded:
type: CAFFEINE
caffeine:
limit: 1000
expire-after-write-in-millis: 3600000
external:
type: REDISSON
cache-configs:
product-cache:
type: COMPOSITE
expire-after-write-in-millis: 86400000
cache-null-value: true

Bước 3 — khai báo annotation trên service. Luôn đặt cacheType khớp với loại cache đã cấu hình:

@GCacheable(cacheNames = "products", key = "#id", cacheType = CacheType.EMBEDDED,
expireTime = 10, timeUnit = TimeUnit.MINUTES)
public Product getProductById(Long id) {
return productRepository.findById(id).orElse(null);
}

@GCachePut(cacheNames = "products", key = "#product.id", expireTime = 1, timeUnit = TimeUnit.DAYS)
public Product updateProduct(Product product) {
return productRepository.save(product);
}

@GCacheEvict(cacheNames = "products", key = "#id", condition = "#id > 0")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}

Khi cần thao tác thủ công, inject GCacheManager và dùng GCache:

GCache cache = cacheManager.getCache("products");
cache.put("product:123", product, 30, TimeUnit.MINUTES);

Product product = cache.computeIfAbsent("product:123", key -> fetchProductFromDb(key),
true, 30, TimeUnit.MINUTES);

try (AutoReleaseLock lock = cache.tryLock("product:123", 30, TimeUnit.SECONDS)) {
if (lock != null) {
refreshProduct("product:123");
}
}

Lưu ý

  • Bắt buộc có @EnableGCaching: dependency không đăng ký auto-configuration qua AutoConfiguration.imports, thiếu annotation thì cache không hoạt động.
  • cacheType của @GCacheable phải trỏ tới cache manager đã đăng ký. Nếu chọn sai (ví dụ đặt EMBEDDED khi govex.cache.type=NONE), ứng dụng lỗi khi parse annotation với CacheConfigException.
  • Cache EXTERNAL/COMPOSITE cần thêm implementation govex-cloud-redis hoặc govex-cloud-redisson; govex.cache.external.type mặc định là REDISSON.
  • Cache embedded chỉ nằm trong bộ nhớ từng instance; khi chạy nhiều instance cần cache phân tán hoặc cấu hình đồng bộ qua topic của dependency external.
  • govex.cache.stat.interval dùng định dạng Duration của Spring Boot (ví dụ 30s, 1m).
  • Chi tiết chọn backend external, đồng bộ cache qua topic và lưu ý xung đột bean redisTemplate — xem mục Backend external cache (Redis / Redisson) ở trên.