spring boot 集成 jetcache【基础篇:@Cached、@CreateCache、@CacheRefresh】

news2024/11/24 21:06:55

手打不易,如果转摘,请注明出处!

注明原文:https://zhangxiaofan.blog.csdn.net/article/details/129832925


目录

前言

版本

 配置通用说明

项目结构

代码

启动类

实体类

基础使用——增删改查(@Cached、@CacheInvalidate、@CacheUpdate)

基础使用——@CreateCache注解和手动方式

基础使用——@CacheRefresh 详解


前言

最近有个项目用到jetcache,正好用到,加上spring-boot在使用jetcache的时候会有一些需要注意的坑,下面通过基础使用来给大家简单介绍。

下面有一些示例代码,都有注释,这些注释可以仔细阅读一下!

版本

本篇的jetcache、jedis等版本如下:

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.7.3</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.1.0</version>
</dependency>

完整的maven的pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-boot-redis-jetcache-base</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>spring-boot-redis-jetcache-base</name>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>aspectjweaver</artifactId>
                    <groupId>org.aspectj</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>

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

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.25</version>
        </dependency>

        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>5.4.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.0.RELEASE</version>
            </plugin>
        </plugins>
    </build>
</project>

配置文件 application.yml 如下: 

server:
  servlet:
    context-path: /
  port: 8081

# 官方表示,不会支持 redisson 客户端, 仅支持 jedis 和 lettuce 链接:https://github.com/alibaba/jetcache/issues/634
jetcache:
  statIntervalMinutes: 10 # 默认值0,统计间隔,0表示不统计
  areaInCacheName: false # 默认值false, 是否将 areaName 作为远程缓存key前缀
  # 本地
  local:
    # 默认分组配置,可以创建多个,对应@Cached和@CreateCache的 area 属性, 默认名就是 'default'
    default:
      type: caffeine # 可选 linkedhashmap,caffeine
      keyConvertor: fastjson # 指定KEY的转换方式, 可选 fastjson2,fastjson,jackson
  # 远程
  remote:
    default:
      type: redis
      keyConvertor: fastjson # 指定KEY的转换方式, 可选 fastjson2,fastjson,jackson
      valueEncoder: java # 可选 java,kryo,kryo5
      valueDecoder: java # 可选 java,kryo,kryo5
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: 127.0.0.1
      port: 6379
      password: 123456

 配置通用说明

参考官网:https://github.com/alibaba/jetcache/blob/master/docs/CN/Config.md

属性默认值说明
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表示不使用这个功能。

项目结构

代码

启动类

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;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
// 开启 Cache
@EnableMethodCache(basePackages = "com.myjetcache")
// 如果不用@CreateCache注解可以删除 EnableCreateCacheAnnotation
@EnableCreateCacheAnnotation
@EnableScheduling
public class SpringJetCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringJetCacheApplication.class, args);
    }
}

实体类

@Data
public class Student implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final Random RANDOM = new Random();

    /**
     * 当对象只包含基本数据类型的时候,RADM工具可以直接展示数据. 非基本数据类型的字段, 默认不会展示.
     */
    private Integer id;

    private String name;

    private Integer age;

    public static Student getStudent(String id) {
        Student student = new Student();
        student.setId(Integer.parseInt(id));
        student.setName(UUID.randomUUID().toString().substring(0, 3));
        student.setAge(RANDOM.nextInt(9999));
        return student;
    }
}

基础使用——增删改查(@Cached、@CacheInvalidate、@CacheUpdate)

先定义接口

public interface JetCacheBaseService {

    Student add(Student student);

    void delete(Long id);

    void update(Student student);

    Student get(Long id);

    Student get(Long id, boolean isUseCache);
}

写好实现类

需注意的点:

@Cached——[增]

注意点:

  • 由于我们是在方法上使用,该注解缓存的是方法 return 的数据,因此方法返回类型不能是 void
  • 缓存执行的是 com.alicp.jetcache.Cache#PUT() , 接口默认是异步存储 

@CacheInvalidate——[删]

  • 注意点:方法执行后,再删除

@CacheUpdate——[改]

  • 注意点:方法执行后,再修改,作者说明: https://github.com/alibaba/jetcache/issues/115

@Cached——[查]

