Java本地緩存技術選型(Guava Cache、Caffeine、Encache)

對一個java後臺開發者而言,提到緩存,第一反應就是redis和memcache。利用這類緩存足以解決大多數的性能問題了,並且java針對這兩者也都有非常成熟的api可供使用。但是我們也要知道,這兩種都屬於remote cache(分佈式緩存),應用的進程和緩存的進程通常分佈在不同的服務器上,不同進程之間通過RPC或HTTP的方式通信。這種緩存的優點是緩存和應用服務解耦,支持大數據量的存儲,缺點是數據要經過網絡傳輸,性能上會有一定損耗。

與分佈式緩存對應的是本地緩存,緩存的進程和應用進程是同一個,數據的讀寫都在一個進程內完成,這種方式的優點是沒有網絡開銷,訪問速度很快。缺點是受JVM內存的限制,不適合存放大數據。

本篇文章我們主要主要討論Java本地緩存的的一些常用方案。

本地緩存常用技術

本地緩存和應用同屬於一個進程,使用不當會影響服務穩定性,所以通常需要考慮更多的因素,例如容量限制、過期策略、淘汰策略、自動刷新等。常用的本地緩存方案有:

  • 根據HashMap自實現本地緩存
  • Guava Cache
  • Caffeine
  • Encache

下面分別進行介紹:

1. 根據HashMap自定義實現本地緩存

緩存的本質就是存儲在內存中的KV數據結構,對應的就是jdk中的HashMap,但是要實現緩存,還需要考慮併發安全性、容量限制等策略,下面簡單介紹一種利用LinkedHashMap實現緩存的方式:

public class LRUCache extends LinkedHashMap {

    /**
     * 可重入讀寫鎖,保證併發讀寫安全性
     */
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = readWriteLock.readLock();
    private Lock writeLock = readWriteLock.writeLock();

    /**
     * 緩存大小限制
     */
    private int maxSize;

    public LRUCache(int maxSize) {
        super(maxSize + 1, 1.0f, true);
        this.maxSize = maxSize;
    }

