likes
comments
collection
share

实战之Spring Cache与Caffeine的整合优化高并发场景下的接口请求性能Spring Cache与Caffe

作者站长头像
站长
· 阅读数 23

引言

最近现场总是cpu飙高,公司的大佬们通力合作发现啊,有大量数据一次性下发(削峰处理,多线程处理),商品策略计算时,商品与货位的笛卡尔积过大(减少商品计算数量),因为项目历史原因,一次性计算需要2W多条不同sql计算,导致mybatis缓存过多(不要mybatis 缓存),还有就是分到我头上接口请求过多且参数一样结果也一样,故此大佬们希望缓存住相同参数获取到的结果,故此基于现有项目和希望,采取Spring Cache + Caffeine的整合。

概述

在现代的应用开发中,缓存是一项至关重要的技术,它能显著提高应用性能,减少数据库压力,提升用户体验。 Spring Framework 为Java开发者提供了一个强大的缓存抽象层——Spring Cache,它让缓存的使用变得简单而统一。 而Caffeine,作为一个高性能的Java缓存库,因其轻量级和快速的特性,被广泛应用于各种场景。 本文将详细介绍如何将Spring Cache与Caffeine整合,实现一个高效的缓存系统。

Spring Cache的简介

Spring Cache是Spring框架中的一部分,它提供了一个声明式的缓存抽象层,允许开发者通过简单的注解来控制方法的缓存行为。 常用的注解包括@Cacheable@CachePut@CacheEvict。 通过这些注解,开发者可以轻松地实现方法结果的缓存、更新以及缓存的清除。

Caffeine缓存库的特点

Caffeine是一个由Ben Manes开发的Java缓存库,它是Guava Cache的一个优秀替代品,特别设计用于提供高性能的本地缓存。Caffeine的主要特点有:

  • 高性能:Caffeine使用了高效的算法和数据结构,如窗口TinyLFU策略,以提供最佳的命中率。
  • 轻量级:Caffeine的API简洁,依赖少,易于集成。
  • 丰富的特性:支持大小限制、过期策略、引用回收等。

正文

在Spring框架中整合Caffeine缓存,通常需要以下几个步骤:

  1. 添加依赖
  2. 配置缓存管理器
  3. 使用缓存注解

配置pom

首先,在项目的pom.xml文件中添加了Caffeine相关的依赖。

<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
  <version>2.9.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.17</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.17</version>
</dependency>

配置类CacheConfig

然后,配置Spring的缓存管理器以使用Caffeine。在Spring配置类中添加以下配置:

@Configuration
@EnableCaching
public class StrategyCacheConfig {

    private CacheManager conditionalCacheManager(CaffeineCacheManager delegate) {
        if ("true".equalsIgnoreCase(SysConfigUtils.getValue("cache_conditional", "true"))) {
            return delegate;
        } else {
            return new NoOpCacheManager();
        }
    }


@Bean  
    public CacheManager strategyCacheManager() {  
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();  
        cacheManager.registerCustomCache("siteStrategyCache",  
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.HOURS).maximumSize(200).build());  
  
        cacheManager.registerCustomCache("operationPointCache",  
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.HOURS).maximumSize(200).build());  
  
        cacheManager.registerCustomCache("seedingWallSideCache",  
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.HOURS).maximumSize(200).build());  
  
        cacheManager.registerCustomCache("strategyWaveDetailControl",  
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.HOURS).maximumSize(200).build());  
  
        cacheManager.registerCustomCache("strategyElectronicLabel",  
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.HOURS).maximumSize(200).build());  
        disableCacheManagerDynamic(cacheManager);  
       // return conditionalCacheManager(cacheManager);  
        return cacheManager;  
    }
    
     @Bean
    @Primary
    public CacheManager baseManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.registerCustomCache("station",
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).maximumSize(200).build());
        return cacheManager;
    }
    
    /**  
     * 解决 CaffeineCacheManager.setDynamic(false) 效果,让 getCache("notExists")返回null  
     * 确保只有预定义的缓存名称可被使用,请求不存在的缓存会抛出异常  
     * 如果不设置,则如果使用了不存在的缓存名时,会自动创建新的缓存空间,没有大小和过期限制  
     *  
     * @param cacheManager CaffeineCacheManager  
     */  
    public static void disableCacheManagerDynamic(CaffeineCacheManager cacheManager) {  
        try {  
            Field field = cacheManager.getClass().getDeclaredField("dynamic");  
            field.setAccessible(true);  
            field.set(cacheManager, false);  
        } catch (Exception ignore) {  
            System.out.println("disableCacheManagerDynamic error:" + ignore.getMessage());  
        }  
    }
 }

