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 快取的get回null(強制 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)
兩層強制
- per-cache byte 上限:每個 byte 計 heap/offheap tier 由 Ehcache 在 runtime 達上限時主動驅逐 entry,強制單一 cache 不超標。
- manager 層加總檢查:
createCache時讀 Ehcache live config 重算所有 cache 的 byte 池加總,超過預算即依onExceed(預設REJECT、拋IllegalStateException)處置。
預設值推導
- heap 預算省略時 =
-Xmx(Runtime.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
}
最佳實踐
- 選擇適當的儲存層 - 熱資料用 Heap,大量資料用 Disk
- 設定合理的過期時間 - 避免過期風暴
- 啟用統計 - 監控 Hit Rate,調整快取策略
- 考慮序列化成本 - Offheap/Disk 需要序列化