jetcache缓存官网教程

Posted 莫非技术栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jetcache缓存官网教程相关的知识,希望对你有一定的参考价值。

努力可以从现在开始!

精品专栏

 





jetcache


简介


JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了Cache接口用于手工缓存操作。当前有四个实现,RedisCache、TairCache(此部分未在github开源)、CaffeineCache(in memory)和一个简易的LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。


全部特性:


通过统一的API访问Cache系统

通过注解实现声明式的方法缓存,支持TTL和两级缓存

通过注解创建并配置Cache实例

针对所有Cache实例和方法缓存的自动统计

Key的生成策略和Value的序列化策略是可以配置的

分布式缓存自动刷新,分布式锁 (2.2+)

异步Cache API (2.2+,使用Redis的lettuce客户端时)

Spring Boot支持


要求


JetCache需要JDK1.8、Spring Framework4.0.8以上版本。Spring Boot为可选,需要1.1.9以上版本。如果不使用注解(仅使用jetcache-core),Spring Framework也是可选的,此时使用方式与Guava/Caffeinecache类似。


文档目录


  1. 快速入门

  2. 基本Cache API

  3. 通过@CreateCache注解创建Cache实例

  4. 通过注解实现方法缓存

  5. 配置详解

  6. 高级Cache API

  7. Redis支持(两种redis客户端二选一即可)

    使用jedis客户端连接redis

    使用lettuce客户端连接redis

  8. 内存缓存LinkedHashMapCache和CaffeineCache

  9. 统计

  10. Builder:未使用Spring4(或未使用Spring)的时候,或通过Builder手工构造Cache

  11. 开发者文档

  12. 升级和兼容性指南


依赖哪个jar?


jetcache-anno-api:定义jetcache的注解和常量,不传递依赖。如果你想把Cached注解加到接口上,又不希望你的接口jar传递太多依赖,可以让接口jar依赖jetcache-anno-api。jetcache-core:核心api,完全通过编程来配置操作Cache,不依赖Spring。两个内存中的缓存实现LinkedHashMapCache和CaffeineCache也由它提供。jetcache-anno:基于Spring提供@Cached和@CreateCache注解支持。jetcache-redis:使用jedis提供Redis支持。jetcache-redis-lettuce(需要JetCache2.3以上版本):使用lettuce提供Redis支持,实现了JetCache异步访问缓存的的接口。jetcache-starter-redis:Spring Boot方式的Starter,基于Jedis。jetcache-starter-redis-lettuce(需要JetCache2.3以上版本):Spring Boot方式的Starter,基于Lettuce。


快速入门


1、创建缓存实例


通过@CreateCache注解创建一个缓存实例,默认超时时间是100秒

@CreateCache(expire = 100)private Cache<Long, UserDO> userCache;

用起来就像map一样

UserDO user = userCache.get(123L);userCache.put(123L, user);userCache.remove(123L);

创建一个两级(内存+远程)的缓存,内存中的元素个数限制在50个。

@CreateCache(name = "UserService.userCache", expire = 100, cacheType = CacheType.BOTH, localLimit = 50)private Cache<Long, UserDO> userCache;

name属性不是必须的,但是起个名字是个好习惯,展示统计数据的使用,会使用这个名字。如果同一个area两个@CreateCache的name配置一样,它们生成的Cache将指向同一个实例。


创建方法缓存


使用@Cached方法可以为一个方法添加上缓存。JetCache通过Spring AOP生成代理,来支持缓存功能。注解可以加在接口方法上也可以加在类方法上,但需要保证是个Springbean。

public interface UserService { @Cached(name="UserService.getUserById", expire = 3600) User getUserById(long userId);}


基本配置(使用Spring Boot)


如果使用SpringBoot,可以按如下的方式配置。


POM

<dependency> <groupId>com.alicp.jetcache</groupId> <artifactId>jetcache-starter-redis</artifactId> <version>2.4.4</version></dependency>

配置一个springboot风格的application.yml文件,把他放到资源目录中


