jetcache(1)基本使用

基本使用

当前有四个实现,RedisCache、TairCache(此部分未在github开源)、CaffeineCache(in memory)和一个简易的LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。

  • pom
<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
@CreateCache(expire = 100)
private Cache userCache;

具体说明https://github.com/alibaba/jetcache/wiki/CreateCache_CN:

  • 方法缓存
    • @Cached注解可以为一个方法添加缓存
    • @CacheUpdate用于更新缓存
    • @CacheInvalidate用于移除缓存元素

注解可以加在接口上也可以加在类上,加注解的类必须是一个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>

基本的cache api

  • javax.cache.Cache接口一致的方法
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(SetK> keys);
void putAll(MapK,? extends V> map);
void removeAll(SetK> 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
    };
    
  • 大写的方法
    V get(K key)这样的方法虽然用起来方便,但有功能上的缺陷,当get返回null的时候,无法断定是对应的key不存在,还是访问缓存发生了异常,所以JetCache针对部分操作提供了另外一套API,提供了完整的返回值,包括:
CacheGetResult GET(K key);
MultiGetResult<K, V> GET_ALL(SetK> keys);
CacheResult PUT(K key, V value);
CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
CacheResult PUT_ALL(MapK, ? extends V> map);
CacheResult PUT_ALL(MapK, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);
CacheResult REMOVE(K key);
CacheResult REMOVE_ALL(SetK> 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 API

CacheBuilder

创建本地缓存
Cache cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
                .limit(100)
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();
创建redis缓存
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。==

异步api

从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());
    }
});

自动load

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);
}

你可能感兴趣的:(缓存/redis)