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

时间:2023-02-15 21:57:46

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




普通的缓存和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秒,还是能获取到保存的数据,这是为什么呢?




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