介紹
Caffeine是一個基於Java8開發的提供了近乎最佳命中率的高效能的快取庫。
快取和ConcurrentMap有點相似,但還是有所區別。最根本的區別是ConcurrentMap將會持有所有加入到快取當中的元素,直到它們被從快取當中手動移除。
但是,Caffeine的快取Cache 通常會被配置成自動驅逐快取中元素,以限制其記憶體佔用。在某些場景下,LoadingCache和AsyncLoadingCache 因為其自動載入快取的能力將會變得非常實用。
基本使用
GitHub 官方文件:https://github.com/ben-manes/caffeine/wiki/Home-zh-CN
專案整合
使用 Caffeine 作為一級快取,Redis 作為二級快取。先從 Caffeine 讀取快取,如果讀不到則到 Redis 中讀取,如果還沒有則返回 null.
模組使用了 Redis 作為二級快取,使用 stringRedisTemplate 模板,Jedis 作為客戶端。
Spring 配置
@Configuration
public class Config {
@Bean
public AbstractStringFirstCache stringFirstCache() {
// 可以隨意更換底層實現,比如我們可以使用 Guava Cache,只需要 new GuavaCache() 並繼承 AbstractStringFirstCache 即可
return new CaffeineCache();
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
@Bean
public JedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost", 6379);
return new JedisConnectionFactory(config);
}
}
核心程式碼
// 核心介面
public interface FirstCache<K, V> {
V get(@NonNull K key);
void set(@NonNull K key, V value);
void delete(@NonNull K key);
}
public abstract class AbstractFirstCache<K, V> implements FirstCache<K, V> {
}
public abstract class AbstractStringFirstCache extends AbstractFirstCache<String, String> {
abstract void set(@NonNull String key, String value, Long expertTime, TimeUnit timeUnit);
}
public class CaffeineCache extends AbstractStringFirstCache {
Log log = LogFactory.get(); //Hutool api
@Autowired
private StringRedisTemplate stringRedisTemplate;
LoadingCache<String, String> caffeineCache;
public CaffeineCache() {
LoadingCache<String, String> cache = Caffeine.newBuilder()
.recordStats()
.initialCapacity(100)
.maximumSize(1000)
.writer(new CacheWriter<String, String>() {
@Override
public void write(String key, String value) {
log.info("caffeineCache write key=" + key + ", value=" + value);
}
@Override
public void delete(String key, String value, RemovalCause cause) {
log.info("caffeineCache delete key=" + key);
}
})
.expireAfterWrite(30, TimeUnit.SECONDS) //一個元素將會在其建立或者最近一次被更新之後的一段時間後被認定為過期項
.build(new CacheLoader<String, String>() {
// 查詢二級快取
@Override
public @Nullable String load(@NonNull String s) throws Exception {
String value = stringRedisTemplate.opsForValue().get(s);
if (StrUtil.isEmpty(value)) {
return null;
}
return value;
}
});
caffeineCache = cache;
}
@Override
public String get(String key) {
//查詢快取,如果快取不存在則生成快取元素
String value = caffeineCache.get(key);
log.info("get key from caffeineCache, key: " + key);
return value;
}
@Override
public void set(String key, String value) {
//放入二級快取
stringRedisTemplate.opsForValue().set(key, value);
stringRedisTemplate.expire(key, 15, TimeUnit.MINUTES);
}
@Override
void set(String key, String value, Long expertTime, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(key, value);
stringRedisTemplate.expire(key, expertTime, timeUnit);
}
@Override
public void delete(String key) {
caffeineCache.invalidate(key);
stringRedisTemplate.delete(key);
log.info("delete key from caffeineCache, key: " + key);
}
}
工具類
@Component
public class CaffeineCacheUtils {
static AbstractStringFirstCache cache;
@Autowired
public void setCache(AbstractStringFirstCache cache) {
CaffeineCacheUtils.cache = cache;
}
/**
* 查詢快取,如果快取不存在則生成快取元素
*
* @return value or null
*/
public static String get(@NonNull String key) {
return cache.get(key);
}
/**
* 設定快取,預設 15min
*/
public static void set(@NonNull String key, String value) {
cache.set(key, value);
}
/**
* 設定快取,提供時間和時間單位
*/
public static void set(@NonNull String key, String value, @NonNull Long expertTime, @NonNull TimeUnit timeUnit) {
cache.set(key, value, expertTime, timeUnit);
}
/**
* 刪除快取
*/
public static void delete(@NonNull String key) {
cache.delete(key);
}
}