jetcache: statIntervalMinutes: 15 areaInCacheName: false local: default: type: linkedhashmap keyConvertor: fastjson remote: default: type: redis keyConvertor: fastjson valueEncoder: java valueDecoder: java poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: 127.0.0.1 port: 6379


然后创建一个App类放在业务包的根下,EnableMethodCache,EnableCreateCacheAnnotation这两个注解分别激活Cached和CreateCache注解,其他和标准的SpringBoot程序是一样的。这个类可以直接main方法运行。

package com.company.mypackage;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;import com.alicp.jetcache.anno.config.EnableMethodCache;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication@EnableMethodCache(basePackages = "com.company.mypackage")@EnableCreateCacheAnnotationpublic class MySpringBootApp { public static void main(String[] args) { SpringApplication.run(MySpringBootApp.class); }}

未使用SpringBoot的配置方式


如果没有使用springboot,可以按下面的方式配置(这里使用jedis客户端连接redis为例)。


<dependency> <groupId>com.alicp.jetcache</groupId> <artifactId>jetcache-anno</artifactId> <version>2.4.4</version></dependency><dependency> <groupId>com.alicp.jetcache</groupId> <artifactId>jetcache-redis</artifactId> <version>2.4.4</version></dependency>

配置了这个JetCacheConfig类以后,可以使用@CreateCache和@Cached注解。

package com.company.mypackage;
import java.util.HashMap;import java.util.Map;
import com.alicp.jetcache.anno.CacheConsts;import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;import com.alicp.jetcache.anno.config.EnableMethodCache;import com.alicp.jetcache.anno.support.GlobalCacheConfig;import com.alicp.jetcache.anno.support.SpringConfigProvider;import com.alicp.jetcache.embedded.EmbeddedCacheBuilder;import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;import com.alicp.jetcache.redis.RedisCacheBuilder;import com.alicp.jetcache.support.FastjsonKeyConvertor;import com.alicp.jetcache.support.JavaValueDecoder;import com.alicp.jetcache.support.JavaValueEncoder;import org.apache.commons.pool2.impl.GenericObjectPoolConfig;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import redis.clients.util.Pool;
@Configuration@EnableMethodCache(basePackages = "com.company.mypackage")@EnableCreateCacheAnnotationpublic class JetCacheConfig {
@Bean public Pool<Jedis> pool(){ GenericObjectPoolConfig pc = new GenericObjectPoolConfig(); pc.setMinIdle(2); pc.setMaxIdle(10); pc.setMaxTotal(10); return new JedisPool(pc, "localhost", 6379); }
@Bean public SpringConfigProvider springConfigProvider() { return new SpringConfigProvider(); }
@Bean public GlobalCacheConfig config(SpringConfigProvider configProvider, Pool<Jedis> pool){ Map localBuilders = new HashMap(); EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder .createLinkedHashMapCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE); localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);
Map remoteBuilders = new HashMap(); RedisCacheBuilder remoteCacheBuilder = RedisCacheBuilder.createRedisCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .jedisPool(pool); remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);
GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig(); globalCacheConfig.setConfigProvider(configProvider); globalCacheConfig.setLocalCacheBuilders(localBuilders); globalCacheConfig.setRemoteCacheBuilders(remoteBuilders); globalCacheConfig.setStatIntervalMinutes(15); globalCacheConfig.setAreaInCacheName(false);
return globalCacheConfig; }}


进一步阅读


CreateCache的详细使用说明可以看这里

使用@CacheCache创建的Cache接口实例,它的API使用可以看这里

关于方法缓存(@Cached, @CacheUpdate, @CacheInvalidate)的详细使用看这里

详细的配置说明看这里。

基本Cache API


简介


JetCache2.0的核心是com.alicp.jetcache.Cache接口(以下简写为Cache),它提供了部分类似于javax.cache.Cache(JSR107)的API操作。没有完整实现JSR107的原因包括:


希望维持API的简单易用。

