跳至主要内容

Cache API

快取模組提供多層快取支援,基於 EhCache 實作。

套件: io.leandev.appfuse.cache

核心介面

Cache<K, V>

快取核心操作介面。

public interface Cache<K, V> {
V get(K key);
void put(K key, V value);
void remove(K key);
void clear();
boolean containsKey(K key);

void disable();
void enable();
boolean isEnabled();

CacheStatistics getStatistics();
CacheStatus getStatus();
}

CacheManager

快取生命週期管理介面。

public interface CacheManager extends AutoCloseable {
<K, V> Cache<K, V> createCache(CacheConfiguration<K, V> configuration);
<K, V> Cache<K, V> getCache(String name, Class<K> keyType, Class<V> valueType);
void removeCache(String name);
Collection<String> getCacheNames();
boolean hasCache(String name);

// 啟用開關(ADR-007,皆為 default 方法、非破壞性)
default void disableAll(); // 拉總閘:停用此管理器下所有快取
default void enableAll(); // 放總閘(不動各 cache 個別狀態)
default boolean isEnabled(); // 查詢總閘狀態(預設 true)
default void disableCache(String name); // 停用單一快取(可在建立前先指名)
default void enableCache(String name); // 啟用單一快取
default boolean isCacheEnabled(String name); // 有效服務狀態(總閘 AND 個別開關)

void close();
}

啟用開關語意(ADR-007)

  • disableAll() / enableAll():管理器層級的總閘。停用為邏輯旁路、不清空資料——停用期間所有 managed 快取的 getnull(強制 miss)、put 被忽略;底層資料保留,enableAll() 後即可再服務。停用後新建立(含懶建)的 managed 快取也自動受此總閘管制。enableAll() 僅翻回總閘,不會覆蓋個別快取以 disableCache(String) 設定的停用狀態。
  • disableCache(String) / enableCache(String)個別快取開關,與總閘正交——即使總閘啟用,被指名的快取仍停用。可在快取建立之前先指名,之後懶建的同名快取會以停用狀態誕生。
  • isCacheEnabled(String):有效服務狀態 = 總閘 isEnabled() AND 個別開關。
  • 上述方法在介面為 default(不具管理能力的實作:mutator 拋 UnsupportedOperationException、query 回 sane 預設),由內建 Ehcache 實作覆寫為正解。詳見 ADR-007: 快取啟用開關

CacheBuilder

使用 Fluent API 建構快取。

基本用法

Cache<String, User> cache = CacheBuilder
.newCache(cacheManager, "users", String.class, User.class)
.heap(1000)
.ttl(Duration.ofMinutes(30))
.build();

newCache(...) 第一個參數為 CacheManager(快取由其建立並納管)。

記憶體管制注意(ADR-006):CacheManager 預設啟用記憶體預算管制(safe-by-default)。管制啟用時 heap tier 必須以 byte 計heapMemory(long sizeMB));以筆數計的 heap(long entries) 會被 REJECT。詳見下方「記憶體預算管制」節。

方法列表

儲存層配置

方法說明
heap(long entries)堆記憶體物件數量(筆數計;記憶體管制啟用時不可用,會被 REJECT)
heapMemory(long sizeMB)堆記憶體大小(MB,byte 計;記憶體管制啟用時 heap tier 須用此)
offheap(long sizeMB)堆外記憶體大小(MB)
disk(long sizeMB)磁碟大小(MB)
persistent()啟用持久化

過期配置

方法說明
ttl(Duration duration)Time-To-Live(存活時間)
tti(Duration duration)Time-To-Idle(閒置時間)
noExpiration()不過期

管理功能

方法說明
managed(boolean)是否啟用管理功能
build()建構快取實例

配置類別

CacheConfiguration

public class CacheConfiguration<K, V> {
private String name;
private Class<K> keyType;
private Class<V> valueType;
private TierConfiguration tierConfig;
private ExpiryConfiguration expiryConfig;
private boolean managed;
}

TierConfiguration

public class TierConfiguration {
private Long heapEntries; // 堆記憶體物件數量(筆數計;與 heapSizeMB 互斥)
private Long heapSizeMB; // 堆記憶體大小(MB,byte 計;與 heapEntries 互斥)
private Long offheapSizeMB; // 堆外記憶體大小(MB)
private Long diskSizeMB; // 磁碟大小(MB)
private boolean persistent; // 是否持久化
}

heap 層可二擇一計量:heapEntries(以物件數量計,驅逐判斷零開銷,但無法封住實際記憶體)或 heapSizeMB(以 MB 計,由 Ehcache sizeof 引擎封住記憶體;記憶體管制下 heap tier 須用此)。兩者互斥,同時設定會在驗證時拋 IllegalArgumentException

MemoryBudget

CacheManager 層已解析的記憶體預算(ADR-006),由 MemoryBudgetResolver 依推導規則產生,描述一個 CacheManager 下所有 cache 的 heap / offheap 記憶體上限與超額處置。

public class MemoryBudget {
private long heapBudgetMB; // heap 預算(MB)——所有 cache 的 byte heap 加總上限
private long offheapBudgetMB; // offheap 預算(MB)——所有 cache 的 offheap 加總上限
private OnExceed onExceedHeap; // heap 超額處置(恆套用使用者選定策略)
private OnExceed onExceedOffheap; // offheap 超額處置(推導不確定時降級為 WARN)
private String heapBudgetSource; // heap 預算來源(記錄/診斷用,如 "25% of -Xmx")
private String offheapBudgetSource; // offheap 預算來源(記錄/診斷用,如 "container total-based")
}

OnExceed

記憶體預算超額時的處置策略(啟用記憶體管制、且新建 cache 會使加總超過預算時的行為)。

