首页 > 其他分享 >自命为缓存之王的Caffeine(6)

自命为缓存之王的Caffeine(6)

时间:2023-02-17 22:23:42浏览次数:31  
标签:expiretime 缓存 return 过期 Caffeine 自命为 key final

您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~

 


 

 

之前用Caffeine替代Redis的时候,发现先保存KV,再获取key,过期时间为3秒。但即使过了3秒,还是能获取到保存的数据。这是为什么呢?因为之前在整合SpringBoot时,使用的是注解方式,在配置文件中已经定死了Caffeine的过期时间。

## Caffeine

spring.cache.cache-names=test

spring.cache.type=caffeine

spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=300s

就是因为这里的expireAfterWrite=300s导致数据3秒后不能清除。经过测试,发现果然是300秒后Caffeine过期。

使用注解式的Caffeine,应用一旦启动,是无法动态调整过期时间的,必然与MongoDB时间不同步。

进一步延伸思考:Caffeine是没有持久化功能的,所以当应用重新启动的时候,上一次为Caffeine设置的过期时间会被重置。因此Caffeine + MongoDB替代Redis存储Token其实需要解决一个很关键的问题:MongoDB和Caffeine过期时间的同步问题,也就是Caffeine的过期时间要能够灵活调整的问题。

 

 

 

所以,需要放弃注解式Caffeine,使用自定义LoadingCache。当MongoDB保存时,就要同步到Caffeine。而当应用重启时,就要重新同步Caffeine。

修改CacheDao,增加LoadingCache定义:

 1 private static LoadingCache<String, String> loadingCache = null;
 2 
 3 /**
 4  * 自定义LoadingCache,指定过期时间expiretime
 5  *
 6  */
 7 private LoadingCache<String, String> initCache(long expiretime) {
 8     return Caffeine.newBuilder()
 9             .initialCapacity(1)
10             .maximumSize(100)
11             .expireAfterWrite(expiretime, TimeUnit.MILLISECONDS)
12             .build(key -> {
13                 // 没有数据或过期时返回null
14                 return null;
15             });
16 }

 

注意:时间单位是TimeUnit.MILLISECONDS,搞错了就看不到效果了。

修改saveObject()方法:

 1 /**
 2  * 保存时,需要增加过期时间,方便同步到Caffeine
 3  *
 4  * @param key
 5  * @param value
 6  * @param expiretime
 7  * @return
 8  */
 9 public boolean saveObject(final String key, final String value, final long expiretime) {
10     Query query = new Query(Criteria.where("key").is(key));
11     Update update = new Update();
12     long time = System.currentTimeMillis();
13     update.set("key", key);
14     update.set("value", value);
15     update.set("time", time);
16     try {
17         UpdateResult result = mongoTemplate.upsert(query, update, Cache.class);
18         if (result.wasAcknowledged()) {
19             // 同步到Caffeie
20             loadingCache = initCache(expiretime * 1000);
21             loadingCache.put(key, value);
22             return true;
23         }
24     } catch (Exception e) {
25         e.printStackTrace();
26     }
27     return false;
28 }

 

注意其中的同步到Caffeine那两行。

最后,修改getObject()——重点来了!这是最关键的一步,应用重启之后还能否和MongoDB保持时间同步,就在于它了:

 1 // expiretime指的是从存储到失效之间的时间间隔,单位毫秒
 2 public String getObject(final String key, final long expiretime) {
 3     String result = null;
 4     // loadingCache不为空说明之前已经同步过了,可以直接读取它的值
 5     if (null != loadingCache) {
 6         result = loadingCache.get(key);
 7         if (null != result) {
 8             // 读取到值时,直接返回,读取不到就去mongodb读取
 9             return result;
10         }
11     }
12     Query query = new Query(Criteria.where("key").is(key));
13     Cache cache = (Cache) mongoTemplate.findOne(query, Cache.class);
14     System.out.println("getObject(" + key + ", " + expiretime + ") from mongo");
15 
16     if (null != cache) {
17         // -1表示永不过期
18         if (-1 == expiretime) {
19             return cache.getValue();
20         }
21         // 如果当前时间 - 存储cache时的时间 >= 过期间隔
22         long currentTtime = System.currentTimeMillis();
23         if (currentTtime - cache.getTime() >= expiretime * 1000) {
24             // 删除key,并返回null
25             removeObject(key);
26         } else {
27             /**
28              * 需要计算出当前时间与过期时间之间的差值,并赋予Caffeine的失效时间
29              * 计算过程分析:
30              * 保存时间:00:00
31              * 当前时间:00:03
32              * 过期时间:10秒
33              * 那么第一次读取时需要将剩余的7秒赋给Caffeine
34              */
35             if (null == loadingCache) {// loadingCache==null说明loadingCache需要同步
36                 loadingCache = initCache(expiretime * 1000 - (currentTtime - cache.getTime()));
37                 loadingCache.put(key, cache.getValue());
38             }
39             return cache.getValue();
40         }
41     }
42     return null;
43 }

 