对于特定的远程缓存系统来说,javax.cache.Cache中定义的有些操作无法高效率的实现,比如一些原子操作方法和类似removeAll()这样的方法。

JSR107比较复杂,完整实现要做的工作很多。

JSR107 style API


以下是Cache接口中和JSR107的javax.cache.Cache接口一致的方法,除了不会抛出异常,这些方法的签名和行为和JSR107都是一样的。


V get(K key)void put(K key, V value);boolean putIfAbsent(K key, V value); //多级缓存MultiLevelCache不支持此方法boolean remove(K key);<T> T unwrap(Class<T> 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);

JetCache特有API

V computeIfAbsent(K key, Function<K, V> loader)

当key对应的缓存不存在时,使用loader加载。通过这种方式,loader的加载时间可以被统计到。

V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull)

当key对应的缓存不存在时,使用loader加载。cacheNullWhenLoaderReturnNull参数指定了当loader加载出来时null值的时候,是否要进行缓存(有时候即使是null值也是通过很繁重的查询才得到的,需要缓存)。

V computeIfAbsent(K key, Function<K, V> 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 };

tryLock内部会在访问远程缓存失败时重试,会自动释放,而且不会释放不属于自己的锁,比你自己做这些要简单。当然,基于远程缓存实现的任何分布式锁都不会是严格的分布式锁,不能和基于ZooKeeper或Consul做的锁相比。


大写API


Vget(K key)这样的方法虽然用起来方便,但有功能上的缺陷,当get返回null的时候,无法断定是对应的key不存在,还是访问缓存发生了异常,所以JetCache针对部分操作提供了另外一套API,提供了完整的返回值,包括:

CacheGetResult<V> 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<OrderDO> 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);}

通过@CreateCache注解创建Cache实例


简介


在Springbean中使用@CreateCache注解创建一个Cache实例。例如

@CreateCache(expire = 100)private Cache<Long, UserDO> userCache;

@CreateCache属性表

默认值


对于以上未定义默认值的参数,如果没有指定,将使用yml中指定的全局配置,请参考配置详解。


通过注解实现方法缓存


JetCache方法缓存和SpringCache比较类似,它原生提供了TTL支持,以保证最终一致,并且支持二级缓存。JetCache2.4以后支持基于注解的缓存更新和删除。


在spring环境下,使用@Cached注解可以为一个方法添加缓存,@CacheUpdate用于更新缓存,@CacheInvalidate用于移除缓存元素。注解可以加在接口上也可以加在类上,加注解的类必须是一个spring bean,例如:

public interface UserService { @Cached(name="userCache.", key="#userId", expire = 3600) User getUserById(long userId);
@CacheUpdate(name="userCache.", key="#user.userId", value="#user") void updateUser(User user);
@CacheInvalidate(name="userCache.", key="#userId") void deleteUser(long userId);}

key使用Spring的SpEL脚本来指定。如果要使用参数名(比如这里的key="#userId"),项目编译设置target必须为1.8格式,并且指定javac的-parameters参数,否则就要使用key="args[0]"这样按下标访问的形式。


@CacheUpdate和@CacheInvalidate的name和area属性必须和@Cached相同,name属性还会用做cache的key前缀。


@Cached注解和@CreateCache的属性非常类似,但是多几个:

jetcache缓存官网教程

jetcache缓存官网教程

@CacheInvalidate注解说明:

jetcache缓存官网教程


@CacheUpdate注解说明:

jetcache缓存官网教程

使用@CacheUpdate和@CacheInvalidate的时候,相关的缓存操作可能会失败(比如网络IO错误),所以指定缓存的超时时间是非常重要的。


@CacheRefresh注解说明:

jetcache缓存官网教程

对于以上未定义默认值的参数,如果没有指定,将使用yml中指定的全局配置,全局配置请参考配置说明。


配置详解


yml配置文件案例(如果没使用springboot,直接配置GlobalCacheConfig是类似的,参考快速入门教程):


