JetCache-Alibaba缓存框架

前言

官方网址:https://github.com/alibaba/jetcache

官方文档:https://github.com/alibaba/jetcache/tree/master/docs/CN

JetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能

JetCache设定了本地缓存与远程缓存的多级缓存解决方案。

本地缓存(local)支持:

  • LinkedHashMap
  • Caffeine

远程缓存(remote)支持:

  • Redis
  • Tair

一.JetCache的基本概念

1.JetCache是由阿里巴巴 Group 开源的一款基于 Spring 和 Redis 的分布式缓存框架。它的主要特点包括:

  1. 基于注解:通过注解的方式来实现缓存的配置和使用,代码简洁,易于维护。
  2. 多级缓存:支持多级缓存,可以将数据缓存在本地内存、Redis、Tair、Memcached 等多种缓存存储中,提高缓存的命中率和查询效率。
  3. 多种缓存协议:支持 Redis、Tair、Memcached 等多种缓存协议,具有良好的可扩展性和兼容性,可以灵活切换缓存存储方式。
  4. 高性能:JetCache 针对分布式环境下的高并发访问优化了缓存的数据结构和查询算法,提供了较高的性能。
  5. 易用性:JetCache 的 API 简单易用,支持 Spring 注入和自定义配置,提供了丰富的缓存操作功能,非常适合中小型应用开发。

2.JetCache 可以应用于各种场景,主要包括:

  1. 热点数据缓存:JetCache 可以将常用的热点数据缓存在本地或远程缓存中,减少数据库或其他数据源的访问,并提高响应性能和性能稳定性。
  2. 多级缓存融合:JetCache 支持多种缓存存储类型,可以将不同的缓存存储类型融合使用,以达到更高的缓存效率和命中率。
  3. 高并发读写:JetCache 的缓存算法可以优化高并发读写场景,提高程序性能并降低系统压力。
  4. 高速访问查询:JetCache 使用缓存,可以实现高速查询和访问,减少等待时间,并提高用户体验。

二.JetCache 的 API 和使用:

  1. 缓存管理:JetCache 提供了 CacheBuilder 类来创建缓存,以及 Cache 对象来操作缓存,例如 get、put、remove 等。在调用 Cache 对象的 put 方法时,可以使用注解 @Cache 来设置缓存时间和名称等配置。
  2. 注解支持:JetCache 提供了多种注解来实现缓存操作,例如 @Cached,@CacheUpdate, @CacheInvalidate 等。使用注解的方式,可以更加方便地进行缓存操作,例如缓存预热、删除、更新等。
  3. 缓存类型:JetCache 支持多种缓存类型,例如本地 memory、Redis、Tair 和 Memcached 等缓存,可以根据业务需求来选择缓存类型。
  4. Spring 集成支持:JetCache 提供了 Spring 集成支持,可以通过注解来注入和配置缓存,简化使用流程。

三、SpringBoot整合JetCache

1.导入依赖

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

2.JetCache配置

2.1 yaml配置

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,如果仍未指定则是无穷大

2.2高级API模式:通过CacheManager使用缓存,2.7 版本才可使用

@Configuration
public class JetCacheConfig {
    /**
     * 缓存管理器.
     */
    @Resource
    private CacheManager cacheManager;
    
    /**
     * 消息模版ID缓存.
     */
    private Cache<String, String> msgTemplateCache;
    
    /**
     * 初始化缓存.
     */
    @PostConstruct
    public void init() {
      	/* 1、一级缓存:仅使用本地缓存,且永不失效
        msgTemplateCache = cacheManager.getOrCreateCache(QuickConfig.newBuilder("cache-")
                .cacheType(CacheType.LOCAL)
                .build());
        */
        // 2、二级缓存: 使用本地缓存+redis
        Duration cacheDuration = Duration.ofSeconds(2 * 60);
        QuickConfig qc = QuickConfig.newBuilder("cache-")
                .cacheType(CacheType.BOTH)
                .syncLocal(true)
                .localLimit(1000)
                .localExpire(cacheDuration)
                .expire(cacheDuration)
                .build();
        msgTemplateCache = cacheManager.getOrCreateCache(qc);
    }
    
