jetcache缓存使用

1、简介

​ jetcache 是阿里开源的一个缓存框架,支持像guava的cache、caffeine的Cache 用法,也可以集成springboot,像spring的@Cache 注解一样进行使用。

​ jetcache的缓存类似于map,提供了get、put、putAll、computeIfAbsent等方法, 另外还提供了单机锁、分布式锁机制,一般也不用这个做锁就先不研究了。

2、简单使用

1、直接cache 用法

1、pom

<!-- https://mvnrepository.com/artifact/com.alicp.jetcache/jetcache-core -->
        <dependency>
            <groupId>com.alicp.jetcache</groupId>
            <artifactId>jetcache-core</artifactId>
            <version>2.6.7</version>
        </dependency>

2、简单用法

1》创建缓存的操作类似guava/caffeine cache,例如下面的代码创建基于内存的LinkedHashMapCache

package qz.jetcache;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheConfig;
import com.alicp.jetcache.embedded.EmbeddedCacheConfig;
import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class CacheClient {

    public static void main(String[] args) throws InterruptedException {
        Cache<String, String> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
                .limit(20)
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .buildCache();

        // 每个cache 都有个配置,包括缓存的大小、过期时间、刷新时间、刷新加载开关、刷新加载线程池、刷新加载策略、key/value转换器等
        CacheConfig<String, String> config = cache.config();

        // 第三个参数表示是否要缓存null值
        log.info(cache.computeIfAbsent("1", k -> doGetFromDB(k), true));
        log.info(cache.computeIfAbsent("1", k -> doGetFromDB(k), true));
        log.info(cache.computeIfAbsent("2", k -> doGetFromDB(k), true));
        log.info(cache.computeIfAbsent("2", k -> doGetFromDB(k), true));
        log.info(cache.computeIfAbsent("3", k -> doGetFromDB(k), false));
        log.info(cache.computeIfAbsent("3", k -> doGetFromDB(k), false));
        /**
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO] doGetFromDB, key: 1
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO] 1_1
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO] 1_1
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO] doGetFromDB, key: 2
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO]
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO]
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO] doGetFromDB, key: 3
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO]
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO] doGetFromDB, key: 3
         * 2024-08-01 16:35:36 [main] [qz.jetcache.CacheClient]-[INFO]
         */
    }

    private static String doGetFromDB(String k) {
        log.info("doGetFromDB, key: {}", k);
        if ("1".equals(k)) {
            return "1_1";
        }

        return null;
    }
}

2》也能用caffeine, jetcache 做了整合

        Cache<String, String> cache = CaffeineCacheBuilder.createCaffeineCacheBuilder()
                .limit(20)
                .buildCache();

2、整合springboot 使用

可以作为本地缓存使用,也可以作为分布式缓存使用,也可以本地和远程同时写。

1、pom依赖

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

2、增加配置类

package cn.qz.cloud.alibaba.config;

import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.RefreshPolicy;
import com.alicp.jetcache.anno.CacheConsts;
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.lettuce.RedisLettuceCacheBuilder;
import com.alicp.jetcache.support.FastjsonKeyConvertor;
import com.alicp.jetcache.support.JetCacheExecutor;
import com.alicp.jetcache.support.KryoValueDecoder;
import com.alicp.jetcache.support.KryoValueEncoder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class JetcacheConfig {

    @Bean
    public RedisClient redisClient() {
        // 创建 RedisClient
        RedisClient redisClient = RedisClient.create(RedisURI.create("redis://127.0.0.1:6379"));
        return redisClient;
    }

    @Bean
    public SpringConfigProvider springConfigProvider() {
        return new SpringConfigProvider();
    }

    @Bean
    public GlobalCacheConfig config(RedisClient redisClient, @Qualifier("cache-reader") ScheduledExecutorService reader, @Qualifier("cache-refresh") ScheduledExecutorService refresh) {
        JetCacheExecutor.setDefaultExecutor(reader);
        JetCacheExecutor.setHeavyIOExecutor(refresh);
        Map<String, CacheBuilder> localBuilders = new HashMap<>();
        EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder
                .createLinkedHashMapCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .limit(100)
                .expireAfterWrite(30, TimeUnit.SECONDS);
        localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);

        Map<String, CacheBuilder> remoteBuilders = new HashMap<>();
        RefreshPolicy refreshPolicy = RefreshPolicy.newPolicy(1, TimeUnit.HOURS);
        RedisLettuceCacheBuilder remoteCacheBuilder = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .valueEncoder(KryoValueEncoder.INSTANCE)
                .valueDecoder(KryoValueDecoder.INSTANCE)
                .refreshPolicy(refreshPolicy)
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .redisClient(redisClient);
        remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);

        GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
        globalCacheConfig.setLocalCacheBuilders(localBuilders);
        globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
        globalCacheConfig.setStatIntervalMinutes(15);
        globalCacheConfig.setAreaInCacheName(false);

        return globalCacheConfig;
    }

    @Bean("cache-reader")
    public ScheduledExecutorService cacheReaderExecutor() {
        log.info("cacheReaderExecutor");
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("cache-reader-thread-pool-%d").setDaemon(true).build();
        return new ScheduledThreadPoolExecutor(10, threadFactory, (r, executor) -> {
            log.info("cacheReaderExecutor executorActiveCount={} Task={} rejected from{}", executor.getActiveCount(), r.toString(), executor.toString());
        });
    }

    @Bean("cache-refresh")
    public ScheduledExecutorService cacheRefreshExecutor() {
        log.info("cacheRefreshExecutor");
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("cache-refresh-thread-pool-%d").setDaemon(true).build();
        return new ScheduledThreadPoolExecutor(10, threadFactory, (r, executor) -> {
            log.info("cacheRefreshExecutor executorActiveCount={} Task={} rejected from{}", executor.getActiveCount(), r.toString(), executor.toString());
        });
    }
}