public enum OnExceed {
REJECT, // 拒絕建立,拋出 IllegalStateException(預設)
WARN // 僅記錄警告,仍允許建立
}

ExpiryConfiguration

public class ExpiryConfiguration {
private ExpiryType type; // TIME_TO_LIVE / TIME_TO_IDLE / NO_EXPIRATION
private Duration duration; // 過期時間
}

記憶體預算管制(ADR-006)

CacheManager 在建構時可由 CacheManagerBuilder 啟用記憶體預算管制——封住一個 CacheManager 下所有 cache 的 heap / offheap 記憶體總量。管制預設啟用(safe-by-default)

CacheManagerBuilder 方法

方法說明
governed()啟用記憶體管制(框架預設即此;明確表態用)
ungoverned()停用記憶體管制(向後相容 / 測試 / 小工具的出口)
heapBudgetMB(long)明示 heap 預算(MB);省略時自動以 -Xmx 的 25% 推導
offheapBudgetMB(long)明示 offheap 預算(MB);省略時走四段 fallback 推導
onExceed(OnExceed)超額處置策略(預設 REJECT
CacheManager cacheManager = CacheManagerBuilder
.newCacheManager()
.governed() // 啟用記憶體預算管制(框架預設)
.heapBudgetMB(64) // 所有 cache 的 byte heap 加總上限
.offheapBudgetMB(512) // 所有 cache 的 offheap 加總上限
.onExceed(OnExceed.REJECT)
.build();

關鍵約束:管制啟用時 heap tier 須 byte 計

管制啟用時,凡配置 heap tier 的 managed cache 必須以 byte 計——CacheBuilder.heapMemory(long sizeMB)(或 DualCacheBuilder.fastHeapMemory(long sizeMB))。以筆數計的 heap(long entries) 無法換算 MB、無法計入加總,會被 REJECT。offheap-only / disk-only(無 heap tier)的 cache 不受此限。

// ✅ 管制下正確:heap 以 byte 計
Cache<Long, String> userCache = CacheBuilder
.newCache(cacheManager, "users", Long.class, String.class)
.heapMemory(2) // heap 2MB(byte 計)
.offheap(20)
.ttl(30)
.managed(true)
.build();

// ❌ 管制下會被 REJECT:heap 以筆數計
// .heap(1000)

兩層強制

  1. per-cache byte 上限:每個 byte 計 heap/offheap tier 由 Ehcache 在 runtime 達上限時主動驅逐 entry,強制單一 cache 不超標。
  2. manager 層加總檢查createCache 時讀 Ehcache live config 重算所有 cache 的 byte 池加總,超過預算即依 onExceed(預設 REJECT、拋 IllegalStateException)處置。

預設值推導

  • heap 預算省略時 = -XmxRuntime.maxMemory())的 25%(保守——heap 與應用工作集 + GC 共享)。
  • offheap 預算省略時走四段 fallback(由穩到險):①明示 offheapBudgetMB → ②-XX:MaxDirectMemorySize × 75% → ③確認 cgroup 真有上限時的總量反推 → ④固定 fallback 值 64MB + WARN。推導不確定(第 4 段或無 headroom)時,offheap 超額處置降級為 WARN

詳見 ADR-006: CacheManager 層記憶體預算管制

使用範例

單層快取(Heap Only)

Cache<String, Product> cache = CacheBuilder
.newCache(cacheManager, "products", String.class, Product.class)
.heap(500)
.ttl(Duration.ofHours(1))
.build();

// 存取操作
cache.put("prod-001", product);
Product p = cache.get("prod-001");
cache.remove("prod-001");

多層快取(Heap + Disk)

Cache<String, Report> cache = CacheBuilder
.newCache(cacheManager, "reports", String.class, Report.class)
.heap(100)
.disk(500)
.ttl(Duration.ofDays(1))
.persistent()
.build();

啟用管理功能

Cache<String, Session> cache = CacheBuilder
.newCache(cacheManager, "sessions", String.class, Session.class)
.heap(1000)
.tti(Duration.ofMinutes(30))
.managed(true)
.build();

// 取得統計資訊
CacheStatistics stats = cache.getStatistics();
long hitCount = stats.getHitCount();
long missCount = stats.getMissCount();
double hitRate = stats.getHitRate();

// 動態停用/啟用
cache.disable();
cache.enable();

使用 CacheManager

@Autowired
private CacheManager cacheManager;

// 建立快取
CacheConfiguration<String, User> config = CacheConfiguration.<String, User>builder()
.name("users")
.keyType(String.class)
.valueType(User.class)
.tierConfig(TierConfiguration.builder().heapEntries(1000L).build())
.expiryConfig(ExpiryConfiguration.ttl(Duration.ofMinutes(30)))
.build();

Cache<String, User> cache = cacheManager.createCache(config);

// 取得已存在的快取
Cache<String, User> existing = cacheManager.getCache("users", String.class, User.class);

// 移除快取
cacheManager.removeCache("users");

統計資訊

CacheStatistics

public interface CacheStatistics {
long getHitCount();
long getMissCount();
long getPutCount();
long getRemovalCount();
long getEvictionCount();
double getHitRate();
double getMissRate();
}

CacheStatus

public enum CacheStatus {
UNINITIALIZED,
AVAILABLE,
MAINTENANCE,
CLOSED
}

最佳實踐

  1. 選擇適當的儲存層 - 熱資料用 Heap,大量資料用 Disk
  2. 設定合理的過期時間 - 避免過期風暴
  3. 啟用統計 - 監控 Hit Rate,調整快取策略
  4. 考慮序列化成本 - Offheap/Disk 需要序列化

相關連結