搭建大型分布式服务(二十三)SpringBoot 如何整合比GuavaCache性能好n倍的Caffeine并根据名称设置不同的失效时间?

23 篇文章 1 订阅

系列文章目录



前言

在日常开发当中,为了提高接口性能,本地缓存必不可少,因为在JVM进程内读取数据肯定比Redis、Memcached快,它没有网络消耗,所以你还在用Guava cache吗?SpringBoot2.0后默认就使用Caffeine作为本地缓存,用起来更加丝滑。网上讲解SringBoot整合Caffeine的教程比较多,但很少结合业务实战的。本文将从业务角度出发,比如A、B接口需要配置不同的缓存失效时间,那该怎样去配置Caffeine呢?


一、本文要点

接前文,我们已经在项目里集成了redis。本文将介绍如何优雅地整合Caffeine,解决业务问题。
系列文章完整目录

  • SpringBoot如何优雅地整合Caffeine
  • SpringBoot打印Caffeine缓存置入置出日志
  • Caffeine根据不同缓存设置不同的失效时间
  • 根据Key删除Caffeine缓存
  • Caffeine缓存配置隔离

二、开发环境

  • jdk 1.8
  • maven 3.6.2
  • springboot 2.4.3
  • Caffeine 2.8.8
  • idea 2020

三、创建项目

1、使用早期文章快速创建项目。

(https://blog.csdn.net/hanyi_/article/details/115050732)
《搭建大型分布式服务(十八)Maven自定义项目脚手架》

2、创建Caffeine项目

mvn archetype:generate  -DgroupId="com.mmc.lesson" -DartifactId="caffeine" -Dversion=1.0-SNAPSHOT -Dpackage="com.mmc.lesson.caffeine" -DarchetypeArtifactId=member-archetype  -DarchetypeGroupId=com.mmc.lesson -DarchetypeVersion=1.0.0-SNAPSHOT -B

四、修改项目

1、编写CacheProperties.java为,用来接收多个缓存配置。

@Component("cacheConfig")
@ConfigurationProperties(prefix = "spring.mmc")
@Data
public class CacheProperties {
    /**
     * 根据不同名称配置缓存失效时间和容量.
     */
    private Map<String, Config> caches = new HashMap<>();

    /**
     * 配置.
     */
    @Data
    public static class Config {

        /**
         * 是否启用.
         */
        private boolean enabled;

        /**
         * 过期时间(单位秒).
         */
        private int expire = 300;

        /**
         * 初始容量.
         */
        private int initialCapacity = 100;

        /**
         * 最大容量.
         */
        private int maximumSize = 1000;
    }

}

2、编写CacheConfiguration.java,用来定义Caffeine,原脚手架中RedisConfiguration.java可以删掉或者整合到CacheConfiguration.java。

@Slf4j
@Configuration
@EnableCaching
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class CacheConfiguration extends CachingConfigurerSupport {

    @Resource
    private CacheProperties cacheProperties;

    private Jackson2JsonRedisSerializer<?> createJacksonRedisSerializer() {
        Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }

    @Bean("doeRedisTemplate")
    public RedisTemplate<?, ?> getRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<?, ?> template = new StringRedisTemplate(connectionFactory);
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        template.setKeySerializer(keySerializer);
        template.setValueSerializer(createJacksonRedisSerializer());
        template.setHashKeySerializer(keySerializer);
        template.setHashValueSerializer(createJacksonRedisSerializer());
        return template;
    }

//	  方式一:直接定义Caffeine的Bean,项目里直接使用,这里我们使用CacheManager,所以注释掉这种方式
//    @Bean
//    public Caffeine<Object, Object> caffeineConfig() {
//        return Caffeine.newBuilder()
//                .expireAfterWrite(cacheConfig.getExpire(), TimeUnit.MINUTES)
//                .initialCapacity(cacheConfig.getInitialCapacity())
//                .recordStats()
//                .maximumSize(cacheConfig.getMaximumSize());
//    }

    @Bean("caffeineCacheManager")
    public CacheManager cacheManager() {
        CaffeineCacheManager caffeineCacheManager = new CustomCaffeineCacheManager(cacheProperties);
        // caffeineCacheManager.setCaffeine(caffeine);
        caffeineCacheManager.setAllowNullValues(true); // 空值缓存
        return caffeineCacheManager;
    }

    /**
     * keyGenerator.
     */
    @Bean("keyGenerator")
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            String[] value = new String[1];
            Cacheable cacheable = method.getAnnotation(Cacheable.class);
            if (cacheable != null) {
                value = cacheable.value();
            }
            CachePut cachePut = method.getAnnotation(CachePut.class);
            if (cachePut != null) {
                value = cachePut.value();
            }
            CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
            if (cacheEvict != null) {
                value = cacheEvict.value();
            }
            StringBuilder sb = new StringBuilder();
            sb.append(value[0]);
            for (Object obj : params) {
                sb.append(":")
                        .append(MD5Util.encrypt(com.mmc.lesson.util.GsonUtil.toJson(obj)));
            }
            log.info("keyGenerator:{}", sb.toString());
            return sb.toString();
        };
    }

    /**
     * fixKeyGenerator.
     */
    @Bean("fixKeyGenerator")
    public KeyGenerator fixKeyGenerator() {
        return (target, method, params) -> {
            String[] value = new String[1];
            Cacheable cacheable = method.getAnnotation(Cacheable.class);
            if (cacheable != null) {
                value = cacheable.value();
            }
            CachePut cachePut = method.getAnnotation(CachePut.class);
            if (cachePut != null) {
                value = cachePut.value();
            }
            CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
            if (cacheEvict != null) {
                value = cacheEvict.value();
            }
            return value[0] + "";
        };
    }
}

