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"