因为项目是多模块开发,故此尽量多个模块使用一个CacheConfig 配置,如果实在不行可以根据不同包建立自己的CacheConfig,但是,整个项目中 @Primary 生成的bean 有且有仅有一个CacheManager。

故此我这边做了一个实验:

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.interceptor.KeyGenerator;

import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class ToteRCacheConfig {

    @Bean
    public CacheManager tRCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.registerCustomCache("rCForBoxCode",
                Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).maximumSize(200).build());

        //disableCacheManagerDynamic(cacheManager);

        return cacheManager;
    }

    @Bean("customKeyGenerator")
    public KeyGenerator keyGenerator() {
        return new CustomKeyGenerator();
    }
}

配置Caffeine和Redis混合

这边完一个稍微复杂的,因为有的地方想用Caffeine做缓存,有些地方使用Redis,故此也可以实现Caffeine和Redis混合配置。

具体代码实现如下:

import com.github.ben-manes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(1000));
        return cacheManager;
    }

    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(60)) // 设置缓存有效期一小时
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfiguration)
                .build();
    }
}

在这个配置类中,定义了caffeineCacheManagerredisCacheManager两个bean。caffeineCacheManager使用CaffeineCacheManager,它配置了Caffeine缓存的过期时间和最大大小。redisCacheManager使用RedisCacheManager,配置了Redis缓存的有效期和序列化机制。

现在,当需要在服务中使用缓存时,可以指定使用哪个CacheManager

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class DataService {

    // 使用基于Caffeine的缓存管理器
    @Cacheable(cacheNames = "localCache", cacheManager = "caffeineCacheManager", key = "#id")
    public MyData getDataFromLocalCache(Long id) {
        // 方法实现,比如从数据库加载数据
    }

    // 使用基于Redis的缓存管理器
    @Cacheable(cacheNames = "distributedCache", cacheManager = "redisCacheManager", key = "#id")
    public MyData getDataFromDistributedCache(Long id) {
        // 方法实现,比如从数据库加载数据
    }
}

在上面的服务类中,getDataFromLocalCache方法使用了基于Caffeine的缓存管理器,而getDataFromDistributedCache方法使用了基于Redis的缓存管理器。这样,就可以根据不同的业务需求选择不同的缓存策略。

具体使用

然后,使用Spring的缓存注解在服务层中添加缓存逻辑。例如:

@Slf4j
@Service
public class TReBasePersistenceServiceImpl implements TRBasePersistenceService {
    @Autowired
    TRBaseMapper tRBaseMapper;

    @Override
    @Cacheable(cacheManager = "tRCacheManager", cacheNames = "rCForBoxCode",keyGenerator = "customKeyGenerator", sync = true)
    public List<String> getRCForBoxCode(Integer deliverType, List<String> shelfCodes) {
        List<String> all = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(shelfCodes)) {
            List<List<String>> splitList = com.google.common.collect.Lists.partition(shelfCodes, 500);
            for (List<String> singleList : splitList) {
                all.addAll(tRBaseMapper.getRCForBoxCode(deliverType, singleList));
            }
            return all.stream().distinct().collect(Collectors.toList());
        }
        return all;
    }


    @Cacheable(cacheManager = "tRCacheManager", cacheNames = "rCForBoxCode",
            key = "T(com.***.***.***.tr.strategy.base.DigestUtils).generateKey(#deliverType, #shelfCodes)", sync = true)
    public List<String>getRCForBoxCode1(Integer deliverType, List<String> shelfCodes) {
        List<String> all = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(shelfCodes)) {
            List<List<String>> splitList = com.google.common.collect.Lists.partition(shelfCodes, 500);
            for (List<String> singleList : splitList) {
                all.addAll(tRBaseMapper.getRCForBoxCode(deliverType, singleList));
            }
            return all.stream().distinct().collect(Collectors.toList());
        }
        return all;
    }
}

在上述代码中,其实写了两种key 的生成规则。简单的key就不过多介绍网上查查就有,这边直接进入生成key的详细论述。

