项目使用 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>
spring 提供的默认实现为 org.springframework.cache.interceptor.SimpleKeyGenerator
,仅使用方法的参数生成 key。参数若为自定义的查询对象 ,注意重写 hashCode 和 equals() 方法。
此处实现一个简单的 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();
}
}
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";
}
使用 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;
}
}
本例中主要配置了 maximumSize
,refreshAfterWrite
、expireAfterWrite
三种策略,顾名知义,maximumSize
是基于空间的,refreshAfterWrite
和expireAfterWrite
是基于时间的。
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:
- unless() is not supported
- Only one cache may be specified
- 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-boot-features-caching-provider-caffeine
2.ben-manes-caffeine
3.深入解密来自未来的缓存-Caffeine
4.Caffeine 缓存
5.如何解决多线程高并发场景下的 Java 缓存问题