注意点:

  • 先查缓存,无缓存则走方法
  • 如果注解配合 condition 属性,那么 condition=true: 查缓存, 无缓存则走方法;condition=false: 不查缓存, 直接执行方法(查数据库)
@Slf4j
@Service
public class JetCacheBaseServiceImpl implements JetCacheBaseService {

    private final Random random = new Random();

    /**
     * 增 @Cached
     * 缓存执行的是 com.alicp.jetcache.Cache#PUT() , 接口默认是异步存储
     *
     * @param student spel表达式取值
     * @return 待缓存的的数据(注意,这个返回值不能void)
     */
    @Override
    @Cached(area = "default", name = "my:jetcache:", key = "#student.id", cacheType = CacheType.BOTH,
            expire = 3600, localExpire = 10, timeUnit = TimeUnit.SECONDS, cacheNullValue = false)
    public Student add(Student student) {
        log.info("student:{}", JSON.toJSONString(student));
        return student;
    }

    /**
     * 删 @CacheInvalidate
     * 方法执行后,再删除,作者 huangli 表示也不会增加这个功能选项, 自主控制粒度即可
     */
    @Override
    @CacheInvalidate(name = "my:jetcache:", key = "#id")
    public void delete(Long id) {
        log.info("delete");
    }

    /**
     * 改 @CacheUpdate
     * 方法执行后,再修改. 作者说明:https://github.com/alibaba/jetcache/issues/115
     */
    @Override
    @CacheUpdate(name = "my:jetcache:", key = "#student.id", value = "#student")
    public void update(Student student) {
        log.info("update:{}", JSON.toJSONString(student));
    }

    /**
     * 查
     * 先查缓存,无缓存则走方法
     */
    @Override
    @Cached(name = "my:jetcache:", key = "#id")
    public Student get(Long id) {
        log.info("load from db");
        // 当缓存不存在, 模拟从数据库查询
        return loadFromDb(id);
    }

    /**
     * 查
     * condition=true: 查缓存, 无缓存则走方法
     * condition=false: 不查缓存, 直接执行方法(查数据库)
     */
    @Override
    @Cached(name = "my:jetcache:", key = "#id", condition = "#isUseCache==true")
    public Student get(Long id, boolean isUseCache) {
        // 当 isUseCache 为 false, 或者 缓存不存在的时候, 插叙数据库
        log.info("load from db, isUseCache:{}", isUseCache);
        // 当缓存不存在, 模拟从数据库查询
        return loadFromDb(id);
    }

    /**
     * 模拟从数据库查询
     */
    private Student loadFromDb(Long id) {
        Student student = new Student();
        student.setId(Math.toIntExact(id));
        student.setName("load from db");
        student.setAge(random.nextInt(100));
        return student;
    }
}

基础使用——@CreateCache注解和手动方式

由于 @CreateCache 以及标记为@Deprecated了,这里优先将手动方式

我们先看下属性说明:

@CreateCache属性表

参考官网Link:https://github.com/alibaba/jetcache/blob/master/docs/CN/CreateCache.md#createcache%E5%B1%9E%E6%80%A7%E8%A1%A8

属性默认值说明
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。如果注解上没有定义,则使用全局配置。

先定义接口

public interface CreateCacheUseMethodService {

    Cache<String, String> getStringMethodCache();
}

注意实现类有下面几个要注意的地方:

  • refresh属性功能,必须要 QuickConfig 显示创建,不要用 cache.config().setRefreshPolicy() ,否则可能会出现无效的情况
  • cache.config().setLoader() 加载器会遍历每一个key,并执行这个加载器;缓存没有key的时候是不会执行的,首次 set/get 都会创建 key 
  • 由于refresh 会用加载器刷新所有key,那么一定要设置 stopRefreshAfterLastAccess,表示多久不使用对应的key缓存则会停止刷新。
  • 本地缓存的时间 < 远程缓存的时间,缓存refresh的时间可以接近远程缓存时间,也可以大于它

代码如下,可以仔细阅读注释:

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.RefreshPolicy;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.template.QuickConfig;
import com.myjetcache.service.CreateCacheUseMethodService;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.UUID;

import javax.annotation.PostConstruct;

@Slf4j
@Service
public class CreateCacheUseMethodServiceImpl implements CreateCacheUseMethodService {

    @Autowired
    private CacheManager cacheManager;

    private Cache<String, String> stringMethodCache;