jetcache: statIntervalMinutes: 15 areaInCacheName: false hiddenPackages: com.alibaba local: default: type: caffeine limit: 100 keyConvertor: fastjson expireAfterWriteInMillis: 100000 otherArea: type: linkedhashmap limit: 100 keyConvertor: none expireAfterWriteInMillis: 100000 remote: default: type: redis keyConvertor: fastjson valueEncoder: java valueDecoder: java poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: ${redis.host} port: ${redis.port} otherArea: type: redis keyConvertor: fastjson valueEncoder: kryo valueDecoder: kryo poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: ${redis.host} port: ${redis.port}

配置通用说明如下

jetcache缓存官网教程

jetcache缓存官网教程

上表中${area}对应@Cached和@CreateCache的area属性。注意如果注解上没有指定area,默认值是"default"。


关于缓存的超时时间,有多个地方指定,澄清说明一下:


put等方法上指定了超时时间,则以此时间为准put等方法上未指定超时时间,使用Cache实例的默认超时时间Cache实例的默认超时时间,通过在@CreateCache和@Cached上的expire属性指定,如果没有指定,使用yml中定义的全局配置,例如@Cached(cacheType=local)使用jetcache.local.default.expireAfterWriteInMillis,如果仍未指定则是无穷大

高级Cache API


CacheBuilder


CacheBuilder提供使用代码直接构造Cache实例的方式,使用说明看这里。如果没有使用Spring,可以使用CacheBuilder,否则没有必要直接使用CacheBuilder。


异步API


从JetCache2.2版本开始,所有的大写API返回的CacheResult都支持异步。当底层的缓存实现支持异步的时候,大写API返回的结果都是异步的。当前支持异步的实现只有jetcache的redis-luttece实现,其他的缓存实现(内存中的、Tair、Jedis等),所有的异步接口都会同步堵塞,这样API仍然是兼容的。


以下的例子假设使用redis-luttece访问cache,例如:


CacheGetResult<UserDO> r = cache.GET(userId);

这一行代码执行完以后,缓存操作可能还没有完成,如果此时调用r.isSuccess()或者r.getValue()或者r.getMessage()将会堵塞直到缓存操作完成。如果不想被堵塞,并且需要在缓存操作完成以后执行后续操作,可以这样做:

CompletionStage<ResultData> future = r.future();future.thenRun(() -> { if(r.isSuccess()){ System.out.println(r.getValue()); }});

以上代码将会在缓存操作异步完成后,在完成异步操作的线程中调用thenRun中指定的回调。CompletionStage是Java8新增的功能,如果对此不太熟悉可以先查阅相关的文档。需要注意的是,既然已经选择了异步的开发方式,在回调中不能调用堵塞方法,以免堵塞其他的线程(回调方法很可能是在event loop线程中执行的)。


部分小写的api不需要任何修改,就可以直接享受到异步开发的好处。比如put和removeAll方法,由于它们没有返回值,所以此时就直接优化成异步调用,能够减少RT;而get方法由于需要取返回值,所以仍然会堵塞。


自动load(read through)


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。


需要注意


GET、GET_ALL这类大写API只纯粹访问缓存,不会调用loader。

如果使用多级缓存,loader应该安装在MultiLevelCache上,不要安装在底下的缓存上。

注解的属性只能是常量,所以没有办法在CreateCache注解中指定loader,不过我们可以这样:

@CreateCacheprivate Cache<Long,UserDO> userCache;
@PostConstructpublic void init(){ userCache.config().setLoader(this::loadUserFromDatabase);}

@CreateCache总是初始化一个经过LoadingCache包装的Cache,直接在config中设置loader,可以实时生效。


自动刷新缓存


从JetCache2.2版本开始,RefreshCache基于decorator模式提供了自动刷新的缓存的能力,目的是为了防止缓存失效时造成的雪崩效应打爆数据库。同时设置了loader和refreshPolicy的时候,CacheBuilder的buildCache方法返回的Cache实例经过了RefreshCache的包装。

RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES) .stopRefreshAfterLastAccess(30, TimeUnit.MINUTES);Cache<String, Long> orderSumCache = LinkedHashMapCacheBuilder .createLinkedHashMapCacheBuilder() .loader(key -> loadOrderSumFromDatabase(key)) .refreshPolicy(policy) .buildCache();

对一些key比较少,实时性要求不高,加载开销非常大的缓存场景,适合使用自动刷新。上面的代码指定每分钟刷新一次,30分钟如果没有访问就停止刷新。如果缓存是redis或者多级缓存最后一级是redis,缓存加载行为是全局唯一的,也就是说不管有多少台服务器,同时只有一个服务器在刷新,这是通过tryLock实现的,目的是为了降低后端的加载负担。


与LoadingCache一样,使用@CreateCache时,我们需要这样来添加自动刷新功能

@CreateCacheprivate Cache<String, Long> orderSumCache;
@PostConstructpublic void init(){ RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES) .stopRefreshAfterLastAccess(30, TimeUnit.MINUTES); orderSumCache.config().setLoader(this::loadOrderSumFromDatabase); orderSumCache.config().setRefreshPolicy(policy);}


Redis支持(两种redis客户端二选一即可)


使用jedis客户端连接redis


redis有多种java版本的客户端,JetCache2.2以前使用jedis客户端访问redis。从JetCache2.2版本开始,增加了对luttece客户端的支持,jetcache的luttece支持提供了异步操作和redis集群支持。


如果选用jedis访问redis,对应的maven artifact是jetcache-redis和jetcache-starter-redis(spring boot)。


spring boot环境下的jedis支持


application.yml文件如下(这里省去了local相关的配置):


jetcache:  areaInCacheName: false remote: default: type: redis keyConvertor: fastjson poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: ${redis.host} port: ${redis.port} #sentinels: 127.0.0.1:26379 , 127.0.0.1:26380, 127.0.0.1:26381 #masterName: mymaster


如果需要直接操作JedisPool,可以通过以下方式获取


@Bean(name = "defaultPool")@DependsOn(RedisAutoConfiguration.AUTO_INIT_BEAN_NAME)//jetcache2.2+//@DependsOn("redisAutoInit")//jetcache2.1public JedisPoolFactory defaultPool() { return new JedisPoolFactory("remote.default", JedisPool.class);}


然后可以直接使用

@Autowiredprivate Pool<Jedis> defaultPool;

也可以用Cache接口上的<T> Tunwrap(Class<T> clazz)方法来获取JedisPool,参见RedisCache.unwrap源代码。


不使用spring boot


@Configuration@EnableMethodCache(basePackages = "com.company.mypackage")@EnableCreateCacheAnnotationpublic class JetCacheConfig {
@Bean public Pool<Jedis> pool(){ // build jedis pool ... }
@Bean public SpringConfigProvider springConfigProvider() { return new SpringConfigProvider(); }
@Bean public GlobalCacheConfig config(SpringConfigProvider configProvider, Pool<Jedis> pool){ Map localBuilders = new HashMap(); EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder .createLinkedHashMapCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE); localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);
Map remoteBuilders = new HashMap(); RedisCacheBuilder remoteCacheBuilder = RedisCacheBuilder.createRedisCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .jedisPool(pool); remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);
GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig(); globalCacheConfig.setConfigProvider(configProvider); globalCacheConfig.setLocalCacheBuilders(localBuilders); globalCacheConfig.setRemoteCacheBuilders(remoteBuilders); globalCacheConfig.setStatIntervalMinutes(15); globalCacheConfig.setAreaInCacheName(false);
return globalCacheConfig; }}


Builder API


如果不通过@CreateCache和@Cached注解,可以通过下面的方式创建RedisCache。通过注解创建的缓存会自动设置keyPrefix,这里是手工创建缓存,对于远程缓存需要设置keyPrefix属性,以免不同Cache实例的key发生冲突。

