jetcache参考文档

jetcache简介

https://github.com/alibaba/jetcache/blob/master/docs/CN/GettingStarted.md

简介

JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。
JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了Cache接口用于手工缓存操作。
当前有四个实现,RedisCacheTairCache(此部分未在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/Caffeine cache类似。

例子

参考工程samples目录

依赖哪个jar?

  • jetcache-anno-api:定义jetcache的注解和常量,不传递依赖。如果你想把Cached注解加到接口上,又不希望你的接口jar传递太多依赖,可以让接口jar依赖jetcache-anno-api。
  • jetcache-core:核心api,完全通过编程来配置操作Cache,不依赖Spring。两个内存中的缓存实现LinkedHashMapCacheCaffeineCache也由它提供。
  • 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。

快速入门

创建缓存实例

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

@Autowired
private CacheManager cacheManager;

private Cache<Long, UserDO> userCache;

@PostConstruct
public void init() {
    QuickConfig qc = QuickConfig.newBuilder("userCache") // name用于统计信息展示名字
        .expire(Duration.ofSeconds(100))
        //.cacheType(CacheType.BOTH) // 创建一个两级缓存
        //.localLimit(100) // 本地缓存元素个数限制,只对CacheType.LOCAL和CacheType.BOTH有效
        //.syncLocal(true) // 两级缓存的情况下,缓存更新时发消息让其它JVM实例中的缓存失效,需要配置broadcastChannel才生效。
        .build();
    userCache = cacheManager.getOrCreateCache(qc);
}

用起来就像map一样:

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

创建方法缓存

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

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

基本配置(使用Spring Boot)

如果使用Spring Boot,可以按如下的方式配置(这里使用了jedis客户端连接redis,也可以使用lettuce客户端)。

POM

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>${jetcache.latest.version}</version>
</dependency>

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

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

然后创建一个App类放在业务包的根下,EnableMethodCache,EnableCreateCacheAnnotation这两个注解分别激活Cached和CreateCache注解,其他和标准的Spring Boot程序是一样的。这个类可以直接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")
@EnableCreateCacheAnnotation
public class MySpringBootApp {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApp.class);
    }
}

未使用SpringBoot的配置方式

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

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-anno</artifactId>
    <version>${jetcache.latest.version}</version>
</dependency>
<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-redis</artifactId>
    <version>${jetcache.latest.version}</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.Fastjson2KeyConvertor;
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")
@EnableCreateCacheAnnotation // deprecated in jetcache 2.7, 如果不用@CreateCache注解可以删除
@Import(JetCacheBaseBeans.class) //need since jetcache 2.7+
public 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 for jetcache <=2.6 
    //public SpringConfigProvider springConfigProvider() {
    //    return new SpringConfigProvider();
    //}

    @Bean
    public GlobalCacheConfig config(Pool<Jedis> pool){
    // public GlobalCacheConfig config(SpringConfigProvider configProvider, Pool<Jedis> pool){ // for jetcache <=2.5 
        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(Fastjson2KeyConvertor.INSTANCE)
                .valueEncoder(JavaValueEncoder.INSTANCE)
                .valueDecoder(JavaValueDecoder.INSTANCE)
                .broadcastChannel("projectA")
                .jedisPool(pool);
        remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);

        GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
        // globalCacheConfig.setConfigProvider(configProvider); // for jetcache <= 2.5
        globalCacheConfig.setLocalCacheBuilders(localBuilders);
        globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
        globalCacheConfig.setStatIntervalMinutes(15);
        //globalCacheConfig.setAreaInCacheName(false); for jetcache <=2.6 

        return globalCacheConfig;
    }
}

基本Cache API

简介

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

  1. 希望维持API的简单易用。
  2. 对于特定的远程缓存系统来说,javax.cache.Cache中定义的有些操作无法高效率的实现,比如一些原子操作方法和类似removeAll()这样的方法。
  3. 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 = cache.tryLockAndRun("MyKey",100, TimeUnit.SECONDS, () -> {
    // do something
  });

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