    @PostConstruct
    public void init() {
        // 注意必须要refreshPolicy() ,才能创建 RefreshCache, 否则自动刷新功能无效
        QuickConfig quickConfigStringCache = QuickConfig.newBuilder("myStringCache:use:method:") // 缓存的前缀
                .cacheType(CacheType.BOTH) // local 和 remote 组合成两级缓存
                .expire(Duration.ofSeconds(3600)) // 远程过期时间
                .localExpire(Duration.ofSeconds(5)) // 本地过期时间, 应该小于远程过期时间, 只对CacheType.LOCAL和CacheType.BOTH有效
                .localLimit(1000) // 本地缓存的最大元素数量, 默认:100
                .cacheNullValue(false) // 是否缓存 NULL 值
                .refreshPolicy(getRefreshPolicy()) // 这里必须显式创建, 不要使用 cache.config().setRefreshPolicy(), 否则无效
                .build();

        stringMethodCache = cacheManager.getOrCreateCache(quickConfigStringCache);
        // 刷新执行的加载器, 会遍历刷新每一个key
        stringMethodCache.config().setLoader(this::loadFromDb);
    }

    /**
     * 创建刷新策略
     * 等于注解:@CacheRefresh(refresh = 10, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600, timeUnit = TimeUnit.SECONDS)
     */
    private RefreshPolicy getRefreshPolicy() {
        RefreshPolicy refreshPolicy = new RefreshPolicy();
        // 刷新时间间隔
        refreshPolicy.setRefreshMillis(10 * 1000L);
        // 类型为 BOTH/REMOTE 的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间
        // 不管有多少台服务器,同时只有一个服务器在刷新,这是通过 tryLock 实现的
        refreshPolicy.setRefreshLockTimeoutMillis(10 * 1000);
        // 指定多久未访问后停止自动刷新。 注意:不指定则会一直刷新
        refreshPolicy.setStopRefreshAfterLastAccessMillis(3600 * 1000);
        return refreshPolicy;
    }

    /**
     * 刷新执行的加载器
     * 每个key都有一个刷新任务, 因此必须设置 stopRefreshAfterLastAccess
     * 注意: 没有key则不会定时执行数据库加载器, 首次 get/set 都相当于创建了key
     *
     * @param key 刷新的key
     */
    public String loadFromDb(String key) {
        // 模拟从数据库读取数据
        String uuid = UUID.randomUUID().toString();
        log.info("[Use Method] key:{},load cache form db:{}", key, uuid);
        return uuid;
    }

    @Override
    public Cache<String, String> getStringMethodCache() {
        return stringMethodCache;
    }
}

 上面是手动创建,接下来我们看下如何使用注解 @CreateCache 创建(不建议使用该注解,因为被作者标记为过期 @Deprecated 了)

先定义一个接口

public interface CreateCacheUseAnnotationService {

    Cache<String, String> getStringCache();
}

实现类如下,注解的属性跟上面手动方式的属性可以对应,可以看上面代码的注解。

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.myjetcache.service.CreateCacheUseAnnotationService;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

@Slf4j
@Service
public class CreateCacheUseAnnotationServiceImpl implements CreateCacheUseAnnotationService {
    /**
     * 注解方式
     * local 失效后, 会从 redis 查, redis 也没有则会执行数据库加载器
     */
    @CreateCache(name = "myStringCache:use:annotation:", cacheType = CacheType.BOTH, expire = 3600, localExpire = 5,
            localLimit = 1000)
    @CacheRefresh(refresh = 20, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600, timeUnit = TimeUnit.SECONDS)
    private Cache<String, String> stringCache;

    @PostConstruct
    public void init() {
        // 1.cache.get(),如果local和redis都没有缓存, 则会执行数据库加载器 loadFromDb()
        // 2.定时刷新刷新的加载器, 会为每一个key创建刷新任务; 注意: 没有key则不会定时执行数据库加载器, 首次 get/set 都相当于创建了key
        stringCache.config().setLoader(this::loadFromDb);
    }

    /**
     * 刷新执行的加载器,jetcache会为每个key都会创建一个刷新任务, 因此必须设置 stopRefreshAfterLastAccess, 否则内存消耗太大
     *
     * @param key 刷新的key
     */
    public String loadFromDb(String key) {
        // 模拟从数据库读取数据
        String uuid = UUID.randomUUID().toString();
        log.info("[Use Annotation] key:{},load cache form db:{}", key, uuid);
        return uuid;
    }