3、编写CustomCaffeineCacheManager.java,方便我们通过不同的配置来生成自定义CaffeineCache。编写CustomCaffeineCache.java,主要用来打印缓存置入置出日志,当然可以增加更多自定义操作。

public class CustomCaffeineCacheManager extends CaffeineCacheManager {

    private CacheProperties cacheProperties;

    /**
     * init.
     */
    public CustomCaffeineCacheManager(CacheProperties cacheProperties) {

        this.cacheProperties = cacheProperties;
    }

    /**
     * Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
     * abstraction for the specified cache name.
     *
     * @param name the name of the cache
     * @param cache the native Caffeine Cache instance
     * @return the Spring CaffeineCache adapter (or a decorator thereof)
     * @see CustomCaffeineCache
     * @see #isAllowNullValues()
     * @since 5.2.8
     */
    @Override
    protected Cache adaptCaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache) {

        return new CustomCaffeineCache(cacheProperties, name, cache, isAllowNullValues());

    }

    /**
     * Build a common Caffeine Cache instance for the specified cache name,
     * using the common Caffeine configuration specified on this cache manager.
     *
     * @param name the name of the cache
     * @return the native Caffeine Cache instance
     * @see #createCaffeineCache
     */
    @Override
    protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) {

        Config cfg = cacheProperties.getCaches().get(name);

        return buildCache(cfg).build();
    }

    /**
     * 根据自定义配置生成缓存.
     */
    private Caffeine<Object, Object> buildCache(Config config) {
        return Caffeine.newBuilder()
                .expireAfterWrite(config.getExpire(), TimeUnit.SECONDS)
                .initialCapacity(config.getInitialCapacity())
                .recordStats()
                .maximumSize(config.getMaximumSize());
    }

}

@Slf4j
public class CustomCaffeineCache extends CaffeineCache {

    private Config cacheProperties;
    private String name;

    /**
     * Create a {@link CaffeineCache} instance with the specified name and the
     * given internal {@link Cache} to use.
     *
     * @param name the name of the cache
     * @param cache the backing Caffeine Cache instance
     */
    public CustomCaffeineCache(String name, Cache<Object, Object> cache) {
        super(name, cache);
    }

    /**
     * Create a {@link CaffeineCache} instance with the specified name and the
     * given internal {@link Cache} to use.
     *
     * @param name the name of the cache
     * @param cache the backing Caffeine Cache instance
     * @param allowNullValues whether to accept and convert {@code null}
     */
    public CustomCaffeineCache(CacheProperties cacheProperties, String name, Cache<Object, Object> cache,
            boolean allowNullValues) {
        super(name, cache, allowNullValues);
        this.name = name;
        this.cacheProperties = cacheProperties.getCaches().get(name);
        log.info("CustomCaffeineCache {} cacheConfig: {}", name, com.mmc.lesson.util.GsonUtil.toJson(cacheProperties));
    }

    @Override
    public ValueWrapper get(Object key) {

        if (!cacheProperties.isEnabled()) {
            return null;
        }
        ValueWrapper data = super.get(key);
        show(key, data);
        return data;
    }

    @Override
    public <T> T get(Object key, Class<T> type) {

        if (!cacheProperties.isEnabled()) {
            return null;
        }
        T data = super.get(key, type);
        show(key, data);
        return data;
    }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {

        if (!cacheProperties.isEnabled()) {
            return null;
        }
        T data = super.get(key, valueLoader);
        show(key, data);
        return data;
    }

    private void show(Object key, Object value) {

        log.info("CustomCaffeineCache|get|{}|{}|{}", name, key, null == value ? "miss" : "hit");

    }

