最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

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

来源:博客园

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

之前用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 loadingCache = null; 2  3 /** 4  * 自定义LoadingCache,指定过期时间expiretime 5  * 6  */ 7 private LoadingCache initCache(long expiretime) { 8     return Caffeine.newBuilder() 9             .initialCapacity(1)10             .maximumSize(100)11             .expireAfterWrite(expiretime, TimeUnit.MILLISECONDS)12             .build(key -> {13                 // 没有数据或过期时返回null14                 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             // 同步到Caffeie20             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,并返回null25             removeObject(key);26         } else {27             /**28              * 需要计算出当前时间与过期时间之间的差值,并赋予Caffeine的失效时间29              * 计算过程分析:30              * 保存时间:00:0031              * 当前时间:00:0332              * 过期时间:10秒33              * 那么第一次读取时需要将剩余的7秒赋给Caffeine34              */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 cacheDao;10 11    public String getObject(final String key, final long expiretime) {12       return cacheDao.getObject(key, expiretime);13    }14 15    /**16     * 增加了过期时间expiretime17     *18     * @param key19     * @param value20     * @param expiretime21     * @return22     */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      * 增加了过期时间expiretime13      *14      * @param key15      * @param value16      * @param expiretime17      */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()读取,如果超过有效期,就读取不到值了,有效。

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

关键词: 当前时间 过程分析 这是为什么呢