GenericObjectPoolConfig pc = new GenericObjectPoolConfig();pc.setMinIdle(2);pc.setMaxIdle(10);pc.setMaxTotal(10);JedisPool pool = new JedisPool(pc, "localhost", 6379);
Cache<Long,OrderDO> orderCache = RedisCacheBuilder.createRedisCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .jedisPool(pool) .keyPrefix("orderCache") .expireAfterWrite(200, TimeUnit.SECONDS) .buildCache();


常见问题


如果遇到这个错误

java.lang.NoSuchMethodError: redis.clients.jedis.JedisPool.<init>(Lorg/apache/commons/pool2/impl/GenericObjectPoolConfig;Ljava/lang/String;IILjava/lang/String;ILjava/lang/String;Z)V


请确保jedis的版本在2.9.0以上,spring boot 1.5以下版本的spring-boot-dependencies会引入较低版本的jedis,可以在自己的pom中强制直接依赖jedis版本2.9.0:


<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version></dependency>


使用lettuce客户端连接redis


redis有多种java版本的客户端,JetCache2.2以前使用jedis客户端访问redis。从JetCache2.2版本开始,增加了对lettuce客户端的支持,JetCache的lettuce支持提供了异步操作和redis集群支持。


使用lettuce访问redis,对应的maven artifact是jetcache-redis-lettuce和jetcache-starter-redis-lettuce。lettuce使用Netty建立单个连接连redis,所以不需要配置连接池。


注意:新发布的lettuce5更换了groupId和包名,2.3版本的JetCache同时支持lettuce4和5,jetcache-redis-lettuce,jetcache-starter-redis-lettuce提供lettuce5支持,jetcache-redis-lettuce4和jetcache-starter-redis-lettuce4提供lettuce4支持。


注意:JetCache2.2版本中,lettuce单词存在错误的拼写,错写为“luttece”,该错误存在于包名、类名和配置中,2.3已经改正。


spring boot环境下的lettuce支持


application.yml文件如下(这里省去了local相关的配置):

jetcache:  areaInCacheName: false remote: default: type: redis.lettuce keyConvertor: fastjson uri: redis://127.0.0.1:6379/


如果使用sentinel做自动主备切换,uri可以配置为redis-sentinel://127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381/?sentinelMasterId=mymaster


如果是集群:


jetcache:  areaInCacheName: false remote: default: type: redis.lettuce keyConvertor: fastjson uri: - redis://127.0.0.1:7000 - redis://127.0.0.1:7001 - redis://127.0.0.1:7002

如果需要直接使用lettuce的RedisClient:


@Bean(name = "defaultClient")@DependsOn(RedisLettuceAutoConfiguration.AUTO_INIT_BEAN_NAME)public LettuceFactory defaultClient() { return new LettuceFactory("remote.default", RedisClient.class);}


然后可以直接使用


@Autowired

private RedisClient defaultClient;

也可以用Cache接口上的<T> Tunwrap(Class<T> clazz)方法来获取RedisClient和RedisCommands等。参考RedisLettuceCache.unwrap源代码。


不使用spring boot


@Configuration@EnableMethodCache(basePackages = "com.company.mypackage")@EnableCreateCacheAnnotationpublic class JetCacheConfig {
@Bean public RedisClient redisClient(){ RedisClient client = RedisClient.create("redis://127.0.0.1"); return client; }
@Bean public SpringConfigProvider springConfigProvider() { return new SpringConfigProvider(); }
@Bean public GlobalCacheConfig config(SpringConfigProvider configProvider,RedisClient redisClient){ Map localBuilders = new HashMap(); EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder .createLinkedHashMapCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE); localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);
Map remoteBuilders = new HashMap(); RedisLettuceCacheBuilder remoteCacheBuilder = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .redisClient(redisClient); remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);
GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig(); globalCacheConfig.setConfigProvider(configProvider); globalCacheConfig.setLocalCacheBuilders(localBuilders); globalCacheConfig.setRemoteCacheBuilders(remoteBuilders); globalCacheConfig.setStatIntervalMinutes(15); globalCacheConfig.setAreaInCacheName(false);
return globalCacheConfig; }}