    @Bean
    public Cache<String, String> getMsgTemplateCache() {
        return msgTemplateCache;
    }
}

@Slf4j
@Component
public class ProjectInitDataTask implements ApplicationRunner {
    /**
     * 消息模版缓存.
     */
    @Autowired
    private Cache<String, String> msgTemplateCache;
    
    @Override
    public void run(ApplicationArguments args) {
        log.info("模拟创建缓存jetcache...");
        msgTemplateCache.put("msgTemplateCache1", "msgTemplateCache1");
        msgTemplateCache.put("msgTemplateCache2", "msgTemplateCache2");
        msgTemplateCache.put("msgTemplateCache3", "msgTemplateCache3");
        log.info("模拟创建缓存jetcache完成...");
    }
}

2.3(推荐)AOP模式:通过@Cached,@CacheUpdate,@CacheInvalidate注解

  	@GetMapping("getRemote")
    @Cached(name="userCache:", key = "#id", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.REMOTE)
    public User getRemote(Long id){
        User user = new User();
        user.setId(id);
        user.setName("用户remote"+id);
        user.setAge(23);
        user.setSex(1);
        System.out.println("第一次获取数据,未走缓存:"+id);
        return user;
    }
    
    @GetMapping("getLocal")
    @Cached(name="userCache:", key = "#id", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.LOCAL)
    public User getLocal(Long id){
        // 直接新建用户,模拟从数据库获取数据
        User user = new User();
        user.setId(id);
        user.setName("用户local"+id);
        user.setAge(23);
        user.setSex(1);
        System.out.println("第一次获取数据,未走缓存:"+id);
        return user;
    }
    
    @GetMapping("getBoth")
    @Cached(name="userCache:", key = "#id", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.BOTH)
    public User getBoth(Long id){
        // 直接新建用户,模拟从数据库获取数据
        User user = new User();
        user.setId(id);
        user.setName("用户both"+id);
        user.setAge(23);
        user.setSex(1);
        System.out.println("第一次获取数据,未走缓存:"+id);
        return user;
    }
    
    @PostMapping("updateUser")
    @CacheUpdate(name = "userCache:", key = "#user.id", value = "#user")
    public Boolean updateUser(@RequestBody User user){
        // TODO 更新数据库
        return true;
    }

    @PostMapping("deleteUser")
    @CacheInvalidate(name = "userCache:", key = "#id")
    public Boolean deleteUser(Long id){
        // TODO 从数据库删除
        return true;
    }
属性默认值描述
cacheName“”缓存名称
area“default”缓存区域
key“”缓存的键
condition“”缓存条件,需要通过 Spring EL 表达式的形式传入
unless“”缓存排除条件,需要通过 Spring EL 表达式的形式传入
syncfalse是否使用同步模式
sourceAvailablefalse缓存数据源是否存在
cacheNullValuefalse是否缓存 null 值
cacheValuetrue是否缓存返回值
expire-1缓存过期时间,单位为秒
expireExpression“”缓存过期时间表达式
keyConvertor“”缓存键转换器
serialPolicy“”缓存值序列化策略
areaInKeyfalse缓存中是否包含缓存区域
areaInRegionfalse缓存区域是否独立,即缓存区域是否区分不同实例
postCondition“”缓存后置条件
suppressExceptionfalse是否忽略异常
localLimit-1本地缓存大小限制
cacheType“”缓存类型,例如 caffeine、redis 等
conditionSatisfiedByValuefalse是否根据缓存值来计算缓存条件,而不是方法参数

这和SpringCache很像,不过@Cached注解原生支持了TTL(超时时间),cacheType有LOCAL/REMOTE/BOTH三种选择, 分别代表本地内存/远程Cache Server(例如Redis)/两级缓存,可根据情况选用,合理的使用LOCAL或BOTH类型可以降低Cache Server的压力以及我们提供的服务的响应时间。

深入JetCache

我们直接看代码,最简单的使用场景是这样的:

public interface UserService {
    @Cached(expire = 3600, cacheType = CacheType.REMOTE)
    User getUserById(long userId);
}

再看个复杂点的例子:

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,JetCache会根据参数自动生成,这个例子我们指定了key,并且展示了缓存的更新和删除。

