天天看點

guava-2(cache緩存)為什麼用guava cachecache整合springboot

緩存

  • 為什麼用guava cache
    • 最簡單demo
    • key不存在也想傳回某個計算邏輯資料
      • 第一種方式
      • 第二種形式
    • 限制緩存大小
      • 緩存條數
      • 緩存自定義資料,可以時value的大小
    • 限制緩存時間
      • expireAfterWrite:從寫入時間開始算
      • expireAfterAccess:最後一次通路時間
    • 緩存回收
    • 監聽器
    • 統計分析
  • cache整合springboot

為什麼用guava cache

有些情況我們不需要用redis,覺得大材小用了,二guava正好是本地緩存中比較優秀的一種,guava cache跟ConcurrentMap很想,都是通過鍵取對應的值,但是ConcurrentMap不能隐式的移除裡面的資料,無法規定大小限制(也可以,需要自己實作邏輯)而guava cache已經幫我們實作了這些。

最簡單demo

static Cache<String,Student> cache = CacheBuilder.newBuilder().build();
public static void main(String[] args) {
     cache.put("key1",new Student(18,"李靜",2,2));
     cache.put("key2",new Student(7,"王萍",2,2));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
}
           

key不存在也想傳回某個計算邏輯資料

第一種方式

使用LoadingCache接收,如果LoadingCache接受,則必須傳CacheLoader參數,多了一個get方法,此get方法隻傳key就可以,不需要傳callable,因為定義全局的時候已經設定了。

