Spring Cache(二) - 自定义两级缓存(Caffeine+Redis)

4,306 阅读4分钟

引言

上一篇文章Cache在Springboot中的实现与原理已经介绍了Spring Cache在SpringBoot中的实现与原理, 本文就来聊一聊如何在使用spring cache的时候结合本地缓存 + redis. 也就是自定义两级缓存.

1: 自定义缓存的原理

上一篇文章中已经提到, Cache接口定义了缓存操作的行为,CacheManager定义了如何产生Cache,我们需要定义自己的两级Cache,所以我们就需要定义自己的CacheCacheManager。在Spring已经帮我们提供了一个CacheManager的实现类CompositeCacheManager

2:如何实现

设计与实现思路

  1. 这里我们采用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链接