Java本地快取神器---Caffeine(二)
阿新 • • 發佈:2021-01-24
前言
在上一篇《Java本地快取神器—Caffeine(一)》介紹了Caffeine和其他快取框架的效能對比、Caffeine特性中的載入策略和回收策略,接下來我們繼續實踐Caffeine的其他特性。
最佳實踐
1. 重新整理策略
重新整理策略可以通過LoadingCache.refresh(K)方法,非同步為key對應的快取元素重新整理一個新的值。與回收策略不同的是,在重新整理的時候如果查詢快取元素,其舊值將仍被返回,直到該元素的重新整理完畢後結束後才會返回重新整理後的新值。
public static void refreshLoad() throws InterruptedException { LoadingCache<Integer, String> cache = Caffeine.newBuilder() .maximumSize(10_000) //設定在寫入或者更新之後1分鐘後,呼叫 CacheLoader 重新載入 .refreshAfterWrite(1, TimeUnit.SECONDS) .build(new CacheLoader<Integer, String>() { @Override public String load(Integer key) throws Exception { String values = queryData(key); log("load重新整理,key:" + key + ",查詢的資料庫值:" + values); return values; } @Nullable @Override public String reload(@NonNull Integer key, @NonNull String oldValue) throws Exception { String values = queryData(key); log("reload重新整理,key:" + key + ",舊值:" + oldValue + ",查詢的資料庫值:" + values); return values; } }); Thread thread1 = startLoadingCacheQuery("client1", cache); Thread thread2 = startLoadingCacheQuery("client2", cache); Thread.sleep(2000); Thread thread3 = startLoadingCacheQuery("client3", cache); Thread.sleep(1000); Thread thread4 = startLoadingCacheQuery("client4", cache); } private static Thread startLoadingCacheQuery(String clientName, LoadingCache<Integer, String> cache) { Thread thread = new Thread(() -> { log("非同步從快取中查詢資料開始"); String values = cache.get(1); log("查詢的key為:" + 1 + ",值為:" + values); log("非同步從快取中查詢資料結束"); }); thread.setName(clientName); thread.start(); return thread; } private static String queryData(Integer key) throws InterruptedException { String value = System.currentTimeMillis() + ""; return value; } private static void log(String msg) { System.out.println(String.format("當前時間:%d,執行緒名稱:%s,msg:%s", System.currentTimeMillis(), Thread.currentThread().getName(), msg)); }
2. 快取寫入傳播
CacheWriter給快取提供了充當底層資源的門面的能力,當其與CacheLoader一起使用的時候,所有的讀和寫操作都可以通過快取向下傳播。Writers提供了原子性的操作,包括從外部資源同步的場景。這意味著在快取中,當一個key的寫入操作在完成之前,後續其他寫操作都是阻塞的,同時在這段時間內,嘗試獲取這個key對應的快取元素的時候獲取到的也將都是舊值。如果寫入失敗那麼之前的舊值將會被保留同時異常將會被傳播給呼叫者。
public static void write() throws InterruptedException { Cache<String, String> cache = Caffeine.newBuilder() // .expireAfterWrite(1, TimeUnit.SECONDS) .writer(new CacheWriter<String, String>() { @SneakyThrows @Override public void write(String key, String graph) { // 持久化或者次級快取 Thread.sleep(2000); System.out.println(String.format("寫入時間:%d,key:%s,value:%s", System.currentTimeMillis(), key, graph)); } @Override public void delete(String key, String value, RemovalCause cause) { // 從持久化或者次級快取中刪除 System.out.println(String.format("delete事件,key:%s,value:%s,原因:%s:",key,value,cause.toString())); } }) .removalListener(new RemovalListener<String, String>() { @Override public void onRemoval(@Nullable String key, @Nullable String value, @NonNull RemovalCause removalCause) { System.out.println(String.format("remove事件,key:%s,value:%s,原因:%s:",key,value,removalCause.toString())); } }) .build(); cache.put("name", "小明"); System.out.println(String.format("時間:%d,name:%s",System.currentTimeMillis(),cache.getIfPresent("name"))); //在這裡獲取name會同步等待"小明"寫入完成 cache.put("name", "小強"); System.out.println(String.format("時間:%d,name:%s",System.currentTimeMillis(),cache.getIfPresent("name"))); Thread.sleep(2000); System.out.println(String.format("時間:%d,name:%s",System.currentTimeMillis(),cache.getIfPresent("name"))); }
3. 統計
通過使用Caffeine.recordStats()方法可以開啟資料收集功能。Cache.stats()方法將會返回一個CacheStats物件,其將會含有一些統計指標,比如:
hitRate(): 查詢快取的命中率
evictionCount(): 被驅逐的快取數量
averageLoadPenalty(): 新值被載入的平均耗時
public static void statistics() throws InterruptedException { LoadingCache<String, String> cache = Caffeine.newBuilder() .maximumSize(10) .recordStats() .build(key->"小明"); int i=0; while(i<1000){ i++; cache.put("name"+i,"小明"); } Thread.sleep(10000); //快取命中率 System.out.println("快取命中率:"+cache.stats().hitRate()); //回收的快取數量 System.out.println("回收的快取數量:"+cache.stats().evictionCount()); //新值被載入的平均耗時 System.out.println("新值被載入的平均耗時:"+cache.stats().averageLoadPenalty()); }
歡迎大家關注我的微信公眾號:CodingTao