因为项目中不可能都是那种简单参数如就一个字符串,key = #aaa ,这边会出集合啊,对象等大对象,如果直接拿他们做key,则缓存中的key 就会很大,拿他们做缓存就没有啥意义。故此需要计算出小的且唯一值进行存储。

这边采取了两种方式,

解决key值过大之-使用keyGenerator

要使用keyGenerator生成缓存键,因为shelfCodes列表非常大,将整个列表内容作为缓存键的一部分可能会导致生成的键非常长,这可能会影响性能并增加内存消耗。为了避免这个问题,可以考虑使用列表的哈希值来代替列表内容本身。这样可以确保缓存键的长度保持恒定,同时仍然基于shelfCodes列表的内容生成唯一的键。下面是一个优化后的KeyGenerator实现,它使用deliverTypeshelfCodes列表的哈希值来生成缓存键

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;

@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        Integer deliverType =(Integer) params[0];
        @SuppressWarnings("unchecked")
        List<String> shelfCodes = (List<String>) params[1];

        // 如果shelfCodes为空或者为空列表,使用一个特定的字符串代替
        String shelfCodesKeyPart = (shelfCodes == null || shelfCodes.isEmpty())
                ? "emptyList"
                : String.valueOf(shelfCodes.hashCode());

        // 生成缓存键
        String key = "DeliverType_" + deliverType + "_ShelfCodes_" + shelfCodesKeyPart;
        return key;
    }
}

可以在@Cacheable注解中使用keyGenerator属性引用自定义KeyGenerator

import org.springframework.cache.annotation.Cacheable;  
  
public class TReBasePersistenceServiceImpl implements TRBasePersistenceService {  
  
    @Cacheable(cacheManager = "tRCacheManager", cacheNames = "rCForBoxCode",keyGenerator = "customKeyGenerator", sync = true)
    public List<String> getRCForBoxCode(Integer deliverType, List<String> shelfCodes) {
        // 方法实现  
    }  
}  

这样 使用了keyGenerator = "customKeyGenerator"来告诉Spring使用我们自定义的KeyGenerator生成缓存键。这样,每次调用getRCForBoxCode方法时,都会根据deliverTypeshelfCodes的值生成一个唯一的缓存键。

生成的缓存键应该是唯一的,以确保不同的参数组合不会导致缓存键冲突。此外,如果shelfCodes列表非常大,生成的键可能会非常长,这可能不是一个好的做法,因为它会增加内存使用,并且可能会影响缓存存储的性能。在这种情况下,可能需要考虑使用更复杂的键生成策略

解决key值过大之-使用参数摘要

对于大型对象或多个参数,可以考虑使用摘要算法(如MD5、SHA-256)来生成一个固定长度的缓存键

参数摘要方案是指使用某种摘要算法(如MD5、SHA-1、SHA-256等)来生成一个固定长度的字符串,作为缓存键。这种方法特别适用于方法参数较多或参数对象较大的情况,因为它可以将复杂的参数组合转换为一个简洁的标识符。以下是如何实现参数摘要方案的步骤和代码示例:

