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-011,supersede ADR-006):CacheManager 預設啟用記憶體預算管制(safe-by-default)。byte 預算只治理 offheap(序列化大小精確、不經 sizeof);heap 一律筆數計、不納入 byte 預算。要真實 byte 封頂就用 offheap。詳見下方「記憶體預算管制」節。
方法列表
儲存層配置
| 方法 | 說明 |
|---|---|
heap(long entries) | 堆記憶體物件數量(筆數計;不觸發 Ehcache sizeof,記憶體管制下亦合法) |
offheap(long sizeMB) | 堆外記憶體大小(MB,byte 計;要真實 byte 封頂用此) |
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; // 堆記憶體物件數量(一律筆數計)
private Long offheapSizeMB; // 堆外記憶體大小(MB)
private Long diskSizeMB; // 磁碟大小(MB)
private boolean persistent; // 是否持久化
}
heap 層一律以物件數量計(heapEntries,驅逐判斷零開銷、不觸發 Ehcache sizeof 引擎)。heap 不提供 byte 計量:純 in-process 的 on-heap byte 封頂須靠 sizeof 引擎,而該引擎在 JDK 25 已不可靠。需要真實 byte 封頂時改用 offheap(offheapSizeMB,序列化大小精確)。見 ADR-011。
MemoryBudget
CacheManager 層已解析的記憶體預算(ADR-011,supersede ADR-006),由 MemoryBudgetResolver 依推導規則產生,描述一個 CacheManager 下所有 cache 的 offheap 記憶體上限與超額處置。
public class MemoryBudget {
private long offheapBudgetMB; // offheap 預算(MB)——所有 cache 的 offheap 加總上限
private OnExceed onExceedOffheap; // offheap 超額處置(推導不確定時降級為 WARN)
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-011,supersede ADR-006)
CacheManager 在建構時可由 CacheManagerBuilder 啟用記憶體預算管制——封住一個 CacheManager 下所有 cache 的 offheap 記憶體總量。管制預設啟用(safe-by-default)。byte 預算只治理 offheap(序列化大小精確、不經 sizeof);heap 一律筆數計、不納入 byte 預算。
CacheManagerBuilder 方法
| 方法 | 說明 |
|---|---|
governed() | 啟用記憶體管制(框架預設即此;明確表態用) |
ungoverned() | 停用記憶體管制(向後相容 / 測試 / 小工具的出口) |
offheapBudgetMB(long) | 明示 offheap 預算(MB);省略時走四段 fallback 推導 |
onExceed(OnExceed) | 超額處置策略(預設 REJECT) |
CacheManager cacheManager = CacheManagerBuilder
.newCacheManager()
.governed() // 啟用記憶體預算管制(框架預設)
.offheapBudgetMB(512) // 所有 cache 的 offheap 加總上限
.onExceed(OnExceed.REJECT)
.build();
關鍵約束:byte 封頂走 offheap,heap 一律筆數計
byte 預算治理 offheap——CacheBuilder.offheap(long sizeMB)。offheap 存序列化後的 bytes、大小精確且不經 sizeof,是真實 byte 封頂的所在。heap tier 一律以筆數計(heap(long entries)),管制下亦合法、但不納入 byte 預算(筆數不封 bytes)。要 byte 安全的負載放 offheap,heap 作為其上的小筆數快取層。
// ✅ 真實 byte 封頂走 offheap;heap 作為筆數快取層
Cache<Long, String> userCache = CacheBuilder
.newCache(cacheManager, "users", Long.class, String.class)
.heap(1000) // heap 1000 筆(快取層,不封 bytes)
.offheap(20) // offheap 20MB(byte 封頂、計入預算)
.ttl(30)
.managed(true)
.build();
heap-only cache 只受筆數封頂、無 byte cap;需要 byte 防爆就配 offheap。
兩層強制
- per-cache offheap byte 上限:每個 offheap tier 由 Ehcache 在 runtime 達上限時主動驅逐 entry,強制單一 cache 不超標(序列化大小精確、不經 sizeof)。
- manager 層加總檢查:
createCache時讀 Ehcache live config 重算所有 cache 的 offheap 池加總,超過預算即依onExceed(預設REJECT、拋IllegalStateException)處置。
預設值推導
- offheap 預算省略時走四段 fallback(由穩到險):①明示
offheapBudgetMB→ ②-XX:MaxDirectMemorySize× 75% → ③確認 cgroup 真有上限時的總量反推 → ④固定 fallback 值 64MB + WARN。推導不確定(第 4 段或無 headroom)時,offheap 超額處置降級為WARN。
詳見 ADR-011: Cache 記憶體預算改以 offheap 承擔 byte 封頂。
使用範例
單層快取(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 需要序列化