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"
【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】