淺談LocalCache | 京東雲技術團隊

發表於2024-02-12

1、什麼是LocalCache?

本地快取是一種將資料儲存在應用程式記憶體中的機制,用於提高資料訪問的效能和響應速度。它透過在記憶體中維護一個鍵值對的儲存結構,允許應用程式快速檢索和訪問資料,而無需每次都從慢速的資料來源(如資料庫或網路)獲取資料。

2、LocalCache優缺點

1)優點

•快速訪問:LocalCache將資料儲存在記憶體中,因此可以快速訪問快取資料,提高應用程式的效能。

•減輕後端負載:透過將經常訪問的資料儲存在本地快取中,可以減少對後端資料來源的訪問次數,降低後端負載,提高系統的整體效能。

•靈活性:LocalCache提供了一些配置選項,如最大容量、過期時間等,可以根據需求進行調整。這使得開發人員能夠靈活地控制快取的行為,以適應不同的業務場景。

2)缺點

•有限的容量:由於LocalCache是基於記憶體的,因此容量是有限的。如果快取資料量過大,可能會導致記憶體消耗過高,影響系統的穩定性。

•單點故障:LocalCache儲存在單個應用程式中,如果應用程式崩潰或重啟,快取資料將丟失。這可能會導致快取冷啟動和效能下降。

3、LocalCache應用場景

LocalCache適用於許多不同的使用場景,特別是以下幾種情況:

1.頻繁訪問的資料:如果應用程式中有一些頻繁被訪問的資料,將這些資料儲存在LocalCache中可以大大提高訪問速度。由於資料儲存在記憶體中,相比於從磁碟或網路中讀取資料,從本地快取中獲取資料的速度更快。

2.減輕後端負載:透過將經常訪問的資料儲存在本地快取中,可以減少對後端資料來源(如資料庫或API)的訪問次數。這樣可以降低後端的負載,提高系統的整體效能。

3.降低網路延遲:在分散式系統或微服務架構中,不同的服務可能位於不同的節點上,透過網路進行通訊。使用本地快取可以減少對遠端服務的呼叫次數,從而減少網路延遲,提高系統的響應速度。

4.靈活性和可配置性:LocalCache提供了一些配置選項,如最大容量、過期時間等,可以根據需求進行調整。這使得開發人員能夠靈活地控制快取的行為,以適應不同的業務場景。

4、LocalCache實際使用場景

1.資料庫中介軟體:資料庫中介軟體是位於應用程式和後端資料庫之間的軟體層,常見的資料庫中介軟體包括MySQL Proxy、Cobar、MyCat等。這些中介軟體可以使用LocalCache來快取查詢結果,以減輕後端資料庫的負載並提高查詢效能。當應用程式發出相同的查詢請求時,中介軟體可以先檢查LocalCache中是否有相應的快取結果,如果有則直接返回快取結果,否則再向後端資料庫發起查詢請求。

2.訊息佇列:訊息佇列是一種用於非同步通訊的中介軟體,常見的訊息佇列系統包括Apache Kafka、RabbitMQ等。在訊息佇列系統中,LocalCache可以用來快取訊息,以提高訊息的處理速度和響應能力。當消費者需要處理訊息時,它可以先在LocalCache中查詢是否有相應的訊息快取,如果有則直接消費快取訊息,否則再從訊息佇列中獲取訊息。

3.前端應用:前端應用包括各種Web應用、移動應用和桌面應用,常見的前端框架如React、Angular、Vue.js等。在前端應用中,LocalCache可以用於快取靜態資源(如JavaScript、CSS、影像等)或動態資料,以提高應用的載入速度和使用者體驗。例如,前端應用可以先檢查LocalCache中是否有相應的資源快取,如果有則直接使用快取資源,否則再從伺服器獲取資源並將其快取到LocalCache中。

4.服務端應用:服務端應用包括各種後端服務、微服務和RESTful API等,常見的服務端框架如Spring、Node.js、Ruby on Rails等。LocalCache可以在服務端應用中使用,用於快取計算結果、資料庫查詢結果等,以提高效能和減少對外部資源的依賴。例如,當服務端應用需要進行頻繁的計算或查詢時,它可以先檢查LocalCache中是否有相應的快取結果,如果有則直接返回快取結果,否則再進行計算或查詢,並將結果快取到LocalCache中。

5.開源框架:許多開源框架提供了自己的LocalCache實現,以方便開發人員在應用中使用。這些框架通常提供了豐富的配置選項和高效能的快取實現,可以根據應用需求進行定製。例如,Guava、Caffeine和Ehcache等是常見的Java開源框架,它們提供了LocalCache的實現,可以用於快取計算結果、資料庫查詢結果等。

5、LocalCache在Guava實踐

Guava cache 繼承了 ConcurrentHashMap 的思路,使用多個 segments 方式的細粒度鎖,在保證執行緒安全的同時,支援高併發場景需求。

1、CacheBuilder 快取構建器。構建快取的入口,指定快取配置引數並初始化本地快取。採用 Builder 設計模式提供了設定好各種引數的快取物件。

CacheBuilder<Object,Object> cacheBuilder =CacheBuilder.newBuilder();

