在当前的软件开发中,缓存技术被广泛应用于提高数据访问速度和降低数据库压力,如本地缓存和redis分布式缓存。小义本想实现一个caffeine+redis的多级缓存组件,但没想到又双叒叕踩坑了,今天主要聊聊本地缓存和caffeine。
本地缓存作为单机服务最先触及的缓存层,选择合适的方案对于提升应用性能和响应速度至关重要,其主要的实现方式有以下几种:
Ehcache是一个纯Java的缓存库,易于使用,提供了丰富的缓存策略,如LRU(最近最少使用)、FIFO(先进先出)等。
Guava是Google开发的Java核心库,其中的缓存模块简单易用,支持自定义缓存驱逐策略,如大小限制、时间限制等。
Caffeine相比Guava,它提供了更好的内存管理机制和更快的读写性能,可以看做是guava的增强版。
之所以选择caffeine,是因为Caffeine是为Java 8及以上版本设计的,它利用了Java 8的并发特性,提供了高性能的缓存实现,而且Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上有明显的优越性,同时过期时间设置,很符合自己要实现的组件的功能要求。本以为可以高效实现技术方案了,一写代码哪哪都是bug。
caffeine默认只支持统一设置缓存时间,如下面代码:
public static final Cache<String, Object> caffeine = Caffeine.newBuilder()
.expireAfter(new CaffeineExpiry())
.initialCapacity(100)
.maximumSize(1000)
.build();
不能针对不同的key设置个性化的过期时间,那自然就无法查看缓存项的剩余过期时间。如果想实现像redis那样有ttl命令可以查看key剩余的过期时间,需要自行扩展。
针对上述问题,还好caffeine支持自定义缓存对象,同时利用expireAfter方法可以实现对缓存项的过期时间设置,代码实现方式大致如下:
缓存对象类,设置过期时间和创建时间属性。
@Data
public class CacheObject<T> {
T data;
long expire;
long createDate;//创建时间
public CacheObject(T data, long second, long createDate) {
this.data = data;
this.expire = TimeUnit.SECONDS.toNanos(second);
this.createDate = createDate;
}
}
自定义caffeine本地缓存,可实现不同key的过期时间设置。
@Component
public class MyCaffeineCache {
private Cache<String, CacheObject> CAFFEINE;
public static final long NOT_EXPIRED = Long.MAX_VALUE;
@PostConstruct
public void init() {
CAFFEINE = Caffeine.newBuilder()
.expireAfter(new Expiry<String, CacheObject<?>>() {
@Override
public long expireAfterCreate(@NonNull String s, @NonNull CacheObject<?> cacheObject, long l) {
return cacheObject.getExpire();
}
@Override
public long expireAfterUpdate(@NonNull String s, @NonNull CacheObject<?> cacheObject, long l, @NonNegative long l1) {
return l1;
}
@Override
public long expireAfterRead(@NonNull String s, @NonNull CacheObject<?> cacheObject, long l, @NonNegative long l1) {
return l1;
}
})
.initialCapacity(100)
.maximumSize(1024)
.build();
}
public <T> void set(String key, T value, long expire) {
CacheObject<T> tCacheObject = new CacheObject<>(value, expire, System.currentTimeMillis());
CAFFEINE.put(key, tCacheObject);
}
public <T> T get(String key) {
CacheObject<?> ifPresent = CAFFEINE.getIfPresent(key);
if (Objects.isNull(ifPresent)) {
return null;
}
return (T) ifPresent.getData();
}
public void delete(String key) {
CAFFEINE.invalidate(key);
}
/**
* 获取key的剩余过期时间,单位秒
* @param key
* @return
*/
public Long getTtl(String key) {
CacheObject o = (CacheObject)CAFFEINE.getIfPresent(key);
if (Objects.isNull(o)) {
return null;
}
Long flat = ((o.getCreateDate() + o.getExpire()/1000000) - System.currentTimeMillis())/1000;
return flat;
}
}