    @Override
    public void put(Object key, Object value) {
        if (!cacheProperties.isEnabled()) {
            return;
        }
        log.info("CustomCaffeineCache|put|{}|{}", name, key);
        super.put(key, value);
    }
}

4、编写BookService.java、StoreService.java,用来模拟先查缓存、缓存没有查DB的常规业务,这里简单用一行日志证明缓存失效。

@Slf4j
@Service
public class BookService {
	// 这里的cachNames需要跟application-dev.properties中spring.mmc.caches配置保持一致
    @Cacheable(cacheNames = "book", key = "#id")
    public void get(int id) {

        log.info("load book from db.");
    }
}

@Slf4j
@Service
public class StoreService {
	// 这里的cachNames需要跟application-dev.properties中spring.mmc.caches配置保持一致
    @Cacheable(cacheNames = "store", key = "#id")
    public void get(int id) {

        log.info("load store from db.");
    }
}

4、修改application-dev.properties 增加book
、store接口本地缓存配置,其它环境同理(可以放到Apollo托管)。

## 为了验证缓存配置隔离,这里book接口缓存5s
spring.mmc.caches.book.enabled=true
spring.mmc.caches.book.expire=5
spring.mmc.caches.book.initial-capacity=100
spring.mmc.caches.book.maximum-size=1000
## 为了验证缓存配置隔离,这里store接口缓存10s
spring.mmc.caches.store.enabled=true
spring.mmc.caches.store.expire=10
spring.mmc.caches.store.initial-capacity=100
spring.mmc.caches.store.maximum-size=1000

五、测试一下

1、修改并运行单元测试

@Slf4j
@ActiveProfiles("dev")
@ExtendWith(SpringExtension.class)
@SpringBootTest
class SuiteTest {

    @Resource
    private BookService bookService;

    @Resource
    private StoreService storeService;

    @Test
    void testGet() throws InterruptedException {

        // book 失效时间5s
        // store 失效时间10s
        // 20秒,预计从db加载book 4次,加载store 2次
        for (int i = 0; i < 20; i++) {

            bookService.get(888);
            storeService.get(888);
            Thread.sleep(1000);
        }
    }

}

2、运行20s,book接口打印了四次、store接口打印了2次,测试通过。

[2021-12-25 20:55:40.129] [main] [INFO] [com.mmc.lesson.caffeine.cache.CustomCaffeineCache:?] - CustomCaffeineCache book cacheConfig: {"caches":{"book":{"enabled":true,"expire":5,"initialCapacity":100,"maximumSize":1000},"store":{"enabled":true,"expire":10,"initialCapacity":100,"maximumSize":1000}}}
[2021-12-25 20:55:40.163] [main] [INFO] [com.mmc.lesson.caffeine.service.BookService:?] - load book from db.
[2021-12-25 20:55:40.165] [main] [INFO] [com.mmc.lesson.caffeine.cache.CustomCaffeineCache:?] - CustomCaffeineCache store cacheConfig: {"caches":{"book":{"enabled":true,"expire":5,"initialCapacity":100,"maximumSize":1000},"store":{"enabled":true,"expire":10,"initialCapacity":100,"maximumSize":1000}}}
[2021-12-25 20:55:40.169] [main] [INFO] [com.mmc.lesson.caffeine.service.StoreService:?] - load store from db.
[2021-12-25 20:55:45.194] [main] [INFO] [com.mmc.lesson.caffeine.service.BookService:?] - load book from db.
[2021-12-25 20:55:50.209] [main] [INFO] [com.mmc.lesson.caffeine.service.BookService:?] - load book from db.
[2021-12-25 20:55:50.210] [main] [INFO] [com.mmc.lesson.caffeine.service.StoreService:?] - load store from db.
[2021-12-25 20:55:55.224] [main] [INFO] [com.mmc.lesson.caffeine.service.BookService:?] - load book from db.


六、小结

至此,我们就优雅地整合Caffeine并使用不同的缓存配置,小伙伴们可以发挥自己的动手能力,根据key动态删除Caffeine缓存哦。下一篇《搭建大型分布式服务(二十四)如何创建一个TKE容器集群?