3、测试类:

package cn.qz.cloud.alibaba.controller;

import cn.qz.cloud.alibaba.bean.User;
import com.alicp.jetcache.anno.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RequestMapping("/user")
@RestController
@Slf4j
public class UserController {

    private static final String cacheKeyPrefix = "user:cache:";

    private Map<String, User> userMap = new HashMap() {
        {
            put("qz", new User("qz", 18));
            put("zs", new User("zs", 18));
            put("ww", new User("ww", 18));
        }
    };

    /**
     * 查看所有,默认是存在远程缓存remote 种
     *
     * @return
     */
    @RequestMapping("/lists")
    @Cached(name = cacheKeyPrefix + "listUser", expire = 60, timeUnit = TimeUnit.MINUTES)
    public List<User> listUser() {
        log.info("获取用户集合listUser");
        return new ArrayList<>(userMap.values());
    }

    /**
     * 缓存到本地
     *
     * @return
     */
    @RequestMapping("/lists2")
    @Cached(name = cacheKeyPrefix + "listUser2", expire = 60, timeUnit = TimeUnit.MINUTES, cacheType = CacheType.LOCAL)
    public List<User> listUser2() {
        log.info("获取用户集合listUser2");
        return new ArrayList<>(userMap.values());
    }

    @RequestMapping("/find")
    @CachePenetrationProtect // 防止缓存击穿,通过在多个并发请求中只有一个请求去加载数据,其他请求等待加载完成后直接从缓存获取 (参数相同的时候只有一个线程能获取到)
    @Cached(name = cacheKeyPrefix + "find:", key = "#name", expire = 120, timeUnit = TimeUnit.SECONDS, postCondition = "T(cn.qz.cloud.alibaba.config.CacheCondition).checkNeedCache(#result)")
    // cacheNullValue = true 标识是否结果为null 的情况
    @CacheRefresh(refresh = 60, stopRefreshAfterLastAccess = 300, timeUnit = TimeUnit.SECONDS)
    //60秒刷新一次,如果300秒内没有访问,则停止刷新。刷新机制: 框架自己异步调用该方法进行刷新缓存
    public User find(@RequestParam String name) throws InterruptedException {
        log.info("查找用户, name: {}", name);
        Thread.sleep(2 * 1000);
        return userMap.get(name);
    }

    /**
     * 清除多个缓存,拼接固定的key和带参数的用户详情
     *
     * @param name
     */
    @RequestMapping("/del")
    @CacheInvalidate(name = cacheKeyPrefix + "listUser", key = "'[]'")
    @CacheInvalidate(name = cacheKeyPrefix + "find:", key = "#name")
    @CacheInvalidate(name = cacheKeyPrefix + "listUser2", key = "'[]'")
    public void del(@RequestParam String name) {
        log.info("清除缓存");
    }

}

4、工具类

package cn.qz.cloud.alibaba.config;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CacheCondition {

    /**
     * 检查是否需要缓存
     * <p>
     * 可以根据返回的结果进行判断
     *
     * @param object
     * @return
     */
    public static boolean checkNeedCache(Object object) {
        log.info("checkNeedCache: {}", object);
        if (object == null) {
            return false;
        }
        return true;
    }
}

5、相关注解解释:

1、@Cached(name = cacheKeyPrefix + "find:", key = "#name", expire = 120, timeUnit = TimeUnit.SECONDS, postCondition = "T(cn.qz.cloud.alibaba.config.CacheCondition).checkNeedCache(#result)")
缓存注解,可以设置缓存的key(可以加参数)、时间、单位、postCondition 指定检查的方法,可以设置是否对结果进行缓存
2、@CachePenetrationProtect // 防止缓存击穿,通过在多个并发请求中只有一个请求去加载数据,其他请求等待加载完成后直接从缓存获取 (参数相同的时候只有一个线程能获取到)
3、@CacheRefresh(refresh = 60, stopRefreshAfterLastAccess = 300, timeUnit = TimeUnit.SECONDS)
	60秒刷新一次,如果300秒内没有访问,则停止刷新。刷新机制: 框架自己异步调用该方法进行刷新缓存
	cacheType为REMOTE或者BOTH的时候,刷新行为是全局唯一的,也就是说,即使应用服务器是一个集群,也不会出现多个服务器同时去刷新一个key的情况。
	一个key的刷新任务,自该key首次被访问后初始化,如果该key长时间不被访问,在stopRefreshAfterLastAccess指定的时间后,相关的刷新任务就会被自动移除,这样就避免了浪费资源去进行没有意义的刷新。
4、CacheInvalidate
清除缓存,拼接固定的key和带参数的用户详情


rediskey的结构:
1) "user:cache:listUser[]"
2) "user:cache:find:zs"
posted @ 2024-08-12 19:51  QiaoZhi  阅读(586)  评论(1编辑  收藏  举报