    @Override
    public Cache<String, String> getStringCache() {
        return stringCache;
    }
}

基础使用——@CacheRefresh 详解

需要注意的点如下:

注意 CacheRefresh 会为每个key创建定时任务, 定时来执行这个方法
官方文档:
1.目的是为了防止缓存失效时造成的雪崩效应打爆数据库
2.对key比较少,实时性要求不高,加载开销非常大的缓存场景,适合使用自动刷新

CacheRefresh 刷新机制:
1.如果 CacheType.LOCAL ,那么多个节点会重复刷新。
2.如果 CacheType.REMOTE ,通过在远程缓存中的分布式锁'_#TS#',保证一个周期内只有一个节点执行了刷新操作.
3.如果 CacheType.BOTH ,即两级缓存,通过在远程缓存中的分布式锁,保证一个周期内只有一个节点执行了刷新操作.

注意:仅更新REMOTE,其节点的本地缓存不会更新.jetcache支持给远程和本地缓存设置不同的超时时间,所以可以把本地缓存的超时时间设置短一点.

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

具体代码如下:

public interface RefreshService {

    String getRefreshStringCache(String key);

    Student getRefreshMapCache(Object obj);
}

实现类: 


import com.alibaba.fastjson.JSON;
import com.alicp.jetcache.anno.CachePenetrationProtect;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.myjetcache.entity.Student;
import com.myjetcache.service.RefreshService;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Service
public class RefreshServiceImpl implements RefreshService {

    private final Random random = new Random();

    private final AtomicInteger atomicInteger = new AtomicInteger(0);

    /**
     * 注意 CacheRefresh 会为每个key创建定时任务, 定时来执行这个方法
     * 官方文档:
     * 1.目的是为了防止缓存失效时造成的雪崩效应打爆数据库
     * 2.对key比较少,实时性要求不高,加载开销非常大的缓存场景,适合使用自动刷新
     * <p>
     * CacheRefresh 刷新机制:
     * 1.如果 CacheType.LOCAL ,那么多个节点会重复刷新。
     * 2.如果 CacheType.REMOTE ,通过在远程缓存中的分布式锁'_#TS#',保证一个周期内只有一个节点执行了刷新操作.
     * 3.如果 CacheType.BOTH ,即两级缓存,通过在远程缓存中的分布式锁,保证一个周期内只有一个节点执行了刷新操作.
     * 注意:仅更新REMOTE,其节点的本地缓存不会更新.jetcache支持给远程和本地缓存设置不同的超时时间,所以可以把本地缓存的超时时间设置短一点.
     * <p>
     * CachePenetrationProtect 注解:
     * 当缓存访问【未命中】的情况下,对并发进行的加载行为进行保护.
     * 当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果
     *
     * @param key 缓存的 key
     * @return 缓存的 value
     */
    @Override
    @Cached(name = "my:refresh:stringCache.", key = "#key", cacheNullValue = true, cacheType = CacheType.BOTH,
            expire = 3600, localExpire = 60, timeUnit = TimeUnit.SECONDS)
    @CacheRefresh(timeUnit = TimeUnit.SECONDS, refresh = 10, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600)
    @CachePenetrationProtect
    public String getRefreshStringCache(String key) {
        String value;
        // 模拟查询数据库, 首次查询返回 null, 以后每次查询,如果缓存不存在,则返回 随机uuid
        if (atomicInteger.get() == 0) {
            atomicInteger.addAndGet(1);
            value = null;
        } else {
            // 每隔10秒就会刷新新的uuid到 本地和redis
            value = UUID.randomUUID().toString();
        }
        log.info("return value:{}", value);
        return value;
    }


    /**
     * Map结构的SpEL表达式参考下面的写法
     * 入参是的 SpEL是 key
     * return 是存储的 value
     */
    @Override
    @Cached(area = "default", name = "my:jetcache:", key = "#obj['id']", cacheType = CacheType.BOTH,
            expire = 3600, localExpire = 60, timeUnit = TimeUnit.SECONDS, cacheNullValue = false)
    @CacheRefresh(timeUnit = TimeUnit.SECONDS, refresh = 10, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600)
    @CachePenetrationProtect
    public Student getRefreshMapCache(Object obj) {
        log.info("obj:{}", JSON.toJSONString(obj));
        Student student = Student.getStudent(String.valueOf(random.nextInt(100)));
        log.info("after refresh:{}", JSON.toJSONString(student));
        return student;
    }
}

