阿里开源的缓存框架JetCache

目录

前言 

基本配置(使用Spring Boot)

POM

创建实例缓存

创建方法缓存

看Demo代码

踩坑记录

踩坑1. 整合Apollo的坑 

踩坑2. @Cached定义在接口上的坑 

踩坑3. redis.clients包冲突

踩坑4. 连接redis 没有权限 io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication requir


前言 

        之前一直在用Spring Cache进行接口数据的缓存,主要是Spring Cache在对具体key缓存失效时间的设置不是很方便,还要自己去扩展,无意中发现了阿里JetCache大部分的需求都能满足,并且有一些很实用的功能,今天给大家介绍下。下面是在github官网介绍:(为了快速访问.本人已于2021-09-23从github复制gitee: jetcache)

        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支持

基本配置(使用Spring Boot)

如果使用Spring Boot,可以按如下的方式配置(这里使用了jedis客户端连接redis,如果需要集群、读写分离、异步等特性支持请使用lettuce客户端)。

POM

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

配置一个spring boot风格的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注解,其他和标准的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);
    }
}

创建实例缓存

通过@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生成代理,来支持缓存功能。注解可以加在接口方法上也可以加在类方法上,但需要保证是个Spring bean。

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

@Cached使用

@Cached(name="getUserById.", key="#id", expire = 8, cacheType=CacheType.BOTH)
@Override
public User getUserById(Long id) {
	User user = new User();
	user.setId(1L);
	user.setName("yinjihuan");
	return user;
}
  • name 缓存名称
  • key 缓存key,追加到name后面构成唯一的缓存key, 使用SpEL指定key,如果没有指定会根据所有参数自动生成。
  • expire 缓存失效时间
  • cacheType 缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存

更多配置的介绍请查看文档:https://github.com/alibaba/jetcache/wiki/Home_CN

使用起来还是很方便的,关于更多的功能大家自行去尝试吧,比如缓存定时刷新,缓存命中率统计,自定义序列化方式等等。

看Demo代码

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

    @Override
    @Cached(name = JetCacheConfig.CACHE_KEY_MEMBER + "UserService.userCache", expire = 3600, cacheType = CacheType.REMOTE)
    public UserInfoSimpleDTO getUserCacheById(Long userId) {
        if (Objects.isNull(userId)) {
            return null;
        }
        LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserInfo::getId, userId);
        wrapper.last(ConfigHelper.LIMIT_1);
        UserInfo userInfo = baseMapper.selectOne(wrapper);
        if (Objects.isNull(userInfo)) {
            return null;
        }
        UserInfoSimpleDTO dto = new UserInfoSimpleDTO();
        BeanUtils.copyProperties(userInfo, dto);
        dto.setUserId(userInfo.getId());
        return dto;
    }

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

 再看个复杂点的例子:



    @Override
    @Cached(name = JetCacheConfig.CACHE_KEY_MEMBER + "UserService.userCache", key = "#userId", expire = 3600, cacheType = CacheType.REMOTE)
    public UserInfoSimpleDTO getUserCacheById(Long userId) {
        if (Objects.isNull(userId)) {
            return null;
        }
        LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserInfo::getId, userId);
        wrapper.last(ConfigHelper.LIMIT_1);
        UserInfo userInfo = baseMapper.selectOne(wrapper);
        if (Objects.isNull(userInfo)) {
            return null;
        }
        UserInfoSimpleDTO dto = new UserInfoSimpleDTO();
        BeanUtils.copyProperties(userInfo, dto);
        dto.setUserId(userInfo.getId());
        return dto;
    }

    @Override
    @CacheUpdate(name = JetCacheConfig.CACHE_KEY_MEMBER + "UserService.userCache", key = "#user.userId", value = "#user")
    public int updateUser(UserInfoSimpleDTO user) {
        if (Objects.isNull(user)) {
            return 0;
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setId(user.getUserId());
        userInfo.setParentName(user.getParentName());
        return baseMapper.updateById(userInfo);
    }

    @Override
    @CacheInvalidate(name = JetCacheConfig.CACHE_KEY_MEMBER + "UserService.userCache", key = "#userId")
    public int deleteUser(Long userId) {
        if (Objects.isNull(userId)) {
            return 0;
        }
        return baseMapper.deleteById(userId);
    }
@ApiOperation(value = "测试jetcache", notes = "测试jetcache", nickname = ConstantProperties.HHJ)
    @RequestMapping(value = "/testJetcache", method = RequestMethod.GET)
    public Result<?> testJetcache(@ApiParam(value = "type", example = "1") @RequestParam(value = "type", required = true) Integer type,
                                  @ApiParam(value = "userId", example = "1") @RequestParam(value = "userId", required = true) Long userId) {
        checkTestEnable();
        log.info("testJetcache---getUserCacheById-----");
        UserInfoSimpleDTO user = userInfoService.getUserCacheById(userId);
        if (user == null) {
            return Result.success(null);
        }

        if (type == 2) {
            log.info("testJetcache---updateUser-----");
            user.setParentName(DateUtil.now());
            userInfoService.updateUser(user);
            user = userInfoService.getUserCacheById(userId);
        }
        if (type > 2) {
            log.info("testJetcache---deleteUser-----");
            userInfoService.deleteUser(userId);
        }
        return Result.success(user);
    }

这个例子我们指定了key,并且展示了缓存的更新和删除(注意缓存一致性)


 自动刷新是JetCache的大杀器:

public interface IUserInfoService extends IService<UserInfo> {
    /**
     * salesVolumeSummary
     *
     * @param timeId
     * @param catagoryId
     * @return
     */
    @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一样:

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

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

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

UserDO user = userCache.get(12345L);
userCache.put(12345L, loadUserFromDataBase(12345L));
userCache.remove(12345L);
userCache.computeIfAbsent(1234567L, (key) -> loadUserFromDataBase(1234567L));

 也可以通过和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;

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

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

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

踩坑记录

踩坑1. 整合Apollo的坑 

在这边提一个容易被坑到的点,如果你们的配置都是在Apollo中进行管理的话,那么在集成的时候会有个问题,需要在项目中加上下面的配置才行,其余的配置可以放Apollo中。

spring.application.name=district-service
app.id=${spring.application.name}
apollo.bootstrap.enabled=true
// 这是你要加的,指定缓存类型,我这边用的是lettuce
jetcache.remote.default.type=redis.lettuce

踩坑2. @Cached定义在接口上的坑 

还有一个呢就是@Cached如果定义在接口上就不能指定key属性,框架中会自动根据参数生成key, 如果非得自己用SPEL表达式指定key的话,项目编译设置target必须为1.8格式,并且指定javac的-parameters参数,否则就要使用key="args[0]"这样按下标访问的形式。我建议还是把@Cached的定义放在实现类上,也方便修改。

踩坑3. redis.clients包冲突

这个问题的原因是spring-boot-starter-data-redis的版本不支持导致的.需要排除jetcache自带的redis.clients版本引用和项目的spring-boot对应的版本就可以了

<dependency>
            <groupId>com.alicp.jetcache</groupId>
            <artifactId>jetcache-starter-redis-lettuce</artifactId>
            <version>2.5.14</version>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>3.1.0</version>
      <scope>compile</scope>
    </dependency>

踩坑4. 连接redis 没有权限 io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication requir

这个问题的原因是Redis集群设置了密码,jetcache访问时指定密码就可以了

# uri格式:redis://密码@ip:端口/redis库名?timeout=5s
jetcache.remote.default.uri=redis://password@127.0.0.1:6379/6?timeout=5s

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值