为什么需要多级缓存
缓存的引入是现代系统设计中必须考虑的重要环节。通过多级缓存可以显著提高系统性能。
Redis 缓存的特点
Redis 作为常用中间件,在业务系统中虽然不会遇到数据量增大和数据结构复杂导致的性能下降问题
但网络 IO 消耗会成为整个调用链路中不可忽视的部分
在微服务架构中,一次调用往往会涉及多次调用,例如 Pig OAuth2.0 的 client 认证
Caffeine 缓存的特点
综合上述特点,我们需要构建 L1 Caffeine JVM 级别内存和 L2 Redis 内存的多级缓存架构。
设计难点
目前大部分应用缓存都是基于 Spring Cache 实现,基于注解(annotation)的缓存技术存在以下问题:
Spring Cache 仅支持单一的缓存来源,即只能选择 Redis 实现或者 Caffeine 实现,不能同时使用
数据一致性:各层缓存之间的数据一致性问题,如应用层缓存和分布式缓存之前的数据一致性问题
缓存过期:Spring Cache 不支持主动的过期策略
业务流程
多级缓存的工作流程如下:
首先检查 Caffeine 本地缓存
如果本地缓存未命中,则查询 Redis 缓存
如果 Redis 缓存命中,则更新本地缓存并返回结果
如果两级缓存都未命中,则执行实际业务逻辑
更新结果到两级缓存
如何使用
1. 引入依赖
< dependency >
< groupId > com.pig4cloud.plugin </ groupId >
< artifactId > multilevel-cache-spring-boot-starter </ artifactId >
< version > 3.0.0 </ version >
</ dependency >
2. 开启缓存支持
@ EnableCaching
public class App {
public static void main ( String [] args ) {
SpringApplication . run ( App . class , args);
}
}
3. 使用缓存注解
@ Cacheable ( value = "get" , key = "#key" )
@ GetMapping ( "/get" )
public String get ( String key) {
return "success" ;
}
性能比较
测试环境:
OS: macOS Mojave
CPU: 2.3 GHz Intel Core i5
RAM: 8 GB 2133 MHz LPDDR3
JVM: corretto_11.jdk
Benchmark Mode Cnt Score Units 多级实现 thrpt 2 2716.074 ops/s 默认 redis thrpt 2 1373.476 ops/s
实现原理
1. 自定义 CacheManager
public class RedisCaffeineCacheManager implements CacheManager {
@ Override
public Cache getCache ( String name ) {
Cache cache = cacheMap . get (name);
if (cache != null ) {
return cache;
}
cache = new RedisCaffeineCache (name, stringKeyRedisTemplate, caffeineCache (), cacheConfigProperties);
Cache oldCache = cacheMap . putIfAbsent (name, cache);
log . debug ( "create cache instance, the cache name is : {}" , name);
return oldCache == null ? cache : oldCache;
}
}
2. 多级读取实现
public class RedisCaffeineCache extends AbstractValueAdaptingCache {
protected Object lookup ( Object key ) {
Object cacheKey = getKey (key);
// 1. 先调用 caffeine 查询是否存在指定的值
Object value = caffeineCache . getIfPresent (key);
if (value != null ) {
log . debug ( "get cache from caffeine, the key is : {}" , cacheKey);
return value;
}
// 2. 调用 redis 查询在指定的值
value = stringKeyRedisTemplate . opsForValue (). get (cacheKey);
if (value != null ) {
log . debug ( "get cache from redis and put in caffeine, the key is : {}" , cacheKey);
caffeineCache . put (key, value);
}
return value;
}
}
3. 过期策略实现
public class RedisCaffeineCache extends AbstractValueAdaptingCache {
@ Override
public void put ( Object key , Object value ) {
push ( new CacheMessage ( this . name , key));
}
@ Override
public ValueWrapper putIfAbsent ( Object key , Object value ) {
push ( new CacheMessage ( this . name , key));
}
@ Override
public void evict ( Object key ) {
push ( new CacheMessage ( this . name , key));
}
@ Override
public void clear () {
push ( new CacheMessage ( this . name , null ));
}
private void push ( CacheMessage message ) {
stringKeyRedisTemplate . convertAndSend (topic, message);
}
}
4. 消息监听器实现
public class CacheMessageListener implements MessageListener {
private final RedisTemplate < Object , Object > redisTemplate ;
private final RedisCaffeineCacheManager redisCaffeineCacheManager ;
@ Override
public void onMessage ( Message message , byte [] pattern ) {
CacheMessage cacheMessage = (CacheMessage) redisTemplate . getValueSerializer (). deserialize ( message . getBody ());
redisCaffeineCacheManager . clearLocal ( cacheMessage . getCacheName (), cacheMessage . getKey ());
}
}
源码地址
项目源码地址:https://github.com/pig-mesh/multilevel-cache-spring-boot-starter
♥️ 获取支持
遇到问题? 如果您在使用过程中遇到任何问题、有功能建议或需求,请点击此卡片前往 Gitee 仓库提交 Issue。