引言
上一篇文章Cache在Springboot中的实现与原理已经介绍了Spring Cache在SpringBoot中的实现与原理, 本文就来聊一聊如何在使用spring cache的时候结合本地缓存 + redis. 也就是自定义两级缓存.
1: 自定义缓存的原理
上一篇文章中已经提到, Cache接口定义了缓存操作的行为,CacheManager定义了如何产生Cache,我们需要定义自己的两级Cache,所以我们就需要定义自己的Cache与CacheManager。在Spring已经帮我们提供了一个CacheManager的实现类CompositeCacheManager。
2:如何实现
设计与实现思路
- 这里我们采用Caffeine(一级缓存) + Redis(二级缓存)。实现效果的流程图如下:
2. 需要的关键类与接口
- CompositeCacheManager:它是组合CacheManager的一个实现,其中它的
setCacheManagers方法允许设置一个或者多个CacheManager。 - Cache:定义缓存操作的行为,比如我们可以存到Redis或者内存中等,都可以由我们自己来实现
- CacheSyncManager: 自定义缓存同步管理接口,定义了如何进行缓存的同步, 比如使用Redis发布订阅或者RabbitMq消息来实现缓存的同步
- CaffeineRedisCacheManager:组合CacheManager, 存储自定义两级缓存
- MultipleCache:Cache的具体实现
- MultipleCacheNode:缓存节点, 本地+Redis
3. 部分代码及其实现
CacheManager相关如下:
@Bean
@ConditionalOnMissingBean(CacheManager.class)
public CompositeCacheManager cacheManager(CacheSyncManager cacheSyncManager, RedisCacheWriter redisCacheWriter) {
List<CacheManager> cacheManagerList = new LinkedList<>();
//caffeine
if (!CollectionUtils.isEmpty(cacheProperties.getCaffeine())) {
//构建CaffeineCacheManager
cacheManagerList.add(buildCaffeineCacheManager(cacheProperties.getCaffeine(), cacheSyncManager));
}
//redis
if (!CollectionUtils.isEmpty(cacheProperties.getRedis())) {
//构建RedisCacheManager
cacheManagerList.add(buildRedisCacheManager(redisCacheWriter, cacheSyncManager, cacheProperties.getRedis()));
}
//caffeine + redis
if (!CollectionUtils.isEmpty(cacheProperties.getMultiple())) {
cacheManagerList.add(buildCaffeineRedis(cacheSyncManager, redisCacheWriter));
}
CompositeCacheManager cacheManager = new CompositeCacheManager();
cacheManager.setCacheManagers(cacheManagerList);
return cacheManager;
}
/*
* 构建CaffeineCacheManger缓存
*/
private CaffeineCacheManagerAdapter buildCaffeineCacheManager(Collection<CacheConfigProperties.CaffeineCacheConfig> configs,
CacheSyncManager cacheSyncManager) {
Set<CaffeineCacheManagerAdapter.CacheConfig> caffeineCacheConfigs = new LinkedHashSet<>();
Set<String> caffeineCacheNames = new LinkedHashSet<>();
Map<String, AbstractCaffeineCacheStrategy> cacheStrategyMap = new HashMap<>(8);
Map<String, Set<CacheDecorationHandler>> decorationHandlers = new HashMap<>(8);
configs.forEach(item -> {
caffeineCacheNames.add(item.getName());
CaffeineCacheManagerAdapter.CacheConfig cacheConfig = new CaffeineCacheManagerAdapter.CacheConfig();
cacheConfig.setExpireAfterAccess(item.getExpireAfterAccess());
cacheConfig.setExpireAfterWrite(item.getExpireAfterWrite());
cacheConfig.setInitialCapacity(item.getInitialCapacity());
cacheConfig.setMaximumSize(item.getMaximumSize());
cacheConfig.setName(item.getName());
cacheConfig.setDisableSync(item.isDisableSync());
cacheConfig.setEnableSoftRef(item.isEnableSoftRef());
//Caffeine 的CacheLoader。
if (!ObjectUtils.isEmpty(item.getCacheLoader())
&& cacheLoaderMap.containsKey(item.getCacheLoader())) {
cacheConfig.setCacheLoader(cacheLoaderMap.get(item.getCacheLoader()));
}
//Caffeine缓存配置信息
caffeineCacheConfigs.add(cacheConfig);
//Caffeine自定义缓存存储策略, 如果有的话则应用。
if (!ObjectUtils.isEmpty(item.getStrategy()) && strategyMap.containsKey(item.getStrategy())) {
CacheStrategy strategy = strategyMap.get(item.getStrategy());
if (strategy instanceof AbstractCaffeineCacheStrategy) {
cacheStrategyMap.put(item.getName(), (AbstractCaffeineCacheStrategy) strategy);
}
}
//缓存包装策略, 如果有的话则应用
if (!ObjectUtils.isEmpty(item.getDecorators())) {
List<String> decoratorList = Arrays.asList(item.getDecorators().split(","));
Set<CacheDecorationHandler> collect = decoratorList.stream()
.map(decorationHandlerMap::get).collect(Collectors.toSet());
decorationHandlers.put(item.getName(), collect);
}
});
//CacheManager 继承于 CaffeineCacheManager
return new CaffeineCacheManagerAdapter(caffeineCacheNames, caffeineCacheConfigs, cacheStrategyMap,
decorationHandlers, cacheSyncManager);
}
/*
* 构建RedisCacheManager
*/
private RedisCacheManagerAdapter buildRedisCacheManager(RedisCacheWriter cacheWriter,
CacheSyncManager cacheSyncManager,
Collection<CacheConfigProperties.RedisCacheConfig> configs) {
Set<String> redisCacheNames = new LinkedHashSet<>();
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
Map<String, AbstractRedisCacheStrategy> cacheStrategyMap = new HashMap<>(8);
Map<String, Set<CacheDecorationHandler>> decorationHandlers = new HashMap<>(8);
configs.forEach(item -> {
//缓存名与缓存的key - value关系等
redisCacheNames.add(item.getName());
redisCacheConfigurationMap.put(item.getName(), redisCacheConfiguration(item));
//自定义的缓存策略,如果有则应用,没有就是用默认策略
if (item.getStrategy() != null && strategyMap.containsKey(item.getStrategy())) {
CacheStrategy strategy = strategyMap.get(item.getStrategy());
if (strategy instanceof AbstractRedisCacheStrategy) {
cacheStrategyMap.put(item.getName(), (AbstractRedisCacheStrategy) strategy);
}
} else {
DefaultRedisCacheStrategy cacheStrategy = new DefaultRedisCacheStrategy(item.getName());
cacheStrategyMap.put(item.getName(), cacheStrategy);
}
//缓存包装策略,如果有则应用
if (!ObjectUtils.isEmpty(item.getDecorators())) {
List<String> decoratorList = Arrays.asList(item.getDecorators().split(","));
Set<CacheDecorationHandler> collect = decoratorList.stream()
.map(decorationHandlerMap::get).collect(Collectors.toSet());
decorationHandlers.put(item.getName(), collect);
}
});
//CacheManager 继承于 RedisCacheManager
RedisCacheManagerAdapter redisCacheManager = new RedisCacheManagerAdapter(cacheWriter, redisCacheNames,
false, redisCacheConfigurationMap, cacheStrategyMap, decorationHandlers);
redisCacheManager.initCaches();
return redisCacheManager;
}
/*
* 构建CaffeineRedisCacheManager
*/
private CaffeineRedisCacheManager buildCaffeineRedis(CacheSyncManager cacheSyncManager, RedisCacheWriter redisCacheWriter) {
Set<CacheConfigProperties.CaffeineCacheConfig> caffeineCacheConfigs = new LinkedHashSet<>();
Set<CacheConfigProperties.RedisCacheConfig> redisCacheConfigs = new LinkedHashSet<>();
Map<String, Set<CacheDecorationHandler>> decorationHandlers = new HashMap<>(8);
cacheProperties.getMultiple().forEach(item -> {
//缓存配置参数
CacheConfigProperties.CaffeineCacheConfig caffeineCacheConfig = item.getCaffeine();
caffeineCacheConfig.setName(item.getName());
CacheConfigProperties.RedisCacheConfig redisCacheConfig = item.getRedis();
redisCacheConfig.setName(item.getName());
caffeineCacheConfigs.add(caffeineCacheConfig);
redisCacheConfigs.add(redisCacheConfig);
//缓存装饰策略
if (!ObjectUtils.isEmpty(item.getDecorators())) {
List<String> decoratorList = Arrays.asList(item.getDecorators().split(","));
Set<CacheDecorationHandler> collect = decoratorList.stream()
.map(decorationHandlerMap::get).collect(Collectors.toSet());
decorationHandlers.put(item.getName(), collect);
}
});
//CacheManager, 由RedisCacheManagerAdapter + CaffeineCacheManagerAdapter共同实现
CaffeineRedisCacheManager multipleCacheManager
= new CaffeineRedisCacheManager(
buildCaffeineCacheManager(caffeineCacheConfigs, cacheSyncManager),
buildRedisCacheManager(redisCacheWriter, cacheSyncManager, redisCacheConfigs), decorationHandlers);
multipleCacheManager.initializeCaches();
return multipleCacheManager;
}
Cache相关如下:
public class MultipleCache implements Cache {
private MultipleCacheNode cacheNode;
public static MultipleCacheBuilder builder() {
return new MultipleCacheBuilder();
}
public MultipleCache(MultipleCacheNode cacheNode) {
this.cacheNode = cacheNode;
}
@Override
public String getName() {
return cacheNode.getName();
}
@Override
public Object getNativeCache() {
return cacheNode.getNativeCache();
}
@Override
public ValueWrapper get(Object key) {
return cacheNode.get(key);
}
@Override
public <T> T get(Object key, Class<T> type) {
return (T) cacheNode.get(key, type);
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) cacheNode.get(key, valueLoader);
}
@Override
public void put(Object key, Object value) {
cacheNode.put(key, value);
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
return cacheNode.putIfAbsent(key, value);
}
@Override
public void evict(Object key) {
cacheNode.evict(key);
}
@Override
public void clear() {
cacheNode.clear();
}
public static class MultipleCacheBuilder {
private MultipleCacheNode cache;
public MultipleCacheBuilder nextNode(Cache cache) {
MultipleCacheNode node = new MultipleCacheNode(cache);
if (this.cache == null) {
this.cache = node;
} else {
this.cache.setNext(node);
}
return this;
}
public MultipleCache build() {
return new MultipleCache(cache);
}
}
}
public class MultipleCacheNode<T extends Cache> implements Cache {
//下一个缓存Node
private MultipleCacheNode next;
//当前缓存
private T cache;
public MultipleCacheNode(T cache) {
this.cache = cache;
}
public void setNext(MultipleCacheNode next) {
this.next = next;
}
public boolean hasNext() {
return null != next;
}
@Override
public String getName() {
return cache.getName();
}
@Override
public Object getNativeCache() {
return cache.getNativeCache();
}
@Override
public ValueWrapper get(Object key) {
ValueWrapper value = cache.get(key);
if (null == value && hasNext()) {
value = next.get(key);
if (null != value) {
cache.putIfAbsent(key, value.get());
}
}
return value;
}
@Override
public <T> T get(Object key, Class<T> type) {
return cache.get(key, type);
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return cache.get(key, valueLoader);
}
@Override
public void put(Object key, Object value) {
if (hasNext()) {
next.put(key, value);
}
cache.put(key, value);
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
return cache.putIfAbsent(key, value);
}
@Override
public void evict(Object key) {
if (hasNext()) {
next.evict(key);
}
cache.evict(key);
}
@Override
public void clear() {
if (hasNext()) {
next.clear();
}
cache.clear();
}
}
CacheSync相关:
@Bean(name = "redisCacheMessageSyncListenerContainer")
@ConditionalOnMissingBean(name = "redisCacheMessageSyncListenerContainer")
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, CacheSyncMessageListener receiver,
@Qualifier("syncCacheTaskExecutor") TaskExecutor executor) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setTaskExecutor(executor);
container.addMessageListener(receiver, new ChannelTopic(receiver.getChannelName()));
return container;
}
@Bean
@ConditionalOnMissingBean(CacheSyncManager.class)
public CacheSyncManager redisBasedCacheSyncServce(RedisTemplate redisTemplate) {
return new RedisCacheSyncManager(applicationName, redisTemplate);
}
@Bean
@ConditionalOnMissingBean(CacheSyncMessageListener.class)
public CacheSyncMessageListener cacheSyncMessageListener(CacheSyncManager cacheSyncManager, RedisTemplate redisTemplate) {
return new CacheSyncMessageListener(redisTemplate, cacheSyncManager);
}
/*
* 缓存同步处理接口定义
*/
public interface CacheSyncManager{
String SYNCCHANNEL = "cache-sync";
void publish(CacheSyncEvent event);
void handle(CacheSyncEvent event);
String getChannelName();
}
/*
* 抽象缓存同步类
*/
@Slf4j
public abstract class AbstractCacheSyncManager implements CacheSyncManager {
private static Map<String, CacheSyncEventHandler> handlerMap = new ConcurrentHashMap<>();
public static void registHandler(String name, CacheSyncEventHandler handler) {
handlerMap.put(name, handler);
}
protected String applicationName;
public AbstractCacheSyncManager(String appName) {
this.applicationName = appName;
}
public static void doHandle(CacheSyncEvent event) {
CacheSyncEventHandler handler = handlerMap.get(event.getCacheName());
if (null == handler) {
log.warn("不存在的缓存消息同步器:{}", event);
return;
}
//相关缓存事件
if (event instanceof PutEvent) {
handler.handlePut((PutEvent) event);
} else if (event instanceof EvictEvent) {
handler.handleEvict((EvictEvent) event);
} else if (event instanceof ClearEvent) {
handler.handleClear((ClearEvent) event);
} else {
log.warn("不支持的事件:{}", event);
}
}
@Override
public void handle(CacheSyncEvent event) {
doHandle(event);
}
@Override
public String getChannelName() {
return applicationName + ":" + SYNCCHANNEL;
}
}
/*
* 使用Redis的发布订阅来实现消息同步
*/
@Slf4j
public class RedisCacheSyncManager extends AbstractCacheSyncManager {
private RedisTemplate redisTemplate;
public RedisCacheSyncManager(String appName, RedisTemplate redisTemplate) {
super(appName);
this.redisTemplate = redisTemplate;
}
@Override
public void publish(CacheSyncEvent event) {
redisTemplate.convertAndSend(getChannelName(), event);
log.info("发送缓存同步消息: channel: {}, event: {}", getChannelName(), event);
}
}
/*
* 缓存事件定义
*/
public interface CacheSyncEventHandler {
/**
* 放入缓存事件
* @param event
*/
void handlePut(PutEvent event);
/**
* 清理缓存事件
* @param event
*/
void handleEvict(EvictEvent event);
/**
* 清除缓存事件
* @param event
*/
void handleClear(ClearEvent event);
}
/*
* 缓存消息监听器
*/
@Slf4j
public class CacheSyncMessageListener implements MessageListener {
private RedisTemplate redisTemplate;
private CacheSyncManager cacheSyncManager;
public CacheSyncMessageListener(RedisTemplate redisTemplate, CacheSyncManager cacheSyncManager) {
this.redisTemplate = redisTemplate;
this.cacheSyncManager = cacheSyncManager;
}
@Override
public void onMessage(Message message, byte[] pattern) {
log.debug("接收到缓存同步消息:{}", message);
try {
CacheSyncEvent event = (CacheSyncEvent) redisTemplate
.getValueSerializer().deserialize(message.getBody());
if (ObjectUtils.nullSafeEquals(HostUtil.getHostName(), event.getHost())) {
log.debug("该消息由本机发出,无须处理:{}", event);
return;
}
cacheSyncManager.handle(event);
} catch (Exception e) {
log.error("同步消息异常!", e);
}
}
public String getChannelName() {
return cacheSyncManager.getChannelName();
}
}
使用spring.factories机制来确保能被SpringBoot工程扫描到
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zy.github.multiple.cache.config.CacheManagerAutoConfiguration
以上就是关键部分的代码,总的来说关键部分就是:
- 1: 实现自己的CacheManager
- 2: 实现自己的Cache
- 3: 实现本地缓存之间的同步
如何使用
1:在启动类上加上@EnableCaching
@EnableCaching
@SpringBootApplication
public class MultipleCacheApplication {
public static void main(String[] args) {
SpringApplication.run(MultipleCacheApplication.class, args);
}
}
2:配置Cache的属性信息
spring:
redis:
port: # redis server port
host: # redis server host
lettuce:
pool:
max-active: 50
max-wait: 2000
max-idle: 20
min-idle: 5
# cluster:
# nodes:
# lettuce:
# pool:
# max-active: 50
# max-wait: 2000
# max-idle: 20
# min-idle: 5
application:
name: aaaaaaaaaa
multiple-cache:
# redis:
# - name: testCache #缓存名称
# expire: 100 #缓存过期时间
# caffeine:
# - name: testCache #缓存名称
# expireAfterAccess: 30 #缓存过期时间
# initialCapacity: 100 #缓存初始化存储大小
# maximumSize: 1000 #缓存最大存储大小
multiple:
- name: testCache #缓存名称
caffeine:
expireAfterAccess: 30 #缓存过期时间
initialCapacity: 100 #缓存初始化存储大小
maximumSize: 1000 #缓存最大存储大小
redis:
expire: 100 #缓存过期时间
3: 使用方式没有任何变化,还是基于注解的形式即可。
@RestController
public class DemoController {
@Autowired
private DemoService demoService;
@RequestMapping("cache-test")
public List<User> demo(){
return demoService.cacheTest("testId");
}
}
@Service
public class DemoService {
@Cacheable(cacheNames = "testCache", key = "#id")
public List<User> cacheTest(String id){
User user = new User();
user.setAge(22);
user.setName("xxx");
List<User> users = new ArrayList<>();
users.add(user);
return users;
}
}
4:效果
5:依赖相关
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.zy</groupId>
<artifactId>multiple-cache</artifactId>
<version>0.0.3</version>
<name>multiple-cache</name>
<description>多级缓存</description>
<properties>
<java.version>1.8</java.version>
<jackson.version>2.11.3</jackson.version>
<commons-pool2.version>2.9.0</commons-pool2.version>
<caffeine.version>2.8.5</caffeine.version>
<lettuce.version>6.0.1.RELEASE</lettuce.version>
<maven-plugins.version>3.2.0</maven-plugins.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons-pool2.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-plugins.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
<configuration>
<excludes>
<exclude>**/MultipleCacheApplication.java</exclude>
<exclude>**/application.yml</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-plugins.version}</version>
<configuration>
<excludes>
<exclude>**/application.yml</exclude>
<exclude>**/MultipleCacheApplication**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
传送门
最后附上 github链接