builder API


如果不通过@CreateCache和@Cached注解,可以通过下面的方式创建Cache。通过注解创建的缓存会自动设置keyPrefix,这里是手工创建缓存,对于远程缓存需要设置keyPrefix属性,以免不同Cache实例的key发生冲突。


RedisClient client = RedisClient.create("redis://127.0.0.1");
Cache<Long,OrderDO> orderCache = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .redisClient(client) .keyPrefix("orderCache") .expireAfterWrite(200, TimeUnit.SECONDS)                .buildCache();



内存缓存LinkedHashMapCache和CaffeineCache


本地缓存当前有两个实现。如果自己用jetcache-core的Cache API,可以不指定keyConvertor,此时本地缓存使用equals方法来比较key。如果使用jetcache-anno中的@Cached、@CreateCache等注解,必须指定keyConvertor。


LinkedHashMapCache


LinkedHashMapCache是JetCache中实现的一个最简单的Cache,使用LinkedHashMap做LRU方式淘汰。

Cache<Long, OrderDO> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder() .limit(100) .expireAfterWrite(200, TimeUnit.SECONDS) .buildCache();

CaffeineCache


caffeine cache的介绍看这里,它是guava cache的后续作品。

Cache<Long, OrderDO> cache = CaffeineCacheBuilder.createCaffeineCacheBuilder() .limit(100) .expireAfterWrite(200, TimeUnit.SECONDS) .buildCache();

统计


当yml中的jetcache.statIntervalMinutes大于0时,通过@CreateCache和@Cached配置出来的Cache自带监控。JetCache会按指定的时间定期通过logger输出统计信息。默认输出信息类似如下:


只有使用computeIfAbsent方法或者@Cached注解才会统计loadTime。用get方法取缓存,没有命中的话自己去数据库load,显然是无法统计到的。


