缓存
为什么用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代替