public static void main(String[] args) throws ExecutionException {
 //如果根據key取不到值,也需要傳回一個資料
 LoadingCache<String,Student> cache = CacheBuilder.newBuilder().build(new CacheLoader<String, Student>() {
        @Override
        public Student load(String key) throws Exception {
            return new Student(0,"我是預設值",0,0);
        }
    });
    cache.put("key1",new Student(18,"李靜",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    System.out.println(JSON.toJSONString(cache.get("key1")));
    System.out.println(JSON.toJSONString(cache.get("kkk")));
}
           

{“age”:18,“grade”:2,“name”:“李靜”,“sex”:2}

{“age”:0,“grade”:0,“name”:“我是預設值”,“sex”:0}

第二種形式

可以看出第二種形式時,是對每個鍵都需要設定預設值,而第一中方式,我們可以全局設定,根據key執行某些算法邏輯傳回需要的預設值。

public static void main(String[] args) throws ExecutionException {
 //如果根據key取不到值,也需要傳回一個資料
    Cache<String,Student> cache = CacheBuilder.newBuilder().build();
    cache.put("key1",new Student(18,"李靜",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.get("kkk",()->{
        return new Student(0,"我是kkk這個健的預設值",0,0);
    })));
}
           

{“age”:18,“grade”:2,“name”:“李靜”,“sex”:2}

{“age”:0,“grade”:0,“name”:“我是kkk這個健的預設值”,“sex”:0}

限制緩存大小

緩存條數

如果大于緩存中的條數,最老的一條緩存會被回收掉

public static void main(String[] args) throws ExecutionException {
  //如果根據key取不到值,也需要傳回一個資料
    Cache<String,Student> cache = CacheBuilder.newBuilder().maximumSize(2).build();
    cache.put("key1",new Student(18,"李靜",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    cache.put("key3",new Student(66,"萌萌牛",2,2));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
}
           

null

{“age”:7,“grade”:2,“name”:“王萍”,“sex”:2}

{“age”:66,“grade”:2,“name”:“萌萌牛”,“sex”:2}

緩存自定義資料,可以時value的大小

maximumWeight設定為200,會根據weigher的參數進行計算,如果和接近200就開始回收了,測試時臨界是應該是200的一半左右,大于100後緩存就加不進去了。

maximumSize(long)、maximumWeight(long)是互斥的,隻能二選

public static void main(String[] args) throws ExecutionException {
    //如果根據key取不到值,也需要傳回一個資料
     //緩存回收也是在重量逼近限定值時就進行了,還要知道重量是在緩存建立時計算的,是以要考慮重量計算的複雜度。
     //緩存回收政策暫時不明
     Cache<String,Student> cache = CacheBuilder.newBuilder().maximumWeight(200).weigher((a,b)->{
         byte[] bytes = JSON.toJSONBytes(b);
         System.out.println(String.format("key [%s] 緩存[%s]大小為 %s",a,JSON.toJSONString(b),bytes.length));
         return bytes.length;
     }).build();
     cache.put("key1",new Student(18,"李靜",2,2));
     cache.put("key2",new Student(7,"王萍",2,2));
     cache.put("key3",new Student(66,"萌萌牛",2,2));
     cache.put("key4",new Student(36,"萌萌虎",2,2));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key4")));
}
           
key [key1] 緩存[{"age":18,"grade":2,"name":"李靜","sex":2}]大小為 44
key [key2] 緩存[{"age":7,"grade":2,"name":"王萍","sex":2}]大小為 43
key [key3] 緩存[{"age":66,"grade":2,"name":"萌萌牛","sex":2}]大小為 47
key [key4] 緩存[{"age":36,"grade":2,"name":"萌萌虎","sex":2}]大小為 47
null
null
{"age":66,"grade":2,"name":"萌萌牛","sex":2}
{"age":36,"grade":2,"name":"萌萌虎","sex":2}
           

maximumSize(long)隻能限制條數。

maximumWeight(long)可以用來控制記憶體。比如我們代碼中例子。

限制緩存時間

有兩種形式

expireAfterWrite:從寫入時間開始算

public static void main(String[] args) throws ExecutionException, InterruptedException {
    //如果根據key取不到值,也需要傳回一個資料
    Cache<String,Student> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).maximumSize(20).build();
    cache.put("key1",new Student(18,"李靜",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    cache.put("key3",new Student(66,"萌萌牛",2,2));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
    TimeUnit.SECONDS.sleep(4);
    System.out.println("超過4秒後!!!!!!!!!!!!緩存時間為3秒");
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
}
           

expireAfterAccess:最後一次通路時間

public static void main(String[] args) throws ExecutionException, InterruptedException {
	//如果根據key取不到值,也需要傳回一個資料
	Cache<String,Student> cache = CacheBuilder.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS).maximumSize(20).build();
	cache.put("key1",new Student(18,"李靜",2,2));
	cache.put("key2",new Student(7,"王萍",2,2));
	cache.put("key3",new Student(66,"萌萌牛",2,2));
	for (int i = 0; i < 10; i++) {
	    TimeUnit.SECONDS.sleep(1);
	    //模式key1永遠通路,就是永不删除key緩存
	    cache.getIfPresent("key1");
	    System.out.println(String.format("[%d 秒後,緩存情況] %s",i+1,JSON.toJSONString(cache.asMap())));
	}
}
           

結果

[1 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2},"key2":{"age":7,"grade":2,"name":"王萍","sex":2},"key3":{"age":66,"grade":2,"name":"萌萌牛","sex":2}}
[2 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2},"key2":{"age":7,"grade":2,"name":"王萍","sex":2},"key3":{"age":66,"grade":2,"name":"萌萌牛","sex":2}}
[3 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[4 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[5 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[6 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[7 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[8 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[9 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
[10 秒後,緩存情況] {"key1":{"age":18,"grade":2,"name":"李靜","sex":2}}
           

可以看到,key1永遠被通路,key1永遠不會删除。

緩存回收

前面我們講了如果緩存超過一定數量或者時間的時候,緩存會自動回收,那麼我們如何手動回收緩存呢。

api 說明
Cache.invalidate(key) 根據指定鍵删除
Cache.invalidateAll(keys) 根據指定鍵集合删除
Cache.invalidateAll() 删除所有緩存

監聽器

public static void main(String[] args) throws ExecutionException, InterruptedException {
      //如果根據key取不到值,也需要傳回一個資料
        Cache<String,Student> cache = CacheBuilder
                .newBuilder()
                .expireAfterAccess(3, TimeUnit.SECONDS)
                .removalListener(a->{
                    System.out.println(String.format("删除的key [%s] value [%s],删除原因 [%s]",a.getKey(),a.getValue(),a.getCause()));
                    System.out.println("删除的 key[" + a.getKey() + "],value[" + a.getValue() + "],remove reason[" + a.getCause() + "]");
                })
                .maximumSize(2).build();
        cache.put("key1",new Student(18,"李靜",2,2));
        cache.invalidateAll();
        cache.put("key2",new Student(7,"王萍",2,2));
        cache.put("key3",new Student(66,"萌萌牛",2,2));
        cache.put("key3",new Student(1,"萌萌牛兒子",2,2));
        cache.put("key4",new Student(77,"萌萌虎",2,2));
        Thread.sleep(6000);
}
           

結果

删除的key [key1] value [com.example.demo.vo.Student@4361bd48],删除原因 [EXPLICIT]
删除的 key[key1],value[com.example.demo.vo.Student@4361bd48],remove reason[EXPLICIT]
删除的key [key3] value [com.example.demo.vo.Student@53bd815b],删除原因 [REPLACED]
删除的 key[key3],value[com.example.demo.vo.Student@53bd815b],remove reason[REPLACED]
删除的key [key2] value [com.example.demo.vo.Student@2a33fae0],删除原因 [SIZE]
删除的 key[key2],value[com.example.demo.vo.Student@2a33fae0],remove reason[SIZE]
           

key1:手動删除 EXPLICIT(顯示)

key3:為替換,reason:REPLACED(替換)

key2:為超過最大條數删除 SIZE(大小)

現在不知為什麼逾時删除緩存并沒有被監聽到

統計分析

public static void main(String[] args) throws ExecutionException, InterruptedException {
       //如果根據key取不到值,也需要傳回一個資料
        Cache<String,Student> cache = CacheBuilder
                .newBuilder()
                .expireAfterWrite(11, TimeUnit.SECONDS)
                .recordStats()
                .removalListener(a->{
                    System.out.println(String.format("删除的key [%s] value [%s],删除原因 [%s]",a.getKey(),a.getValue(),a.getCause()));
                    System.out.println("删除的 key[" + a.getKey() + "],value[" + a.getValue() + "],remove reason[" + a.getCause() + "]");
                })
                .maximumSize(2).build();
        cache.put("key1",new Student(18,"李靜",2,2));
        cache.invalidateAll();
        cache.put("key2",new Student(7,"王萍",2,2));
        cache.put("key3",new Student(66,"萌萌牛",2,2));
        cache.put("key3",new Student(1,"萌萌牛兒子",2,2));
        cache.put("key4",new Student(77,"萌萌虎",2,2));

        System.out.println(cache.getIfPresent("key1"));
        System.out.println(cache.getIfPresent("key4"));
        System.out.println(cache.stats());
}
           

結果:命中緩存的資料等等資訊

删除的key [key1] value [com.example.demo.vo.Student@33833882],删除原因 [EXPLICIT]
删除的 key[key1],value[com.example.demo.vo.Student@33833882],remove reason[EXPLICIT]
删除的key [key3] value [com.example.demo.vo.Student@200a570f],删除原因 [REPLACED]
删除的 key[key3],value[com.example.demo.vo.Student@200a570f],remove reason[REPLACED]
删除的key [key2] value [com.example.demo.vo.Student@1e81f4dc],删除原因 [SIZE]
删除的 key[key2],value[com.example.demo.vo.Student@1e81f4dc],remove reason[SIZE]
null
com.example.demo.vo.Student@4d591d15
CacheStats{hitCount=1, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=1}
           

cache整合springboot

spring5開始不再支援guava cache,改用性能更高的Caffeine代替