Spring boot caffeine cache 配置简述

简述

项目使用 Spring boot 开发,需要使用缓存功能减少数据库压力,主要用来缓存一些使用频率高,更新频率低的配置数据。选用官方推荐的一种实现—— Caffeine 。

引入依赖

<properties>
    <spring.boot.version>1.5.6.RELEASEspring.boot.version>
properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-dependenciesartifactId>
            <version>${spring.boot.version}version>
            <type>pomtype>
            <scope>importscope>
        dependency>
    dependencies>
dependencyManagement>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeinegroupId>
    <artifactId>caffeineartifactId>
dependency>

默认 KeyGenerator 配置

spring 提供的默认实现为 org.springframework.cache.interceptor.SimpleKeyGenerator ,仅使用方法的参数生成 key。参数若为自定义的查询对象 ,注意重写 hashCode 和 equals() 方法。

自定义 KeyGenerator 配置

此处实现一个简单的 KeyGenerator,参数若为自定义的查询对象,注意重写 toString() 方法。

实现

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.StringJoiner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;

public class CustomKeyGenerator implements KeyGenerator {

    private static final Logger logger = LoggerFactory.getLogger(CustomKeyGenerator.class);

    @Override
    public Object generate(Object target, Method method, Object... params) {

        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String paramHash = String.valueOf(Arrays.toString(params).hashCode());
        String cacheKey = new StringJoiner("_").add(className).add(methodName)
                .add(paramHash).toString();
        logger.debug("generate cache key : {}", cacheKey);

        return cacheKey;
    }
}

bean

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;

import cn.migu.pk.constant.CommonConstants;

@Configuration
public class CaffeineCacheConfig {

    private static final Logger logger = LoggerFactory.getLogger(CaffeineCacheConfig.class);

    @Bean(CacheConstants.CUSTOM_KEY_GENERATOR)
    public KeyGenerator createMapperKeyGenerator() {
        return new CustomKeyGenerator();
    }
}

默认 CacheManager 配置

spring-boot-starter-cache 包中提供了默认的 CacheManager 实现 CaffeineCacheManager,主要缺点是所有缓存都使用相同的配置。

properties 配置文件

spring.cache.cache-names=cfgDataCache
spring.cache.caffeine.spec=maximumSize=500,refreshAfterWrite=120s,expireAfterWrite=600s

java 配置类


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.benmanes.caffeine.cache.CacheLoader;

@Configuration
public class CaffeineCacheConfig {

    private static final Logger logger = LoggerFactory.getLogger(CaffeineCacheConfig.class);

    @Bean
    public CacheLoader<Object, Object> createCacheLoader() {
        return new CacheLoader<Object, Object>() {
            @Override
            public Object load(Object key) throws Exception {
                logger.debug("cacheLoader load : {}", key);
                return null;
            }
        };
    }

    @Bean(CacheConstants.CUSTOM_KEY_GENERATOR)
    public KeyGenerator createMapperKeyGenerator() {
        return new CustomKeyGenerator();
    }
}

因为使用了 refreshAfterWrite 策略,必须要定义一个 com.github.benmanes.caffeine.cache.CacheLoader bean,其会自动关联到 CaffeineCacheManager 中的 cacheLoader ( CacheLoader)成员变量。

常量类

public final class CacheConstants {

    /*
     * 缓存名称
     */
    public static final String CFG_DATA_CACHE = "cfgDataCache";
    public static final String BIZ_DATA_CACHE = "bizDataCache";

    /*
     * KeyGenerator名称
     */
    public static final String CUSTOM_KEY_GENERATOR = "customKeyGenerator";
}

自定义 CacheManager 配置

使用 SimpleCacheManager 实现不同 Cache 使用不同的配置。

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;

@Configuration
public class CacheManagerConfig {

    private static final Logger logger = LoggerFactory.getLogger(CacheManagerConfig.class);