大写API

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

通过CacheManager注解创建Cache实例

CacheManager

使用CacheManager可以创建Cache实例,area和name相同的情况下,它和Cached注解使用同一个Cache实例。

注意:在jetcache 2.7 版本CreateCache注解已经废弃,请改用CacheManager.getOrCreateCache(QuickConfig)

例子:

@Autowired
private CacheManager cacheManager;
private Cache<String, UserDO> userCache;

@PostConstruct
public void init() {
    QuickConfig qc = QuickConfig.newBuilder("userCache")
        .expire(Duration.ofSeconds(100))
        .cacheType(CacheType.BOTH) // two level cache
        .syncLocal(true) // invalidate local cache in all jvm process after update
        .build();
    userCache = cacheManager.getOrCreateCache(qc);
}

CreateCache注解

使用@CreateCache注解创建一个Cache实例,例如

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

@CreateCache属性表

属性默认值说明
area“default”如果需要连接多个缓存系统,可在配置多个cache area,这个属性指定要使用的那个area的name
name未定义指定缓存的名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。如果两个@CreateCachenamearea相同,它们会指向同一个Cache实例
expire未定义该Cache实例的默认超时时间定义,注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取无穷大
timeUnitTimeUnit.SECONDS指定expire的单位
cacheTypeCacheType.REMOTE缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存
localLimit未定义如果cacheType为CacheType.LOCAL或CacheType.BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取100
serialPolicy未定义如果cacheType为CacheType.REMOTE或CacheType.BOTH,指定远程缓存的序列化方式。JetCache内置的可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取SerialPolicy.JAVA
keyConvertor未定义指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,JetCache内置的可选值为KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON通过fastjson将复杂对象KEY转换成String。如果注解上没有定义,则使用全局配置。

默认值

对于以上未定义默认值的参数,如果没有指定,将使用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的属性非常类似,但是多几个:

属性默认值说明
area“default”如果在配置中配置了多个缓存area,在这里指定使用哪个area
name未定义指定缓存的唯一名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。
key未定义使用SpEL指定key,如果没有指定会根据所有参数自动生成。
expire未定义超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大
timeUnitTimeUnit.SECONDS指定expire的单位
cacheTypeCacheType.REMOTE缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存
localLimit未定义如果cacheType为LOCAL或BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为100
localExpire未定义仅当cacheType为BOTH时适用,为内存中的Cache指定一个不一样的超时时间,通常应该小于expire
serialPolicy未定义指定远程缓存的序列化方式。可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为SerialPolicy.JAVA
keyConvertor未定义指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,当前支持KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON可以将复杂对象KEY转换成String。如果注解上没有定义,会使用全局配置。
enabledtrue是否激活缓存。例如某个dao方法上加缓存注解,由于某些调用场景下不能有缓存,所以可以设置enabled为false,正常调用不会使用缓存,在需要的地方可使用CacheContext.enableCache在回调中激活缓存,缓存激活的标记在ThreadLocal上,该标记被设置后,所有enable=false的缓存都被激活
cacheNullValuefalse当方法返回值为null的时候是否要缓存
condition未定义使用SpEL指定条件,如果表达式返回true的时候才去缓存中查询
postCondition未定义使用SpEL指定条件,如果表达式返回true的时候才更新缓存,该评估在方法执行后进行,因此可以访问到#result

@CacheInvalidate注解说明:

属性默认值说明
area“default”如果在配置中配置了多个缓存area,在这里指定使用哪个area,指向对应的@Cached定义。
name未定义指定缓存的唯一名称,指向对应的@Cached定义。
key未定义使用SpEL指定key
condition未定义使用SpEL指定条件,如果表达式返回true才执行删除,可访问方法结果#result

@CacheUpdate注解说明:

属性默认值说明
area“default”如果在配置中配置了多个缓存area,在这里指定使用哪个area,指向对应的@Cached定义。
name未定义指定缓存的唯一名称,指向对应的@Cached定义。
key未定义使用SpEL指定key
value未定义使用SpEL指定value
condition未定义使用SpEL指定条件,如果表达式返回true才执行更新,可访问方法结果#result

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