    @Override
    public Object get(Object key) {
        readLock.lock();
        try {
            return super.get(key);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public Object put(Object key, Object value) {
        writeLock.lock();
        try {
            return super.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return this.size() > maxSize;
    }
}

複製代碼

LinkedHashMap維持了一個鏈表結構,用來存儲節點的插入順序或者訪問順序(二選一),並且內部封裝了一些業務邏輯,只需要覆蓋removeEldestEntry方法,便可以實現緩存的LRU淘汰策略。此外我們利用讀寫鎖,保障緩存的併發安全性。需要注意的是,這個示例並不支持過期時間淘汰的策略。

自實現緩存的方式,優點是實現簡單,不需要引入第三方包,比較適合一些簡單的業務場景。缺點是如果需要更多的特性,需要定製化開發,成本會比較高,並且穩定性和可靠性也難以保障。對於比較複雜的場景,建議使用比較穩定的開源工具。

2. 基於Guava Cache實現本地緩存

Guava是Google團隊開源的一款 Java 核心增強庫,包含集合、併發原語、緩存、IO、反射等工具箱,性能和穩定性上都有保障,應用十分廣泛。Guava Cache支持很多特性:

  • 支持最大容量限制
  • 支持兩種過期刪除策略(插入時間和訪問時間)
  • 支持簡單的統計功能
  • 基於LRU算法實現

Guava Cache的使用非常簡單,首先需要引入maven包:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>
複製代碼

一個簡單的示例代碼如下:

public class GuavaCacheTest {

    public static void main(String[] args) throws Exception {
        //創建guava cache
        Cache<String, String> loadingCache = CacheBuilder.newBuilder()
                //cache的初始容量
                .initialCapacity(5)
                //cache最大緩存數
                .maximumSize(10)
                //設置寫緩存後n秒鐘過期
                .expireAfterWrite(17, TimeUnit.SECONDS)
                //設置讀寫緩存後n秒鐘過期,實際很少用到,類似於expireAfterWrite
                //.expireAfterAccess(17, TimeUnit.SECONDS)
                .build();
        String key = "key";
        // 往緩存寫數據
        loadingCache.put(key, "v");

        // 獲取value的值,如果key不存在,調用collable方法獲取value值加載到key中再返回
        String value = loadingCache.get(key, new Callable<String>() {
            @Override
            public String call() throws Exception {
                return getValueFromDB(key);
            }
        });

        // 刪除key
        loadingCache.invalidate(key);
    }

    private static String getValueFromDB(String key) {
        return "v";
    }
}
複製代碼

總體來說,Guava Cache是一款十分優異的緩存工具,功能豐富,線程安全,足以滿足工程化使用,以上代碼只介紹了一般的用法,實際上springboot對guava也有支持,利用配置文件或者註解可以輕鬆集成到代碼中。

3. Caffeine

Caffeine是基於java8實現的新一代緩存工具,緩存性能接近理論最優。可以看作是Guava Cache的增強版,功能上兩者類似,不同的是Caffeine採用了一種結合LRU、LFU優點的算法:W-TinyLFU,在性能上有明顯的優越性。Caffeine的使用,首先需要引入maven包:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.5.5</version>
</dependency>
複製代碼

使用上和Guava Cache基本類似:

public class CaffeineCacheTest {

    public static void main(String[] args) throws Exception {
        //創建guava cache
        Cache<String, String> loadingCache = Caffeine.newBuilder()
                //cache的初始容量
                .initialCapacity(5)
                //cache最大緩存數
                .maximumSize(10)
                //設置寫緩存後n秒鐘過期
                .expireAfterWrite(17, TimeUnit.SECONDS)
                //設置讀寫緩存後n秒鐘過期,實際很少用到,類似於expireAfterWrite
                //.expireAfterAccess(17, TimeUnit.SECONDS)
                .build();
        String key = "key";
        // 往緩存寫數據
        loadingCache.put(key, "v");

        // 獲取value的值,如果key不存在,獲取value後再返回
        String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);

        // 刪除key
        loadingCache.invalidate(key);
    }

    private static String getValueFromDB(String key) {
        return "v";
    }
}
複製代碼

相比Guava Cache來說,Caffeine無論從功能上和性能上都有明顯優勢。同時兩者的API類似,使用Guava Cache的代碼很容易可以切換到Caffeine,節省遷移成本。需要注意的是,SpringFramework5.0(SpringBoot2.0)同樣放棄了Guava Cache的本地緩存方案,轉而使用Caffeine。

4. Encache

Encache是一個純Java的進程內緩存框架,具有快速、精幹等特點,是Hibernate中默認的CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加豐富,擴展性更強:

  • 支持多種緩存淘汰算法,包括LRU、LFU和FIFO
  • 緩存支持堆內存儲、堆外存儲、磁盤存儲(支持持久化)三種
  • 支持多種集羣方案,解決數據共享問題

Encache的使用,首先需要導入maven包:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.8.0</version>
</dependency>
複製代碼

以下是一個簡單的使用案例:

public class EncacheTest {

    public static void main(String[] args) throws Exception {
        // 聲明一個cacheBuilder
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                .withCache("encacheInstance", CacheConfigurationBuilder
                        //聲明一個容量爲20的堆內緩存
                        .newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))
                .build(true);
        // 獲取Cache實例
        Cache<String,String> myCache =  cacheManager.getCache("encacheInstance", String.class, String.class);
        // 寫緩存
        myCache.put("key","v");
        // 讀緩存
        String value = myCache.get("key");
        // 移除換粗
        cacheManager.removeCache("myCache");
        cacheManager.close();
    }
}

複製代碼

總結

  • 從易用性角度,Guava Cache、Caffeine和Encache都有十分成熟的接入方案,使用簡單。
  • 從功能性角度,Guava Cache和Caffeine功能類似,都是隻支持堆內緩存,Encache相比功能更爲豐富
  • 從性能上進行比較,Caffeine最優、GuavaCache次之,Encache最差(下圖是三者的性能對比結果)

 

img

 

總體來說,對於本地緩存的方案中,筆者比較推薦Caffeine,性能上遙遙領先。雖然Encache功能更爲豐富,甚至提供了持久化和集羣的功能,但是這些功能完全可以依靠其他方式實現。真實的業務工程中,建議使用Caffeine作爲本地緩存,另外使用redis或者memcache作爲分佈式緩存,構造多級緩存體系,保證性能和可靠性。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章