    @Bean
    public CacheManager createCacheManager() {

        logger.info("cache manager initialize ...");

        LoadingCache<Object, Object> cfgDataCache = Caffeine.newBuilder()
                .maximumSize(100)
                .refreshAfterWrite(5, TimeUnit.MINUTES)
                .build(key -> load(key));

        LoadingCache<Object, Object> bizDataCache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(1, TimeUnit.HOURS)
                .build(key -> load(key));

        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
                new CaffeineCache(CacheConstants.CFG_DATA_CACHE, cfgDataCache),
                new CaffeineCache(CacheConstants.BIZ_DATA_CACHE, bizDataCache)));
        return cacheManager;
    }

    @Bean(CacheConstants.CUSTOM_KEY_GENERATOR)
    public KeyGenerator createMapperKeyGenerator() {
        return new CustomKeyGenerator();
    }

    private Object load(Object key) throws Exception {
        logger.debug("cacheLoader load : {}", key);
        return null;
    }
}

回收策略

本例中主要配置了 maximumSizerefreshAfterWriteexpireAfterWrite 三种策略,顾名知义,maximumSize 是基于空间的,refreshAfterWriteexpireAfterWrite 是基于时间的。

  • refreshAfterWrite :在需要加载缓存时,会由一个线程去计算加载缓存,其他线程直接取旧值。

  • expireAfterWrite :在需要加载缓存时,会阻塞相应线程,逐个 “获取锁,计算加载缓存,释放锁”。

本例中配置数据的缓存更适合使用 refreshAfterWrite 策略。

注解用法

注解示例

为 spring boot 入口类加上@EnableCaching 注解。

在相应方法中使用相应的缓存注解,如本项目中的配置数据缓存使用 refreshAfterWrite 策略及 @Cacheable 注解。

@Mapper
public interface SysConstantMapper {

    @Cacheable(cacheNames = CacheConstants.CFG_DATA_CACHE,
               keyGenerator = CacheConstants.CUSTOM_KEY_GENERATOR,
               sync = true)
    List<SysConstantDO> selectRecordsByQO(@Param("cond") SysConstantQO cond);
}

自定义缓存注解

若注解中的参数比较多,显得比较杂乱,可以自定义缓存注解,如:

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;

@Caching(
    cacheable = {
        @Cacheable(cacheNames = CacheConstants.CFG_DATA_CACHE, 
                   keyGenerator = CacheConstants.CUSTOM_KEY_GENERATOR, 
                   sync = true)
        }
    )
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CfgDataCache {
}
@Mapper
public interface SysConstantMapper {

    @CfgDataCache
    List<SysConstantDO> selectRecordsByQO(@Param("cond") SysConstantQO cond);
}

属性说明

sync 属性:

用于保证缓存需要加载时,只会有一个线程计算数据,其他线程阻塞。caffeine 本身也有类似机制,但是使用 sync 属性,其他线程由 spring 阻塞 ,而不是 caffeine。因为 caffeine 的阻塞机制中 ,每个阻塞的线程仍要重复 “获取锁,计算加载缓存,释放锁” 类似的过程,而由 spring 阻塞,阻塞的线程会待计算数据的线程加载完缓存后,直接从缓存中获取数据。

sync 属性和 unless 属性不能共用。

Synchronize the invocation of the underlying method if several threads are attempting to load a value for the same key. The synchronization leads to a couple of limitations:

  1. unless() is not supported
  2. Only one cache may be specified
  3. No other cache-related operation can be combined

This is effectively a hint and the actual cache provider that you are using may not support it in a synchronized fashion. Check your provider documentation for more details on the actual semantics.

​ —— javadoc

unless 属性:

用于否决(veto)缓存,缓存计算结束后判断,若满足该表达式,则计算结果不会加入缓存中。如 unless = "#result == null" ,表示若计算结果为空,则不加入缓存。

补充说明

  1. spring cache 使用基于动态生成子类的代理机制来对方法的调用进行切面,如果缓存的方法是内部调用而不是外部引用,会导致代理失败,切面失效。

参考

1.spring-boot-features-caching-provider-caffeine

2.ben-manes-caffeine

3.深入解密来自未来的缓存-Caffeine

4.Caffeine 缓存

5.如何解决多线程高并发场景下的 Java 缓存问题

你可能感兴趣的:(----,----,FW,Spring,----,PL,-,Java)