@CacheRefresh注解说明:

属性默认值说明
refresh未定义刷新间隔
timeUnitTimeUnit.SECONDS时间单位
stopRefreshAfterLastAccess未定义指定该key多长时间没有访问就停止刷新,如果不指定会一直刷新
refreshLockTimeout60秒类型为BOTH/REMOTE的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间

@CachePenetrationProtect注解:

当缓存访问未命中的情况下,对并发进行的加载行为进行保护。
当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果。

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

配置详解

配置说明

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

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  hidePackages: com.alibaba
  local:
    default:
      type: caffeine
      limit: 100
      keyConvertor: fastjson2 #其他可选:fastjson/jackson
      expireAfterWriteInMillis: 100000
    otherArea:
      type: linkedhashmap
      limit: 100
      keyConvertor: none
      expireAfterWriteInMillis: 100000
  remote:
    default:
      type: redis
      keyConvertor: fastjson2 #其他可选:fastjson/jackson
      broadcastChannel: projectA
      valueEncoder: java #其他可选:kryo/kryo5
      valueDecoder: java #其他可选:kryo/kryo5
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${redis.host}
      port: ${redis.port}
    otherArea:
      type: redis
      keyConvertor: fastjson2 #其他可选:fastjson/jackson
      broadcastChannel: projectA
      valueEncoder: java #其他可选:kryo/kryo5
      valueDecoder: java #其他可选:kryo/kryo5
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${redis.host}
      port: ${redis.port}

配置通用说明如下

属性默认值说明
jetcache.statIntervalMinutes0统计间隔,0表示不统计
jetcache.areaInCacheNametrue(2.6-) false(2.7+)jetcache-anno把cacheName作为远程缓存key前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。2.4.4以后可以配置,为了保持远程key兼容默认值为true,但是新项目的话false更合理些,2.7默认值已改为false。
jetcache.hiddenPackages@Cached和@CreateCache自动生成name的时候,为了不让name太长,hiddenPackages指定的包名前缀被截掉
jetcache.[local/remote].${area}.type缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型
jetcache.[local/remote].${area}.keyConvertorfastjson2key转换器的全局配置,2.6.5+已经支持的keyConvertor:fastjson2/jackson
2.6.5-只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor
jetcache.[local/remote].${area}.valueEncoderjava序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java/kryo/kryo5;2.6-可选java/kryo
jetcache.[local/remote].${area}.valueDecoderjava序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java/kryo/kryo5;2.6-可选java/kryo
jetcache.[local/remote].${area}.limit100每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部,比如这里指定100,然后用@CreateCache创建了两个缓存实例(并且注解上没有设置localLimit属性),那么每个缓存实例的限制都是100
jetcache.[local/remote].${area}.expireAfterWriteInMillis无穷大以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis)
jetcache.remote.${area}.broadcastChanneljetcahe2.7的两级缓存支持更新以后失效其他JVM中的local cache,但多个服务共用redis同一个channel可能会造成广播风暴,需要在这里指定channel,你可以决定多个不同的服务是否共用同一个channel。如果没有指定则不开启。
jetcache.local.${area}.expireAfterAccessInMillis0需要jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能。

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

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

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

高级CacheApi

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。

需要注意

  1. GET、GET_ALL这类大写API只纯粹访问缓存,不会调用loader。
  2. 如果使用多级缓存,loader应该安装在MultiLevelCache上,不要安装在底下的缓存上。

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

@CreateCache
private Cache<Long,UserDO> userCache;

@PostConstruct
public 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时,我们需要这样来添加自动刷新功能

@CreateCache
private Cache<String, Long> 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);
}

内存缓存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();

升级和兼容性指南

spring兼容性

jetcache在以下spring/spring-boot版本下通过了测试,如果你只用部分功能或者能自己调整依赖的的话,适用范围还可以更大一些。