测试类:

@RestController
@Slf4j
public class RefreshController {

    private final Random random = new Random();

    @Autowired
    private RefreshService refreshService;

    @GetMapping("/refreshstringcache/get")
    public String getStringCache() {
        String stringCache = refreshService.getRefreshStringCache("myStringCache");
        log.info(stringCache);
        return stringCache;
    }


    @GetMapping("/getrefreshmapcache/get")
    public Student getRefreshMapCache() {
        Map<String, Object> body = new HashMap<>();
        body.put("userId", "1");
        body.put("id", "123");
        return refreshService.getRefreshMapCache(body);
    }
}

上面代码是Jetcache的简单自测,满足一般的业务开发,如果更深入的了解,请到官网学习:https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

github上有哪些值得读源码的react项目?

前言 下面是我整理的关于值得一读源码的react项目&#xff0c;希望对你有所帮助~ 1、 calcom Star: 21.6k calcom是一个开源的计算器应用程序。它提供了基本的数学运算功能&#xff0c;例如加法、减法、乘法和除法&#xff0c;还支持 科学计算、进制转换和单位转换等高级功能…

【刻削生千变,丹青图“万相”】阿里云AI绘画创作模型 “通义万相”测评

刻削生千变&#xff0c;丹青图“万相 4月7日&#xff0c;阿里大模型“通义千问”开始邀请用户测试体验。现阶段该模型主要定向邀请企业用户进行体验测试&#xff0c;用户可通过官网申请&#xff08;tongyi.aliyun.com&#xff09;&#xff0c;符合条件的用户可参与体验。 随…

skywalking忽略调用链路中的指定异常

文章目录 一、介绍二、演示项目介绍1. 支付服务2. 订单服务 三、项目演示1. 未忽略异常2. 忽略异常修改配置使用注解 四、结论 往期内容 一、skywalking安装教程 二、skywalking全链路追踪 三、skywalking日志收集 一、介绍 在前面介绍在微服务项目中使用skywalking进行全链…

LLaMA长度外推高性价比trick:线性插值法及相关改进源码阅读及相关记录

前言 最近&#xff0c;开源了可商用的llama2&#xff0c;支持长度相比llama1的1024&#xff0c;拓展到了4096长度&#xff0c;然而&#xff0c;相比GPT-4、Claude-2等支持的长度&#xff0c;llama的长度外推显得尤为重要&#xff0c;本文记录了三种网络开源的RoPE改进方式及相…

使用appuploader怎么安装测试​

使用appuploader怎么安装测试​ 一.安装测试​ 首先我们来看安装测试这个模块&#xff0c;注意按照上面提示内容操作。 点击首页的测试设备管理 二.选择IPA​ 进入“安装测试”页面&#xff0c;选择一个&#xff08;必须是开发类型描述文件编译&#xff0c;且描述文件包含设…

f1tenth仿真设置

文章目录 一、安装依赖二、进入工作空间克隆三、编译四、运行 一、安装依赖 tf2_geometry_msgs ackermann_msgs joy map_server sudo apt-get install ros-noetic-tf2-geometry-msgs ros-noetic-ackermann-msgs ros-melodic-joy ros-noetic-map-server 二、进入工作空间克隆…

详解Linux文本三剑客

目录 一、grep 1.什么是grep? 2.如何使用&#xff1f; 3.正则 二、sed 1.认识sed? 2.如何使用&#xff1f; 三、awk&#xff08;重点&#xff09; 1.awk变量 1.1内置变量 1.2自定义变量 2.awk数组 四、经典实战案例 案例一&#xff1a;筛选IPv4地址 案例二&am…

为MySQL新增一张performance_schema表 | StoneDB 技术分享会 #4

StoneDB开源地址 https://github.com/stoneatom/stonedb 设计&#xff1a;小艾 审核&#xff1a;丁奇、李浩 编辑&#xff1a;宇亭 作者&#xff1a;王若添 中国科学技术大学-软件工程-在读硕士、StoneDB 内核研发实习生 performance_schema 简介 MySQL 启动后会自动创建四…

基于SpringBoot+LayUI的宿舍管理系统 001