加我加群一起交流学习!更多干货下载、项目源码和大厂内推等着你

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Boot可以很方便地整合Caffeine缓存Caffeine是一个高性能Java缓存库,可以用于减少应用程序的响应时间和提高性能。在Spring Boot中,可以使用Spring Cache抽象来集成Caffeine缓存。只需在pom.xml文件中添加Caffeine依赖项,然后在应用程序中配置缓存管理器即可。可以使用@Cacheable注释来标记需要缓存的方法,并在需要时自动从缓存中获取数据。此外,还可以使用@CachePut注释来更新缓存中的数据,以及@CacheEvict注释来删除缓存中的数据。整合Caffeine缓存可以显著提高应用程序的性能和响应时间,特别是在处理大量数据时。 ### 回答2: Spring Boot是目前非常流行的基于Java的开源框架,主要用于在Web应用程序中创建应用程序,而Caffeine是一种基于Java的本地缓存框架,可以有效地提高应用程序的性能。在本文中,我们将讨论如何使用Spring Boot将Caffeine整合到我们的应用程序中。 Caffeine是一个类似于Guava缓存的本地缓存框架,但它在性能和代码大小方面更优。Caffeine使用基于内存的缓存,其中对象在缓存中以键值对的形式存储。当需要缓存对象时,应用程序将其存储在缓存中。下次应用程序需要访问缓存对象时,Caffeine将在缓存中查找该对象,而无需重新计算或访问磁盘。 使用Spring Boot整合Caffeine,我们需要执行以下步骤: 1.添加Caffeine依赖 我们需要在应用程序中添加Caffeine Maven或Gradle依赖项。以下是Maven依赖项的示例: ``` <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.8.5</version> </dependency> ``` 2.创建Caffeine缓存 在Spring Boot应用程序中创建Caffeine缓存是非常简单的。我们可以使用Caffeine类来创建缓存,并指定缓存的属性和采用的策略。以下是创建基本缓存的示例: ``` import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("cacheName"); } } ``` 在上面的示例中,我们创建了一个名为“cacheName”的缓存。我们还启用了Spring Boot的缓存支持@EnableCaching。 3.将Caffeine缓存用于方法 现在,我们已经创建了一个Caffeine缓存,可以在我们的应用程序中使用它。我们可以使用Spring Boot注释将缓存添加到我们的方法中,以便它们能够使用缓存数据而不是重复计算。以下是使用Caffeine缓存的方法示例: ``` @Cacheable(value = "cacheName", key = "#key") public Object getObject(String key) { // Some operations to fetch the object } ``` 在上面的示例中,我们使用@Cacheable注释将getObject()方法添加到缓存中。我们还指定了“cacheName”作为缓存名称。 综上所述,Spring Boot的Caffeine整合是一种简单但有效的缓存解决方案。我们可以使用它轻松地将本地缓存添加到我们的应用程序中,以提高应用程序的性能和效率。 ### 回答3: Spring Boot是目前最为流行的Java Web开发框架之一,而Caffeine则是一种高效的Java缓存库,它可以将缓存对象存储在本地内存中,从而提高应用程序的性能。 如果将Spring Boot和Caffeine进行整合,就能够进一步提高应用程序的性能和效率。下面是整合的详细步骤: 第一步:添加Maven依赖 在Spring Boot应用程序的pom.xml文件中添加以下依赖: ``` <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>${caffeine.version}</version> </dependency> ``` 注意,需要将${caffeine.version}替换成实际的Caffeine版本号。 第二步:创建Caffeine缓存配置类 创建一个带有@Configuration注解的Java类,用于定义Caffeine缓存的配置和属性。 ``` @Configuration @EnableCaching public class CaffeineConfig { @Bean public Caffeine<Object, Object> caffeineConfig() { return Caffeine.newBuilder() .maximumSize(100) .expireAfterAccess(10, TimeUnit.MINUTES); } @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(caffeineConfig()); return cacheManager; } } ``` 在这个类中,我们使用了@EnableCaching注解来启用Spring的缓存支持。在@Bean注解下,我们定义了基本的Caffeine缓存配置。这里我们设置了最大缓存数为100个,缓存失效时间为10分钟。接着,我们使用@Bean注解创建一个缓存管理器实例,并将Caffeine配置传递给它。 第三步:定义目标缓存方法,并在方法上添加缓存注解 定义Spring应用程序的缓存逻辑方法,并在该方法上添加@Cacheable注解。这个注解可以用来告诉Spring将结果存储在缓存中,以便后续请求可以更快地检索结果。 ``` @Service public class UserServiceImpl implements UserService { @Override @Cacheable(value = "users", key = "#id") public User getUserById(Long id){ //从数据库获取用户 return userDao.getUserById(id); } } ``` 在这个示例中,通过@Cacheable注解,getUserById()方法的结果将被存储在名为users的缓存中。缓存的键值是方法的输入参数id。 总结 Spring Boot整合Caffeine非常简单。只需添加Maven依赖、创建Caffeine缓存配置类和添加缓存注解,就可以在Spring应用程序中实现高效的缓存处理。这种缓存处理可以非常好地提高应用程序的性能和效率,从而更好地满足用户需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值