jetcache版本spring版本spring boot版本说明
2.54.0.8.RELEASE~5.1.1.RELEASE1.1.9.RELEASE~2.0.5.RELEASE
2.65.0.4.RELEASE~5.2.4.RELEASE2.0.0.RELEASE~2.2.5.RELEASEjetcache-redis依赖jedis3.1.0,spring-data(jedis,boot版本<=2.1.X)依赖jedis2.9.3,不能同时用
2.75.2.4.RELEASE~5.3.232.2.5.RELEASE~2.7.5jetcahe-redis依赖jedis4,spring-data(jedis)依赖jedis3,不能同时用

兼容性改动说明

2.7.2

  • 更新了redisson的编码方式,和2.7.1不兼容

2.7.0

  • jetcahe-redis依赖jedis4,如果你使用spring data并且使用jedis的话(spring-data默认用lettuce),它需要3,所以你需要自己把版本改回去,并且不能再使用jetcahe-redis了(改用jetcache-redis-springdata)
  • encoder/decoder现在同时支持kryo4和kryo5,在yml中"kryo"仍然代表kryo4,"kryo5"代表kryo5。kryo4和kryo5的序列化内容完全不兼容。
    • kryo4对应的依赖是com.esotericsoftware:kryo,kryo5对应的依赖是com.esotericsoftware.kryo:kryo5
    • kryo4和kryo5可以并存,maven id和包名都不一样。
    • 要注意com.esotericsoftware:kryo的版本号也可以改为5.x.x
  • lettuce连接redis cluster需要在yml里面指定mode=cluster
  • 默认的key convertor改成了"fastjson2",fastjson2和fastjson可以并存,fastjson(非fastjson2)/kryo/kryo5/mvel在maven中都改为optional,如果使用了需要用户手工声明依赖
  • 如果没有使用spring boot,应该增加@Import(JetCacheBaseBeans.class),同时删除原来定义的configProvider bean,具体例子可以看最新文档
  • GlobalCacheConfig.areaInCacheName默认值改为false,以前所有的代码案例都显式的写了areaInCacheName=false,不会有人没加这一行吧

2.6.0

  • GET/GET_ALL方法不再触发自动刷新(大写的方法只简单访问缓存, 小写的方法才能触发这些附加功能)
  • 不再支持lettuce4
  • 不再支持jedis2.9

2.5.0

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

FAQ

为什么调用同一个类的另一个方法,@Cached注解没有生效?

JetCache的注解功能是使用Spring AOP来实现的,而Spring基于Proxy来实现AOP。
从Spring Context中获得的bean,以及通过@Autowired注解都是代理增强过的,所以可以织入缓存相关的逻辑,同一个类中通过this调用另一个方法,不经过代理,所以JetCache的缓存逻辑以及Spring的其它AOP切面都不会生效。

JetCache暂未支持AspectJ。

一个替代方法是,在bean中通过@Autowired注入它自己,然后在用注入的实例代替this来调用。

@Cached的key、condition等表达式中使用参数名以后缓存没有生效

javac编译器需要指定-parameters参数以后才会把参数名信息写入到字节码中,然后才能被反射机制读取,默认情况下这个参数是没有指定的。

pom中的指定方式:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <compilerArgument>-parameters</compilerArgument>
        </configuration>
    </plugin>
</plugins>

如果你在IDE中运行程序,光在pom中指定还不够。

在IntelliJ IDEA中设置:
在这里插入图片描述

在Eclipse中设置:
在这里插入图片描述

如何定制自己的序列化器

在Cached和CreateCache上的serialPolicy可以指向一个Spring Bean。
做一个类实现SerialPolicy接口,在Spring Context中创建该类的实例(假设名字为myBean),在Cached和CreateCache注解上设置serialPolicy="bean:myBean"即可。

更进一步,如果想把自定义的序列化器设置为默认的,实现一个EncoderParser(继承DefaultSpringEncoderParser修改即可),然后做成一个bean放到 spring context中。

我想要JSON序列化器

