緩存
- 為什麼用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代替