项目简介 源码来源于网络&#xff0c;项目文档仅用于参考&#xff0c;请自行二次完善哦。 系统以MySQL 8.0.23为数据库&#xff0c;在Spring Boot SpringMVC MyBatis Layui框架下基于B/S架构设计开发而成。 系统中的用户分为三类&#xff0c;分别为学生、宿管、后勤。这三…

【MySQL常见面试题】

索引的基本原理 索引⽤来快速地寻找那些具有特定值的记录。如果没有索引&#xff0c;⼀般来说执⾏查询时遍历整张表。 索引的原理&#xff1a;就是把⽆序的数据变成有序的查询 把创建了索引的列的内容进⾏排序 对排序结果⽣成倒排表 在倒排表内容上拼上数据地址链 在查询的…

计算机网络:网络通信相关概念入门

目录 一、网络发展背景二、理解网络通信三、理解IP地址1.简述IP地址2.IP地址的版本3.提高地址利用率的技术 四、理解端口1.简述端口2.使用端口的原因 五、理解网络通信协议 一、网络发展背景 网络发展背景&#xff1a; 最初的计算机是单机&#xff0c;那么单机是这样传输数据的…

谁是5G应用的狮子座?

8月5日&#xff0c;广和通公布2023年上半年财报&#xff0c;2023上半年总营业收入38.65亿元&#xff0c;同比增长59.87%。在全球经济存在诸多不确定性的背景下&#xff0c;广和通精准预判市场风向&#xff0c;制定稳健发展策略&#xff0c;业绩仍保持逆势增长。其中&#xff0c…

详谈基于布局分析的表格识别方法

基于布局分析的OCR&#xff08;Optical Character Recognition&#xff09;是一种基于页面布局信息的文本识别方法。传统的OCR系统通常依赖于表格线或者特定的格式来进行文本区域检测和字符识别&#xff0c;但对于一些表格线不全或线不清晰&#xff0c;甚至没表格线&#xff0c…

协程(一)单机--》并发--》协程

目录 一 协程的概述1.1 并行与并发1.2 线程1.3 新的思路1.4 Goroutine 二 第一个入门程序 一 协程的概述 我查看了网上的一些协程的资料&#xff0c;发现每个人对协程的概念都不一样&#xff0c;但是我认可的一种说法是&#xff1a;协程就是一种轻量级的线程框架&#xff08;K…

去趋势化一个心电图信号、信号功率谱、低通IIR滤波器并平滑信号、对滤波器引起的延迟进行补偿研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【C语言进阶篇】关于指针的八个经典笔试题(图文详解)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 指针笔试题&#x1f4ad; 笔试题 1&#xff1a;✅ 代码解析⁉️ 检验结果&…

二叉树(4)------收尾

1)最大二叉树 654. 最大二叉树 - 力扣&#xff08;LeetCode&#xff09; 题目解析: 1)首先我们找到了整个数组中最大的元素作为我们的根节点&#xff0c;然后再从左区间中找到最大的元素作为当前根节点的左子树&#xff0c;然后再从右区间里面找到最大的元素作为根节点的右子树…

【OpenVINOSharp】 基于C#和OpenVINO2023.0部署Yolov8全系列模型

基于C#和OpenVINO2023.0部署Yolov8全系列模型 1 项目简介1.1 OpenVINOTM 2 OpenVinoSharp2.1 OpenVINOTM 2023.0安装配置2.2 C 动态链接库2.3 C#构建Core推理类2.4 NuGet安装OpenVinoSharp 3 获取和转换Yolov8模型3.1 安装ultralytics3.2 导出yolov8模型3.3 安装OpenVINOTM Pyt…

杭电多校 Rikka with Square Numbers 费马平方和定理

&#x1f468;‍&#x1f3eb; Rikka with Square Numbers &#x1f9c0; 参考题解 &#x1f37b; AC code import java.util.Scanner;public class Main {static boolean isSqu(int x){int t (int) Math.sqrt(x);return t * t x;}public static void main(String[] args…

Vue2:组件基础(上)

Vue2&#xff1a;组件基础&#xff08;上&#xff09; Date: July 29, 2023 Sum: 生命周期、Vue-cli、组件的使用、小黑记账清单、小兔鲜首页 生命周期&#xff1a; 生命周期介绍 思考&#xff1a; 什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#x…