步骤1:创建工具类方法生成摘要键
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class DigestUtils {

    // 使用SHA-256摘要算法生成缓存键
    public static String generateKey(Object... params) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            for (Object param : params) {
                // 检查参数是否为空
                if (param != null) {
                    if (param instanceof Collection<?>) {
                        // 对于列表,计算列表元素的组合哈希值
                        Collection<?> listParam = (Collection<?>) param;
                        int listHash = listParam.hashCode();
                        digest.update(Integer.toString(listHash).getBytes(StandardCharsets.UTF_8));
                    } else {
                        // 将参数转换为字符串表示(这里需要自定义转换逻辑,例如调用toString或使用JSON序列化)
                        String paramString = String.valueOf(param);
                        // 更新摘要信息
                        digest.update(paramString.getBytes(StandardCharsets.UTF_8));
                    }
                } else {
                    // 对于null参数,使用一个特定的字符串代替,例如"null"
                    digest.update("null".getBytes(StandardCharsets.UTF_8));
                }
            }
            // 生成摘要字节
            byte[] hash = digest.digest();
            // 将字节转换为十六进制字符串
            return bytesToHex(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Could not generate hash", e);
        }
    }

    // 将字节转换为十六进制字符串
    private static String bytesToHex(byte[] hash) {
        StringBuilder hexString = new StringBuilder(2 * hash.length);
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }


    public static void main(String[] args) {
        List<String> c1 = new ArrayList<>();
        List<String> c3 = new ArrayList<>();
        List<String> c4 = new ArrayList<>();
        for (int i = 0; i <= 9000; i++) {
            c1.add(String.valueOf(i));
            c3.add(String.valueOf(i));
        }

        for (int i = 9000; i  >=0; i--) {
            c4.add(String.valueOf(i));
        }
        Long s = System.currentTimeMillis();
        System.out.println("**************" + generateKey("123", 321321L, 321321333, c1));
        System.out.println("**************" + (System.currentTimeMillis() - s));




        System.out.println("**************" + generateKey("123", 321321L, 321321333, c3));

        c4.sort(Comparator.comparingInt(Integer::parseInt));

        System.out.println("**************" + generateKey("123", 321321L, 321321333, c4));

        System.out.println("**************"+ c1.size()+"**************"+ c4.size());

        List<String> c2 = null;
        System.out.println("**************" + generateKey("123", 321321L, 321321333, c2));
    }

    static class C {
        private String name;
        private String age;

        public C(String name, String age) {
            this.name = name;
            this.age = age;
        }
    }
}
步骤2:在@Cacheable注解中使用工具类方法
@Cacheable(cacheManager = "tRCacheManager", cacheNames = "rCForBoxCode",
        key = "T(com.***.base.DigestUtils).generateKey(#deliverType, #shelfCodes)", sync = true)
public List<String> getRCForBoxCode1(Integer deliverType, List<String> shelfCodes) {
//代码实现
  }

在这个示例中,定义了一个DigestUtils工具类,它包含一个generateKey方法,该方法使用SHA-256算法来生成参数的摘要。在@Cacheable注解的key属性中,我们使用了Spring Expression Language(SpEL)来调用DigestUtils.generateKey方法,并传入方法参数来生成缓存键。

这种方法的优点是无论参数有多复杂或多大,生成的缓存键都是一个固定长度的字符串,这有助于保持缓存的一致性和性能。

请注意,在生成参数字符串表示时,需要确保参数的转换是一致的,以便相同的参数组合总是产生相同的缓存键。在本例中,我们使用了String.valueOf(param),但在实际应用中,可能需要更复杂的转换逻辑,例如使用JSON序列化器将对象转换为JSON字符串,以确保对象的每个属性都被考虑在内。

遇到超坑问题-跨JVM

在开发测试中发现,明明调用CacheEvict

@CacheEvict(cacheManager = "strategyCacheManager", cacheNames = "siteStrategyCache", allEntries = true)

摸索解决问题

进行删除缓存,但是就删除不掉.后来自我实现一套了删除缓存的策略如下:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @author Derek_Smart
 * @company 
 * @project 
 * @date 2024/8/20 7:42
 * @since 1.0.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomCacheEvict {
    String[] cacheNames();
    String key() default "";
    String cacheManager() default "";
    //String keyGenerator() default "";
    String condition() default "";
    boolean allEntries() default false;
}

和切面


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author derek_smart
 * @company 
 * @project 
 * @date 2024/8/20 8:32
 * @since 1.0.0
 */
@Aspect
@Component
public class CustomCacheEvictAspect implements ApplicationContextAware {
    @Autowired
    private CacheManager cacheManager; // 默认的缓存管理器
    private ApplicationContext applicationContext;
    private ExpressionParser expressionParser = new SpelExpressionParser();
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    // 省略其他代码...

    @Around("@annotation(customCacheEvict)")
    public Object cacheEvictAdvice(ProceedingJoinPoint joinPoint, CustomCacheEvict customCacheEvict) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        Object target = joinPoint.getTarget();