2、LocalCache 資料結構。快取核心類 LocalCache 資料結構與 ConcurrentHashMap 很相似,由多個 segment 組成,且各 segment 相對獨立,互不影響,所以能支援並行操作,每個 segment 由一個 table 和若干佇列組成。快取資料儲存在 table 中,其型別為AtomicReferenceArray,具體結構圖及程式碼解釋如下。

#Guava中LocalCache宣告segments變數
final LocalCache.Segment<K, V>[] segments;

#Guava中初始化segments
this.segments = this.newSegmentArray(segmentCount);
final LocalCache.Segment<K, V>[] newSegmentArray(int ssize) {
    return new LocalCache.Segment[ssize];
}
#獲取Segment
Segment<K, V> segmentFor(int hash) {
    return segments[(hash >>> segmentShift) & segmentMask];
}

#Guava中Segment宣告table變數用於儲存資料
volatile AtomicReferenceArray<LocalCache.ReferenceEntry<K, V>> table;

V put(K key, int hash, V value, boolean onlyIfAbsent) {
    #保證執行緒安全
    lock(); 
    #獲取資料
    AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
    int index = hash & (table.length() - 1);
    ReferenceEntry<K, V> first = table.get(index);
    for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
      if (e.getHash() == hash
      && entryKey != null
      && map.keyEquivalence.equivalent(key, entryKey)) {
      }
    }
}

3、在Guava中,CacheBuilder提供了一系列方法,用於指定快取的大小、過期策略、併發級別等屬性。

1.配置快取屬性:透過一系列方法來配置快取的屬性,例如:

maximumSize(long size):指定快取的最大容量,當快取達到最大容量時,根據快取策略淘汰部分快取項。

expireAfterWrite(Duration duration):指定快取項的寫入後過期時間,過期後的快取項將被自動移除。

expireAfterAccess(Duration duration):指定快取項的最後一次訪問後過期時間,過期後的快取項將被自動移除。

concurrencyLevel(int level):指定併發級別,即同時可以進行快取操作的執行緒數。

Cache<Object,Object> cache = cacheBuilder.maximumSize(1000)
                                        .expireAfterWrite(Duration.ofMinutes(10))
                                        .concurrencyLevel(4)
                                        .build();

2. 使用快取:透過Cache物件的方法來進行快取的讀取、寫入和移除操作。例如:

get(Object key):根據鍵獲取快取項的值。

put(Object key, Object value):向快取中新增或更新一個快取項。

invalidate(Object key):移除指定鍵的快取項。

3. 其他功能:Guava的LocalCache還提供了其他功能,如統計資訊、監聽器、載入器等,用於監控快取的狀態、處理快取未命中的情況等。

CacheStats stats = cache.stats();
cache.asMap().forEach((key, value)->System.out.println(key +": "+ value));
cache.cleanUp();

總結來說,Guava的LocalCache使用CacheBuilder構建和配置快取,透過Cache物件進行快取的讀取、寫入和移除操作。開發人員可以根據自己的需求使用相應的方法和功能來定製和管理快取。

6、後記

設計一個可用的 Cache 絕對不是一個普通的 Map 這麼簡單,這裡小結一下關於 Guava Cache 的知識。

迴歸LocalCache 的源頭,我是希望可以瞭解 設計一個快取要考慮什麼?,_區域性性原理_ 是一個系統效能提升的最直接的方式(程式設計上,硬體上當然也可以),快取的出現就是根據 區域性性原理 所設計的。

快取作為儲存金字塔的一部分,一定需要考慮以下幾個問題:

1.何時載入

在設計何時載入的問題上,Guava Cache 提供了一個 Loader 介面,讓使用者可以自定義載入過程,在由 Cache 在找不到物件的時候主動呼叫 Loader 去載入,還透過一個巧妙的方法,既保證了 Loader 的只執行一次,還能保證鎖粒度極小,保證併發載入時,安全且高效能。

2. 何時失效

失效處理上,Guava Cache 提供了基於容量、有限時間(讀有限時、寫有限時)等失效策略,在官方文件上也寫明,在基於限時的情況下,並不是使用一個執行緒去單獨清理過期 K-V,而是把這個清理工作,均攤到每次訪問中。假如需要定時清理,也可以呼叫 CleanUp 方法,定時呼叫就可以了。

3. 如何保持熱點資料有效性

在 Cache 容量有限時, LRU 演算法是一個通用的解決方案,在原始碼中,Guava Cache 並不是嚴格地保證全域性 LRU 的,只是針對一個 Segment 實現 LRU 演算法。這個前提是 Segment 對使用者來說是隨機的,所以全域性的 LRU 演算法和單個 Segment 的演算法是基本一致的。

4. 寫回策略

在 Guava Cache 裡,並沒有實現任何的寫回策略。原因在於,Guava Cache 是一個本地快取,直接修改物件的資料,Cache 的資料就已經是最新的了,所以在資料能夠寫入 DB 後,資料就已經完成一致了。

參考文獻:Google Guava Cache 全解析

作者:京東科技 遊斌平

來源:京東雲開發者社群 轉載請註明來源

相關文章