jetcache老版本中是有两个序列化器的:java、kryo。jetcache2.7增加了kryo5、fastjson2、jackson。
现在默认的序列化器是性能最差,但是兼容性最好,大家也最熟悉的java序列化器。

json不是一个专门的java序列化库,更多的时候是用来和前端进行数据交互,前端传入的
不受信任的json字符串如果随便就反序列化,会造成极其严重的安全问题,为了避免这些问题各个json类库都变得极为保守,
只要反射无法识别类型(比如字段类型是Object实际上是XxxBean),就会反序列化为JSONObject,这样反序列化的兼容性就变得很差。

因此fastjson2和jackson这两个json序列化器默认是没有注册的,如果要使用请自行做好相关的配置和改动。
如果你不知道该怎么注册,建议不要用json序列化器,因为json反序列化出了问题你也搞不定的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/23795.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

shell变量类型含义 长方形面积计算 截取相应字段方法

1.总结变量的类型及含义&#xff1f; 2.实现课堂案例计算长方形面积&#xff1f;&#xff08;6种方式&#xff09; 3.定义变量urlhttps://blog.csdn.net/weixin_45029822/article/details/103568815&#xff08;通过多种方法实现&#xff09; 1&#xff09;截取网站访问的协议 …

文心一言 VS 讯飞星火 VS chatgpt (22)-- 算法导论4.2 2题

二、为Strassen算法编写伪代码。 文心一言&#xff1a; Strassen算法是一种用于矩阵乘法的算法&#xff0c;它的核心思想是通过不断将原始矩阵分割成较小的子矩阵来实现高效的乘法运算。以下是Strassen算法的伪代码&#xff1a; function Strassen(A, B): if A[0][0] 0 or…

Springboot整合WebSocket

目录 1.WebSocket 简介 2.WebSocket 实战 1.WebSocket 简介 WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务器只需要…

基于html+css的图展示98

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

C++入门

文章目录 &#x1f600;1. 命名空间&#x1f604;1.1 命名空间定义&#x1f604;1.2 命名空间使用 &#x1f60a;2. 输入和输出&#x1f609;3. 缺省参数&#x1fae0;3.1 缺省参数概念&#x1fae0;3.2 缺省参数分类 &#x1f62c;4. 函数重载&#x1f644;4.1 函数重载概念&a…

TypeScript9-声明文件

本篇文章来讲 TypeScript 的声明文件。 当我们在使用第三方库的时候&#xff0c;很多第三方库不是用 TS 写的&#xff0c;它们是通过原生的 JavaScript 或者是浏览器 / 或者是 node 提供的 run time 对象。如果我们直接使用 TS 肯定就会报编译不通过。 1. 声明语句 假设一个…

浅析变电站无人值守管理的模式与特点

安科瑞虞佳豪 近年来&#xff0c;随着电网的发展&#xff0c;变电站实行无人值班管理模式已成为电网的发展方向。自1998年始&#xff0c;辉县市就开始了变电站综合自动化改造&#xff0c;截止目前全局所属24座变电站&#xff08;其中35kV19座、110kV5座&#xff09;已全部实现…

API自动化测试【postman生成报告】

PostMan生成测试报告有两种&#xff1a; 1、控制台的模式 2、HTML的测试报告 使用到一个工具newman Node.js是前端的一个组件&#xff0c;主要可以使用它来开发异步的程序。 一、控制台的模式 1、安装node.js 双击node.js进行安装&#xff0c;安装成功后在控制台输入node …

Java集合基础

4 集合基础 集合提供一种存储空间可变的存储模型,存储的数据容量可以改变ArrayLis<>: 可调整大小的数组实现<>:是一种特殊的数据类型,泛型可储存重复元素怎么使用呢 在出现E的地方我们使用引用数据类型替换即可举例:ArrayList<String>、ArrayList<Stu…

财务共享时代企业数智化应用能帮我们做些什么?

随着企业规模的不断扩大和业务范围的逐步扩展&#xff0c;财务工作的难度和复杂度也在不断提高&#xff0c;传统的手工录入和处理方式呈现出流程长、效率低、易出错等问题。为了提升财务工作的效率和准确性&#xff0c;越来越多的企业开始利用数智化应用打造企业内部的财务数智…

