自命为缓存之王的Caffeine(5)
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~
普通的缓存和Token的区别在于时效性和持久性。如果用Redis实现Token的话,可以:
1、设置redis kv键值对的过期时间(秒数/毫秒数);
2、redis内部实现计时,无需代码干预,且有持久化;
3、kv超过指定过期时间即被自动删除。
自定义缓存计时非常麻烦,大部分中间件又没有过期失效。如果只是单节点,完全可以用Caffeine替代Redis。这只是一次有益的尝试,发现更多的可能性。
通过对缓存(而非Redis)功能的分析,可知几个关键点:
1、只要缓存失效即可,是否「过期」不是主要问题;
2、是否自动删除不重要,重要的是删除过期值,这个完全可以用代码实现;
3、既然自动计时用代码实现很麻烦,那么是不是可以换个思路呢?
想通了这几个问题,就可以通过变通的方式「曲线救国」,完全实现Redis的缓存功能。利用Mongo + Caffeine的方式,替代Redis的Token存储功能。
引入依赖与增加配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <exclusions> <exclusion> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> </exclusion> </exclusions> </dependency>
在配置文件中增加配置:
## MONGO spring.data.mongodb.host=172.16.185.135 spring.data.mongodb.port=27017 spring.data.mongodb.database=0 spring.data.mongodb.username=test spring.data.mongodb.password=123456
定义Mongo配置类:
/** * 去除_class字段的配置类 * * @author 湘王 */ @Configuration public class MongoConfigure implements InitializingBean { @Resource private MappingMongoConverter mappingConverter; @Override public void afterPropertiesSet() throws Exception { // 去除插入数据库的_class字段 mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null)); } }
定义Cache类:
/** * 缓存Document * * @author bear.xiong */ public class Cache implements Serializable { private static final long serialVersionUID = 7353685666928500768L; @Id private String id; private String key; private String value; private long time; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } @Override public String toString() { return String.format("{\"id\":\"%s\", \"key\":\"%s\", \"value\":\"%s\", \"time\":%d}", id, key, value, time); } }
创建CacheDao类:
/** * 缓存Dao * * @author 湘王 */ @Component public class CacheDao<T> { @Autowired private MongoTemplate mongoTemplate; // expiretime指的是从存储到失效之间的时间间隔,单位毫秒 @Cacheable(value = "test", key = "#key") public String getObject(final String key, final long expiretime) { Query query = new Query(Criteria.where("key").is(key)); Cache cache = (Cache) mongoTemplate.findOne(query, Cache.class); System.out.println("getObject(" + key + ", " + expiretime + ") from mongo"); if (null != cache) { // -1表示永不过期 if (-1 == expiretime) { return cache.getValue(); } // 如果当前时间 - 存储cache时的时间 >= 过期间隔 long currentTtime = System.currentTimeMillis(); if (currentTtime - cache.getTime() >= expiretime * 1000) { // 删除key,并返回null removeObject(key); } else { return cache.getValue(); } } return null; } // 保存时,需要增加过期时间,方便同步到Caffeine @CachePut(value = "test", key = "#key") public boolean saveObject(final String key, final String value, final long expiretime) { Query query = new Query(Criteria.where("key").is(key)); Update update = new Update(); long time = System.currentTimeMillis(); update.set("key", key); update.set("value", value); update.set("time", time); try { UpdateResult result = mongoTemplate.upsert(query, update, Cache.class); if (result.wasAcknowledged()) { return true; } } catch (Exception e) { e.printStackTrace(); } return false; } @CacheEvict(value = "test", key = "#key") public boolean removeObject(final String key) { Query query = new Query(Criteria.where("key").is(key)); try { DeleteResult result = mongoTemplate.remove(query, Cache.class); if (result.wasAcknowledged()) { return true; } } catch (Exception e) { e.printStackTrace(); } return false; } }
创建CacheService类:
/** * 缓存Service接口 * * @author 湘王 */ @Service public class CacheService { @Autowired private CacheDao<Cache> cacheDao; public String getObject(final String key, final long expiretime) { return cacheDao.getObject(key, expiretime); } public boolean saveObject(final String key, final String value, final long expiretime) { return cacheDao.saveObject(key, value, expiretime); } public boolean removeObject(final String key) { return cacheDao.removeObject(key); } }
最后再创建CacheController类:
@RestController public class CacheController { @Autowired private CacheService cacheService; @GetMapping("/cache/save") public void save(final String key, final String value, final int expiretime) { cacheService.saveObject(key, value, expiretime); } // 获取数据,过期时间为秒(会转换为毫秒) @GetMapping("/cache/get") public String get(final String key, final int expiretime) { String result = cacheService.getObject(key, expiretime); if (null == result) { return "expire value"; } return result; } }
测试后发现:先保存KV,再获取key,过期时间为3秒。但即使过了3秒,还是能获取到保存的数据,这是为什么呢?
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~