        // 创建评估上下文,使用 DefaultParameterNameDiscoverer
        MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
                target, method, args, parameterNameDiscoverer);

        // 计算 condition 表达式的值
        boolean conditionPasses = true;
        if (!customCacheEvict.condition().isEmpty()) {
            Expression expression = expressionParser.parseExpression(customCacheEvict.condition());
            conditionPasses = expression.getValue(evaluationContext, Boolean.class);
        }

        // 执行目标方法
        Object result = joinPoint.proceed();

        // 如果 condition 表达式为 true,则清除缓存
        if (conditionPasses) {
            for (String cacheName : customCacheEvict.cacheNames()) {
                CacheManager managerToUse = this.cacheManager;
                if (!customCacheEvict.cacheManager().isEmpty()) {
                    // 如果注解中指定了缓存管理器,则获取相应的缓存管理器
                    managerToUse = getCacheManagerByName(customCacheEvict.cacheManager());
                }
                if (customCacheEvict.allEntries()) {
                    // 清除指定缓存中的所有条目
                    managerToUse.getCache(cacheName).clear();
                } else {
                    // 如果指定了 keyGenerator,则生成 key 并清除对应条目
                    // 需要实现 keyGenerator 相关逻辑
                    managerToUse.getCache(cacheName).evict(customCacheEvict.key());
                }
            }
        }

        return result;
    }

    private CacheManager getCacheManagerByName(String cacheManagerName) {
        // 从应用上下文中按名称获取缓存管理器
        return ApplicationContextUtils.loadBean(cacheManagerName);
      //  return applicationContext.getBean(cacheManagerName, CacheManager.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}

然后再调用方法上实验

@CustomCacheEvict(cacheManager = "strategyCacheManager", cacheNames = "siteStrategyCache", allEntries = true)

一样删除不掉,但是通过自己写的切面 可以很清除的看到strategyCacheManager对象中的cache 就是为空,无论如何去删就是删除不掉。然后通过

log.info("地址:Service instance: " + System.identityHashCode(siteStrategyService));

打印整个虚拟机生命周期内hashCode,发现 生成缓存的地方和删除缓存地方 居然不一样。突然想到我们项目部署是两个war,生成和删除刚好分别在两个war 中,故此其对应spring 上下文不一样,获取的本地缓存也不一样。

解决方案

故此我这边采取新的方式进行解决多war下缓存不一致,解决CacheEvict失效问题:创建一个可以跨 WAR 文件共享的缓存机制,需要使用一个外部的缓存服务器,这样不同的 Spring 上下文可以访问同一个缓存实例。下面是一个使用 Redis 作为外部缓存服务器的示例。Redis 是一个开源的、支持网络、基于内存、键值存储数据库,它具有高性能和灵活性,非常适合用作分布式缓存解决方案 实现代码如下:

public class StrategyCacheConfig {

    @Bean
    public CacheManager strategyCacheManager(RedissonClient redissonClient) {
        // 创建缓存名称到缓存配置的映射
        // 创建缓存名称到缓存配置的映射
        Map<String, CacheConfig> configMap = new HashMap<>();
        configMap.put("siteStrategyCache", new CacheConfig(TimeUnit.SECONDS.toMillis(30), 0)); // 30小时
        configMap.put("operationPointCache", new CacheConfig(TimeUnit.HOURS.toMillis(30), 0)); // 30天
        configMap.put("seedingWallSideCache", new CacheConfig(TimeUnit.HOURS.toMillis(30), 0)); // 30秒
        configMap.put("strategyWaveDetailControl", new CacheConfig(TimeUnit.HOURS.toMillis(30), 0)); // 30秒
        configMap.put("strategyElectronicLabel", new CacheConfig(TimeUnit.HOURS.toMillis(30), 0)); // 30秒

        // 创建 RedissonSpringCacheManager 并返回
        return new RedissonSpringCacheManager(redissonClient, configMap);
    }

这样现在,RedisCacheManager 应该已经配置好了,并且可以跨 WAR 文件共享缓存。

当然在本项目中,依赖redis地方太多,且数据量过大,I/O耗时,从redis 获取数据和从数据库获取数据有些地方时间相差不大,故此该方案只是目前可选方案之一。

总结

通过整合Spring Cache和Caffeine,能够以最小的开销实现一个功能强大而高效的缓存系统。Spring Cache的抽象层让缓存的使用变得简单,而Caffeine的高性能确保了我们的应用能够快速响应用户的请求。这种组合为构建可伸缩的Java应用提供了坚实的基础,无论是在云环境还是传统的部署环境中。

缓存是现代应用架构中不可或缺的一部分,而Spring Cache与Caffeine的整合使得缓存的实现和维护变得既简单又高效。通过这篇文章的介绍,希望能够在自己的项目中成功应用这些技术,为用户带来更快、更稳定的服务体验。

本文皆为个人原创,请尊重创作,未经许可不得转载。

转载自:https://juejin.cn/post/7410654079842697225
评论
请登录