当前有四个实现,RedisCache、TairCache(此部分未在github开源)、CaffeineCache(in memory)和一个简易的LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。
<dependency>
<groupId>com.alicp.jetcachegroupId>
<artifactId>jetcache-starter-redisartifactId>
<version>2.4.4version>
dependency>
jetcache.statIntervalMinutes: 15
jetcache.statIntervalMinutes: 15
jecache.local.default.type: linkedhashmap
jecache.local.default.keyConvertor: fastjson
jetcache.remote.default.type: redis
jetcache.remote.default.keyConvertor: fastjson
jetcache.remote.default.valueEncoder: java
jetcache.remote.default.valueDecoder: java
jetcache.remote.default.poolConfig.minIdle:5
jetcache.remote.default.poolConfig.maxIdle:20
jetcache.remote.default.poolConfig.maxTotal:50
jetcache.remote.default.host:127.0.0.1
jetcache.remote.default.port:6379
在启动类上加注解
@EnableMethodCache(basePackages="com.company.mypackage")
@EnableCreateCacheAnnotation
@CreateCache(expire = 100)
private Cache userCache;
具体说明https://github.com/alibaba/jetcache/wiki/CreateCache_CN:
注解可以加在接口上也可以加在类上,加注解的类必须是一个spring bean
具体说明https://github.com/alibaba/jetcache/wiki/MethodCache_CN:
配置详解
具体说明https://github.com/alibaba/jetcache/wiki/Config_CN:
自定义监控
@Configuration
public class JetCacheConfig {
@Bean
public SpringConfigProvider springConfigProvider() {
return new SpringConfigProvider() {
@Override
public Consumer statCallback() {
return new Consumer() {
@Override
public void accept(StatInfo stat) {
List stats = stat.getStats();
if (stats != null && stats.size() > 0) {
for (CacheStat c : stats) {
System.out.println(DateUtil.getNow());
System.out.println("mycount............");
String info = "";
info += c.getCacheName() + ",";
info += c.getGetCount() + ",";
info += c.getGetHitCount() + ",";
info += c.getGetFailCount();
System.out.println(info);
}
}
}
};
}
};
}
}
自定义监控可以将监控信息输出到任何地方
jetchache默认的实现是:return new StatInfoLogger(false);
StatInfoLogger的构造参数设置为true会有更详细的统计信息,包括put等操作的统计。
StatInfoLogger输出的是给人读的信息,你也可以自定义logger将日志输出成特定格式,然后通过日志系统统一收集和统计。
如果想要让jetcache的日志输出到独立的文件中,在使用logback的情况下可以这样配置:
<appender name="JETCACHE_LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>jetcache.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>jetcache.log.%d{yyyy-MM-dd}fileNamePattern>
<maxHistory>30maxHistory>
rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%npattern>
encoder>
appender>
<logger name="com.alicp.jetcache" level="INFO" additivity="false">
<appender-ref ref="JETCACHE_LOGFILE" />
logger>
V get(K key)
void put(K key, V value);
boolean putIfAbsent(K key, V value); //多级缓存MultiLevelCache不支持此方法
boolean remove(K key);
T unwrap(Class clazz);//2.2版本前,多级缓存MultiLevelCache不支持此方法
Map<K,V> getAll(Set extends K> keys);
void putAll(Map extends K,? extends V> map);
void removeAll(Set extends K> keys);
特有的方法
V computeIfAbsent(K key, Function loader)
当key对应的缓存不存在时,使用loader加载。通过这种方式,loader的加载时间可以被统计到。
V computeIfAbsent(K key, Function loader, boolean cacheNullWhenLoaderReturnNull)
当key对应的缓存不存在时,使用loader加载。cacheNullWhenLoaderReturnNull参数指定了当loader加载出来时null值的时候,是否要进行缓存(有时候即使是null值也是通过很繁重的查询才得到的,需要缓存)
V computeIfAbsent(K key, Function loader, boolean cacheNullWhenLoaderReturnNull, long expire, TimeUnit timeUnit)
当key对应的缓存不存在时,使用loader加载。cacheNullWhenLoaderReturnNull参数指定了当loader加载出来时null值的时候,是否要进行缓存(有时候即使是null值也是通过很繁重的查询才得到的,需要缓存)。expire和timeUnit指定了缓存的超时时间,会覆盖缓存的默认超时时间。
void put(K key, V value, long expire, TimeUnit timeUnit)
put操作,expire和timeUnit指定了缓存的超时时间,会覆盖缓存的默认超时时间。
AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit)
boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action)
非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。用法如下:
// 使用try-with-resource方式,可以自动释放锁
try(AutoReleaseLock lock = cache.tryLock("MyKey",100, TimeUnit.SECONDS)){
if(lock != null){
// do something
}
}
上面的代码有个潜在的坑是忘记判断if(lock!=null),所以一般可以直接用tryLockAndRun更加简单
boolean hasRun = tryLockAndRun("MyKey",100, TimeUnit.SECONDS), () -> {
// do something
};
CacheGetResult GET(K key);
MultiGetResult<K, V> GET_ALL(Set extends K> keys);
CacheResult PUT(K key, V value);
CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
CacheResult PUT_ALL(Map extends K, ? extends V> map);
CacheResult PUT_ALL(Map extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);
CacheResult REMOVE(K key);
CacheResult REMOVE_ALL(Set extends K> keys);
CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
这些方法的特征是方法名为大写,与小写的普通方法对应,提供了完整的返回值,用起来也稍微繁琐一些。例如:
CacheGetResult r = cache.GET(orderId);
if( r.isSuccess() ){
OrderDO order = r.getValue();
} else if (r.getResultCode() == CacheResultCode.NOT_EXISTS) {
System.out.println("cache miss:" + orderId);
} else if(r.getResultCode() == CacheResultCode.EXPIRED) {
System.out.println("cache expired:" + orderId));
} else {
System.out.println("cache get error:" + orderId);
}
Cache cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
.limit(100)
.expireAfterWrite(200, TimeUnit.SECONDS)
.buildCache();
GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
pc.setMinIdle(2);
pc.setMaxIdle(10);
pc.setMaxTotal(10);
JedisPool pool = new JedisPool(pc, "localhost", 6379);
Cache orderCache = RedisCacheBuilder.createRedisCacheBuilder()
.keyConvertor(FastjsonKeyConvertor.INSTANCE)
.valueEncoder(JavaValueEncoder.INSTANCE)
.valueDecoder(JavaValueDecoder.INSTANCE)
.jedisPool(pool)
.keyPrefix("orderCache")
.expireAfterWrite(200, TimeUnit.SECONDS)
.buildCache();
Cache multiLevelCache = MultiLevelCacheBuilder.createMultiLevelCacheBuilder()
.addCache(memoryCache, redisCache)
.expireAfterWrite(100, TimeUnit.SECONDS)
.buildCache();
Cache orderCache = ...
CacheMonitor orderCacheMonitor = new DefaultCacheMonitor("OrderCache");
orderCache.config().getMonitors().add(orderCacheMonitor); // jetcache 2.2+, or call builder.addMonitor() before buildCache()
// Cache monitedOrderCache = new MonitoredCache(orderCache, orderCacheMonitor); //before jetcache 2.2
int resetTime = 1;
boolean verboseLog = false;
DefaultCacheMonitorManager cacheMonitorManager = new DefaultCacheMonitorManager(resetTime, TimeUnit.SECONDS, verboseLog);
cacheMonitorManager.add(orderCacheMonitor);
cacheMonitorManager.start();
==CacheBuilder提供使用代码直接构造Cache实例的方式,使用说明。如果没有使用Spring,可以使用CacheBuilder,否则没有必要直接使用CacheBuilder。==
从JetCache2.2版本开始,所有的大写API返回的CacheResult都支持异步。当底层的缓存实现支持异步的时候,大写API返回的结果都是异步的。当前支持异步的实现只有jetcache的redis-luttece实现,其他的缓存实现(内存中的、Tair、Jedis等),所有的异步接口都会同步堵塞,这样API仍然是兼容的。
以下的例子假设使用redis-luttece访问cache,例如:
CacheGetResult r = cache.GET(userId);
CompletionStage future = r.future();
future.thenRun(() -> {
if(r.isSuccess()){
System.out.println(r.getValue());
}
});
LoadingCache类提供了自动load的功能,它是一个包装,基于decorator模式,也实现了Cache接口。如果CacheBuilder指定了loader,那么buildCache返回的Cache实例就是经过LoadingCache包装过的。例如:
Cache<Long,UserDO> userCache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
.loader(key -> loadUserFromDatabase(key))
.buildCache();
LoadingCache的get和getAll方法,在缓存未命中的情况下,会调用loader,如果loader抛出一场,get和getAll会抛出CacheInvokeException。
注意:
1. GET、GET_ALL这类大写API只纯粹访问缓存,不会调用loader。
2. 如果使用多级缓存,loader应该安装在MultiLevelCache上,不要安装在底下的缓存上。
==springboot自动load==:注解的属性只能是常量,所以没有办法在CreateCache注解中指定loader,不过我们可以这样:
@CreateCache
private Cache userCache;
@PostConstruct
public void init(){
userCache.config().setLoader(this::loadUserFromDatabase);
}
从JetCache2.2版本开始,RefreshCache基于decorator模式提供了自动刷新的缓存的能力,目的是为了防止缓存失效时造成的雪崩效应打爆数据库。同时设置了loader和refreshPolicy的时候,CacheBuilder的buildCache方法返回的Cache实例经过了RefreshCache的包装。
RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES)
.stopRefreshAfterLastAccess(30, TimeUnit.MINUTES);
Cache orderSumCache = LinkedHashMapCacheBuilder
.createLinkedHashMapCacheBuilder()
.loader(key -> loadOrderSumFromDatabase(key))
.refreshPolicy(policy)
.buildCache();
上面的代码指定每分钟刷新一次,30分钟如果没有访问就停止刷新。如果缓存是redis或者多级缓存最后一级是redis,缓存加载行为是全局唯一的,也就是说不管有多少台服务器,同时只有一个服务器在刷新,这是通过tryLock实现的,目的是为了降低后端的加载负担。
==springboot自动刷新缓存==, 与LoadingCache一样,使用@CreateCache时,我们需要这样来添加自动刷新功能
@CreateCache
private Cache orderSumCache;
@PostConstruct
public void init(){
RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES)
.stopRefreshAfterLastAccess(30, TimeUnit.MINUTES);
orderSumCache.config().setLoader(this::loadOrderSumFromDatabase);
orderSumCache.config().setRefreshPolicy(policy);
}