由于保存时增加了过期时间,Service和Controller也要修改:

 1 /**
 2  * 缓存Service接口
 3  *
 4  * @author 湘王
 5  */
 6 @Service
 7 public class CacheService {
 8    @Autowired
 9    private CacheDao<Cache> cacheDao;
10 
11    public String getObject(final String key, final long expiretime) {
12       return cacheDao.getObject(key, expiretime);
13    }
14 
15    /**
16     * 增加了过期时间expiretime
17     *
18     * @param key
19     * @param value
20     * @param expiretime
21     * @return
22     */
23    public boolean saveObject(final String key, final String value, final long expiretime) {
24       return cacheDao.saveObject(key, value, expiretime);
25    }
26 
27    public boolean removeObject(final String key) {
28       return cacheDao.removeObject(key);
29    }
30 }

 

 1 /**
 2  * Cache控制器
 3  *
 4  * 湘王
 5  */
 6 @RestController
 7 public class CacheController {
 8     @Autowired
 9     private CacheService cacheService;
10 
11     /**
12      * 增加了过期时间expiretime
13      *
14      * @param key
15      * @param value
16      * @param expiretime
17      */
18     @GetMapping("/cache/save")
19     public void save(final String key, final String value, final int expiretime) {
20         cacheService.saveObject(key, value, expiretime);
21     }
22 
23     // 获取数据,过期时间为秒(会转换为毫秒)
24     @GetMapping("/cache/get")
25     public String get(final String key, final int expiretime) {
26         String result = cacheService.getObject(key, expiretime);
27         if (null == result) {
28             return "expire value";
29         }
30         return result;
31     }
32 }

 

修改后测试:

1、启动应用,通过save()保存,再通过get()读取,有效;

2、启动应用,通过get()读取,读取不到值(因为未设置),有效; 

3、启动应用,通过save()保存,停止服务并稍后重启(可以在过期时间内重启,也可以在过期时间外重启):

3.1、通过get()读取,如果是在有效期内,能够读取到值,有效;

3.2、通过get()读取,如果超过有效期,就读取不到值了,有效。

 

 


 

 

感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

标签:expiretime,缓存,return,过期,Caffeine,自命为,key,final
From: https://www.cnblogs.com/xiangwang1111/p/17131624.html

相关文章

  • 使用springboot cache + redis缓存时使用gzip压缩以提升性能
    背景在高并发的场景中,我们通常会使用缓存提升性能。在使用springbootcache时,我们通常会使用基于JSON的序列化与反序列化。JSON具有可读性强,结构简单的特点,使用灵活。但......
  • vue基于微前端qiankun的多页签缓存方案实践
    一、多页签是什么?常见的浏览器多页签、编辑器多页签,从产品角度来说,就是为了能够实现用户访问可记录,快速定位工作区等作用;那对于单页应用,可以通过实现多页签,对用户的访问记......
  • [10] 缓存穿透&击穿&雪崩&过期
    Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。......
  • 自命为缓存之王的Caffeine(5)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~   普通的缓存和Token的区别在于时效性和持久性。如果用Redis实现Token的话,可以:1、设置rediskv键值对的过期时间(......
  • 在 Flask 中使用 Redis 来缓存数据
    (一)Redis简介Redis是一个高性能的key-value数据库,它是基于内存运行的数据库,因此有很高的性能,存取速度非常快,而且Redis还可以定期的将数据同步到磁盘中,实现数据的持......
  • 云音乐 iOS 跨端缓存库
    云音乐iOS跨端缓存库-NEMichelinCachehttps://mp.weixin.qq.com/s/jZ6QEuc0qoAn27lYzN1Yfw云音乐iOS跨端缓存库-NEMichelinCache原创 绎推 网易云音乐技术团......
  • 多级缓存降低高并发压力
    多级缓存简介1.传统缓存传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图:存在下面的问题:•由于redis的承受能力大于tomcat,所以请求要经......
  • 使用indexDB对接口请求进行简单的缓存数据处理
    exportfunction_getAction(url:string,param?:any,header?:any){returnnewPromise((resovle:any,error:any)=>{indexDBMap.getDataByKey(JSON......
  • Nginx一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...
    Nginx一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...Linux就该这么学 2023-02-1508:02 发表于北京作者:竹子爱熊猫  来源:juejin.cn/post/71......
  • Mybatis13 - 缓存介绍
    介绍理解缓存的工作机制和缓存的用途。1、缓存机制介绍2、一级缓存和二级缓存①使用顺序查询的顺序是:先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的......