自动刷新是JetCache的大杀器:

public interface SummaryService{
    @Cached(expire = 3600, cacheType = CacheType.REMOTE)
    @CacheRefresh(refresh = 1800, stopRefreshAfterLastAccess = 3600, timeUnit = TimeUnit.SECONDS)
    BigDecimal salesVolumeSummary(int timeId, long catagoryId);
}

cacheType为REMOTE或者BOTH的时候,刷新行为是全局唯一的,也就是说,即使应用服务器是一个集群,也不会出现多个服务器同时去刷新一个key的情况。
一个key的刷新任务,自该key首次被访问后初始化,如果该key长时间不被访问,在stopRefreshAfterLastAccess指定的时间后,相关的刷新任务就会被自动移除,这样就避免了浪费资源去进行没有意义的刷新。

加在方法上的注解毕竟不能提供最灵活的控制,所以JetCache提供了Cache API,使用起来就像Map一样:

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

userCache.computeIfAbsent(1234567L, (key) -> loadUserFromDataBase(1234567L));

实际上Cache API实现了jsr107规范Cache接口的部分方法,以后的大版本可能会完整实现。

Cache实例可以通过注解创建:

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

也可以通过和guava cache/caffeine类似的builder来创建:

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

Cache接口支持异步:

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

可以实现不严格的分布式锁

cache.tryLockAndRun("key", 60, TimeUnit.SECONDS, () -> heavyDatabaseOperation());

使用Cache API也可以做自动刷新哦:

@CreateCache
@CacheRefresh(timeUnit = TimeUnit.MINUTES, refresh = 60)
private Cache<String, Long> orderSumCache;

@PostConstruct
public void init(){
    orderSumCache.config().setLoader(this::loadOrderSumFromDatabase);
}

如果没有使用注解,用builder一样也可以做出自动刷新:

Cache<String, Long> orderSumCache = RedisCacheBuilder.createRedisCacheBuilder()
    ......省略
    .refreshPolicy(RefreshPolicy.newPolicy(60, TimeUnit.SECONDS))
    .loader(this::loadOrderSumFromDatabase)
    .buildCache();

当前支持的缓存系统包括以下4个,而且要支持一种新的缓存也是非常容易的:

  • Caffeine(基于本地内存)
  • LinkedHashMap(基于本地内存,JetCache自己实现的简易LRU缓存)
  • Alibaba Tair(相关实现未在Github开源,在阿里内部Gitlab上可以找到)
  • Redis
### 如何搭建 Spring Cloud Alibaba 微服务框架 #### 准备工作 为了成功构建基于 Spring Cloud Alibaba 的微服务体系,需先安装并配置好 Java 开发环境以及 Maven 或 Gradle 构建工具。确保 JDK 版本不低于 8u45 及以上版本[^1]。 #### 创建父项目结构 通过 IDE (如 IntelliJ IDEA) 新建一个多模块 Maven 工程作为整个项目的根目录,在 `pom.xml` 中引入必要的依赖管理: ```xml <dependencyManagement> <dependencies> <!-- 统一管理 spring cloud 和 spring boot 版本 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ``` #### 添加子模块 针对不同业务功能创建多个独立的服务提供者与消费者应用,每个都应继承自上述定义好的 parent POM 文件,并按需添加特定组件的支持,比如 Nacos 注册中心、Sentinel 流控规则引擎等。 对于 Web 应用场景,默认提供了对 WebServlet、WebFlux 等多种协议栈的支持;而对于远程调用,则集成了 OpenFeign 客户端和服务发现机制,简化了分布式系统的开发流程。 #### 配置文件设置 在各个子模块下的 resources 路径下放置 application.yml 或 properties 形式的配置项,用于指定诸如服务器端口、数据库连接字符串以及其他第三方中间件参数等内容。特别注意要开启 Eureka/Nacos 自动注册特性以便于后续集群部署时能够自动感知其他成员的存在状态变化情况。 #### 启动类编写 最后一步是在每一个应用程序入口处标注 @EnableDiscoveryClient 注解以激活服务治理能力,并实现具体的业务逻辑处理方法即可完成基本版的 Spring Cloud Alibaba 平台建设工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值