绝对不能错过的7个零基础免费的ChatGPT镜像网站

还在为打不开openai官网烦心&#xff1f;本文帮你实现ChatGPTMidJourney自由(&#xffe3;∇&#xffe3;)/ &#x1f4d2;收集了一些截至目前(2023年5月25日午12:00)可以免费访问&#xff0c;并且零基础也能正常使用的镜像网站&#xff0c;后续将持续维护更新(&#xff61;&a…

平安银行广州分行立足地域文化,增强差异化权益服务软实力

立足地域文化&#xff0c;拓展差异化权益服务 瓦屋纸窗之下&#xff0c;一盏清茶&#xff0c;三五好友&#xff0c;怡然自若。中国人对茶的喜爱由来已久&#xff0c;茶文化已成为中华传统文化中一张亮丽的名片&#xff0c;而广东茶文化则是中国四大茶文化系列之一。平安银行广州…

抖音seo优化源代码搭建+抖音小程序私有化开源部署

抖音seo优化源码&#xff0c;抖音seo矩阵系统搭建&#xff0c;抖音账号矩阵系统开发&#xff0c;企业在做账号矩阵过程中&#xff0c;最头疼的莫过于私域线索转化&#xff0c;作为开发者都知道&#xff0c;目前市面上我们了解的矩阵系统除了挂载POI信息外&#xff0c;无法挂载留…

unity四叉树和视锥体剔除

这个最好还是看代码&#xff0c;项目有注释放在这里&#xff1a; GetbadEarlyup/Quadtree-cone-scene: 这是一个unity四叉树场景视锥体剔除的Demo (github.com)https://github.com/GetbadEarlyup/Quadtree-cone-scene国内地址&#xff1a; Quadtree-cone-scene: unity四叉树和…

VESD静电监控系统:提高静电防护效果与管理效率

随着科学技术不断发展&#xff0c;现代的工业对静电防护的要求越来越高。因为静电的存在可能会对产品质量、工作环境、甚至是人身产生威胁。静电监控系统是一项高效的管理工具&#xff0c;能够有效地控制和监测静电产生的情况&#xff0c;提高静电防护效果和管理效率。 VESD静电…

U盘超级加密3000试用版与正式版的区别有哪些?

U盘超级加密3000是一款专业的U盘加密软件&#xff0c;它可以为U盘、移动硬盘、内存卡等移动存储设备加密。软件拥有正式版和试用版&#xff0c;那么这两个版本有什么区别呢&#xff1f;下面我们就一起来了解一下。 U盘超级加密3000试用版和正式版的区别 打开软件时的区别 试用…

基于SSM的酒店客房管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 酒店管理系统是一款高…

VMware重新安装VMwareTool字体为灰色情况+ubuntu时间设置

文章目录 前言&#xff1a;1. 重新安装VMwareTool字体为灰色2. VMware下ubuntu的时间设置 前言&#xff1a; 之前退出VMware关闭的时候没有等待虚拟机的状态保存&#xff0c;强制关机了。这就导致后面使用的时候&#xff0c;共享目录无法显示情况。对于上面的情况我的博客里面…

ASP-IIS中间件文件解析与写权限

ASP-IIS中间件文件解析与写权限 IIS文件解析 IIS 6 解析漏洞 1、该版本默认会将*.asp;.jpg 此种格式的文件名&#xff0c;当成Asp解析 2、该版本默认会将*.asp/目录下的所有文件当成Asp解析。 如&#xff1a;logo.asp;.jpg xx.asp/logo.jpgIIS 7.x 解析漏洞 在一个文件路…

SSM编程---Day 02

目录 一、核心配置文件 二、junit介绍 三、自定义java注解 四、自定义注解 五、添加log4j的支持 六、sql映射文件的介绍 一、核心配置文件 1、核心配置文件中需要注意顺序 2、根节点 <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN"…