天天看點

Caffeine Cache

1. 前言

網際網路軟體神速發展,使用者的體驗度是判斷一個軟體好壞的重要原因,是以緩存就是必不可少的一個神器。在多線程高并發場景中往往是離不開cache的,需要根據不同的應用場景來需要選擇不同的cache,比如分布式緩存如redis、memcached,還有本地(程序内)緩存如ehcache、GuavaCache、Caffeine。

說起Guava Cache,很多人都不會陌生,它是Google Guava工具包中的一個非常友善易用的本地化緩存實作,基于LRU算法實作,支援多種緩存過期政策。由于Guava的大量使用,Guava Cache也得到了大量的應用。但是,Guava Cache的性能一定是最好的嗎?也許,曾經,它的性能是非常不錯的。但所謂長江後浪推前浪,總會有更加優秀的技術出現。今天,我就來介紹一個比Guava Cache性能更高的緩存架構:Caffeine。

2. 比較

Google Guava工具包中的一個非常友善易用的本地化緩存實作,基于LRU算法實作,支援多種緩存過期政策。

EhCache 是一個純Java的程序内緩存架構,具有快速、精幹等特點,是Hibernate中預設的CacheProvider。

Caffeine是使用Java8對Guava緩存的重寫版本,在Spring Boot 2.0中将取代,基于LRU算法實作,支援多種緩存過期政策。

2.1 官方性能比較

場景1:8個線程讀,100%的讀操作

Caffeine Cache

場景二:6個線程讀,2個線程寫,也就是75%的讀操作,25%的寫操作

Caffeine Cache

場景三:8個線程寫,100%的寫操作

3. 如何使用

3.1手動加載(Manual)

public static void main(String[] args) {
        Cache<String, String> manualCache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MILLISECONDS)   //設定過期時間
                .maximumSize(100)    //設定最大緩存個數
                .build();
        String key = "name";
        // 根據key查詢一個緩存,如果沒有傳回NULL
        String value = manualCache.getIfPresent(key);
        System.out.println("key "+ value);    //key null
        // 将一個值放入緩存,如果以前有值就覆寫以前的值
        manualCache.put(key,"reed");
        String value1 = manualCache.getIfPresent(key);
        System.out.println("key1 "+value1);   //key1 reed
        // 删除一個緩存
        manualCache.invalidate(key);
        String value2 = manualCache.getIfPresent(key);
        System.out.println("key2 "+value2);    //key2 null
        // 根據Key查詢一個緩存,如果沒有調用createExpensiveGraph方法,并将傳回值儲存到緩存。
        String value3 =manualCache.get(key,k->"fan");
        System.out.println("key3 "+value3);    //key3 fan
        String value4 = manualCache.get(key,k->"reed");
        System.out.println("key4 "+value4);    //key4 fan
    }           

3.2同步加載(Loading)

@Test
    public void test(){
        LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(key -> getName(key));

        List<String> keys = new ArrayList<>();
        keys.add("reed");
        keys.add("fan");

        Map<String,String> map = loadingCache.getAll(keys);

        for(Map.Entry<String,String> m:map.entrySet()){
            System.out.println("key:" + m.getKey() + " value:" + m.getValue());
            /*key:reed value:reed
            key:fan value:fan*/
        }
    }
    private String getName(String str){
        return str;
    }           

3.3異步加載(Asynchronously Loading)

@Test
    public void test() throws Exception {
        AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .buildAsync(key -> getName(key));

        List<String> keys = new ArrayList<>();
        keys.add("reed");
        keys.add("fan");
        CompletableFuture<Map<String, String>> names = asyncLoadingCache.getAll(keys);
        Map<String, String> map = names.get();

        for (Map.Entry<String, String> m : map.entrySet()) {
            System.out.println("key:" + m.getKey() + " value:" + m.getValue());
            /*key:reed value:reed
            key:fan value:fan*/
        }
    }

    private String getName(String str) {
        return str;
    }