如果需要定制输出,可以这样做:

 @Bean public SpringConfigProvider springConfigProvider() { return new SpringConfigProvider(){ public Consumer<StatInfo> statCallback() { // return new StatInfoLogger(false); ... // 实现自己的logger } }; }

JetCache按statIntervalMinutes指定的周期,定期调用statCallback返回着这个Consumer,传入的StatInfo是已经统计好的数据。这个方法默认的实现是:


returnnew StatInfoLogger(false);

StatInfoLogger的构造参数设置为true会有更详细的统计信息,包括put等操作的统计。StatInfoLogger输出的是给人读的信息,你也可以自定义logger将日志输出成特定格式,然后通过日志系统统一收集和统计。


如果想要让jetcache的日志输出到独立的文件中,在使用logback的情况下可以这样配置:

<appender name="JETCACHE_LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>jetcache.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>jetcache.log.%d{yyyy-MM-dd}</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy>
<encoder> <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder></appender>
<logger name="com.alicp.jetcache" level="INFO" additivity="false"> <appender-ref ref="JETCACHE_LOGFILE" /></logger>

Builder:未使用Spring4(或者spring)的时候,通过Builder手工构造Cache


JetCache2版本的@Cached和@CreateCache等注解都是基于Spring4.X版本实现的,在没有Spring支持的情况下,注解将不能使用。但是可以直接使用JetCache的API来创建、管理、监控Cache,多级缓存也可以使用。


创建缓存


创建缓存的操作类似guava/caffeinecache,例如下面的代码创建基于内存的LinkedHashMapCache:

Cache<String, Integer> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder() .limit(100) .expireAfterWrite(200, TimeUnit.SECONDS) .buildCache();

创建RedisCache:

GenericObjectPoolConfig pc = new GenericObjectPoolConfig(); pc.setMinIdle(2); pc.setMaxIdle(10); pc.setMaxTotal(10); JedisPool pool = new JedisPool(pc, "localhost", 6379);Cache<Long, OrderDO> orderCache = RedisCacheBuilder.createRedisCacheBuilder() .keyConvertor(FastjsonKeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .jedisPool(pool) .keyPrefix("orderCache") .expireAfterWrite(200, TimeUnit.SECONDS) .buildCache();

多级缓存


在2.2以后通过下面的方式创建多级缓存:

Cache multiLevelCache = MultiLevelCacheBuilder.createMultiLevelCacheBuilder() .addCache(memoryCache, redisCache) .expireAfterWrite(100, TimeUnit.SECONDS) .buildCache();

实际上,使用MultiLevelCache可以创建多级缓存,它的构造函数接收的是一个Cache数组(可变参数)。


如果是2.2之前的版本:

Cache memoryCache = ...Cache redisCache = ...Cache multiLevelCache = new MultiLevelCache(memoryCache, redisCache);

监控统计


如果要对Cache进行监控统计:

Cache orderCache = ...CacheMonitor orderCacheMonitor = new DefaultCacheMonitor("OrderCache");orderCache.config().getMonitors().add(orderCacheMonitor); // jetcache 2.2+, or call builder.addMonitor() before buildCache()// Cache<Long, Order> monitedOrderCache = new MonitoredCache(orderCache, orderCacheMonitor); //before jetcache 2.2int resetTime = 1;boolean verboseLog = false;DefaultCacheMonitorManager cacheMonitorManager = new DefaultCacheMonitorManager(resetTime, TimeUnit.SECONDS, verboseLog);cacheMonitorManager.add(orderCacheMonitor);cacheMonitorManager.start();


首先创建一个CacheMonitor,每个DefaultCacheMonitor只能用于一个Cache。当DefaultCacheMonitorManager启动以后,会使用slf4j按指定的时间定期输出统计信息到日志中(简版输出格式参见统计),DefaultCacheMonitor构造时指定的名字会作为输出时cache的名字。


在组装多级缓存的过程中,可以给每个缓存安装一个Monitor,这样可以监控每一级的命中情况。


也可以自己对统计信息进行处理,调用下面的构造方法创建DefaultCacheMonitorManager:


public DefaultCacheMonitorManager(int resetTime, TimeUnit resetTimeUnit, Consumer<StatInfo> stat

 

开发者文档


clone下来以后,可以按maven项目导入idea或eclipse。


跑通单元测试,需要在本地运行redis,先安装docker,然后用下面的命令运行redis-sentinel


docker run --rm -it -p 6379-6381:6379-6381 -p 26379-26381:26379-26381 areyouok/redis-sentinel

接下来mvn cleantest可以跑通所有测试,如果在IDE里面,可能还需要给javac设置-parameters参数。需要注意的是机器繁忙时单元测试有可能会失败,因为很多单元测试使用了sleep,为了不让单元测试运行的时间过长,sleep的时间都设置的比较短,这样机器卡顿时可能导致检查失败,不过对于正常机器这并不经常发生。


使用snapshot版本,在自己的pom里面加上:

<repositories> <repository> <id>sonatype-nexus-snapshots</id> <name>Sonatype Nexus Snapshots</name> <url>https://oss.sonatype.org/content/repositories/snapshots</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>

升级和兼容性指南

2.5.0


从2.3.3及更低版本升级到2.5.0会发生ClassCastException(如果你使用了MultiLevelCache或者cacheType.CacheType.BOTH)。解决办法是先升级到2.4.4并且发布到生产环境,然后再升级到2.5.0。

子类的注解会覆盖接口和父类



以上是关于jetcache缓存官网教程的主要内容,如果未能解决你的问题,请参考以下文章

阿里开源 JetCache 缓存框架介绍使用

203.阿里jetcache

Redis缓存:java语言注释符号,附超全教程文档

205. jetcache:你需要知道的小技巧

Android获取各个应用程序的缓存文件代码小片段(使用AIDL)

VIM 代码片段插件 ultisnips 使用教程