缓存概述
查询频率高、更新频率低的数据可以加缓存,缓存可分为本地缓存、分布式缓存2大类。
l
本地缓存
- 比分布式缓存速度快、效率高;
- 占用应用服务器内存,缓存的数据量通常不能太大;
- 适合单机应用中使用,分布式环境下容易出现数据不一致问题,如果要在分布式环境中使用本地、远程二级缓存,本地缓存的有效期应该设置很短,建议5s以内,减少本地缓存脏数据问题;
- 常见的比如guava、ehcache、caffeine;
本地缓存常见实现
- 自己用 ConcurrentLinkedHashMap 实现;
- guava:基于 CurrentMap 实现;
- ehcache:纯java编写的进程内缓存框架,快速精干,缓存数据存储有内存、硬盘两级,无需担心容量问题;
- caffeine:基于java8,在内存中缓存数据,性能极高、高命中率、低内存占用,在spring 2.x中取代guava成为默认的本地缓存组件
分布式缓存
- 常用于多个服务、应用之间的数据共享,适合分布式环境中使用
- 通常使用专用服务器,支持缓存大量数据;
- 通常使用内存缓存数据,性能高
分布式缓存常见实现
- memcached:只支持String类型;缺乏认证、安全管制机制,使用时需要将memcached服务器放在防火墙后;不支持持久化,宕机全部数据丢失,一般只用于缓存数据;支持多核cpu;
- redis:数据类型丰富;支持数据持久化,可保存数据到硬盘,容灾能力强;功能丰富,支持发布/订阅、队列等多种功能;redis 6.0前使用单核cpu,存储大量数据时memcached整体性能高于redis。
springboot 的缓存接口
springboot 定义了一套缓存标准,提供了默认实现,也可以引入第三方的缓存实现。
基本配置
1、pom.xml
创建项目时勾选 I/O -> Spring cache abstraction,或自行添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、启动类加 @EnableCaching
配置缓存管理器
可以在 yml 中配置,也可以用 java 代码配置
spring:
cache:
#缓存类型,参见CacheType,缺省时默认为 SIMPLE
type: SIMPLE
#缓存名称,list,需要在此处列出所有用到的缓存名称
cache-names: userInfo,systemConfig
# 一些缓存类型可指定扩展配置,参见 CacheProperties 中对应的静态类的属性
# caffeine:
# spec: initialCapacity=100,maximumSize=1000,expireAfterAccess=1800s
常用的缓存类型:CAFFEINE、EHCACHE、REDIS、SIMPLE(基于ConcurrentMap实现,无需引入第三方缓存实现框架)、NONE(不使用缓存)。
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 源码如下
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class) //未配置缓存管理器时默认使用 Simple
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
使用缓存
方式1 缓存注解
@Service
public class UserService {
@Resource
private UserDao userDao;
@Cacheable(cacheNames = "userInfo", key = "#id")
public User getUserById(Long id) {
return userDao.getUserById(id);
}
@CacheEvict(cacheNames = "userInfo", key = "#user.id")
public void updateUserInfo(User user) {
userDao.updateUserInfo(user);
}
@CacheEvict(cacheNames = "userInfo", key = "#id")
public void deleteById(Long id) {
userDao.deleteById(id);
}
}
缓存注解
- @Cacheable:和查询搭配使用,先查本地缓存,本地缓存中有就直接返回,没有才执行方法体中的代码进行查询,执行成功后将返回值写入本地缓存;
- @CacheEvict:方法执行成功后删除指定的本地缓存;
- @CachePut:方法执行成功后将返回值写入缓存,已存在则覆盖。
以上注解都是在目标方法执行完毕后才写入、删除缓存,如果方法中抛出异常,则不写入、删除缓存。
缓存注解的属性
- cacheNames:指定 cacheName,数组,可以指定多个
- key:指定缓存key
- condition:指定注解生效的条件,满足条件时才会执行对应的缓存操作,未指定时默认满足条件
- unless:@Cacheable 特有的属性,指定方法执行成功后不写入缓存的条件,未指定时默认写入缓存。condition 是在方法执行前校验条件,获取不到 #result,unless 是方法执行成功后校验条件,可以获取到 #result。
key、condition、unless 都可以使用SpEL取值,可以使用内置对象,常见的内置对象如下
- #root.methodName:当前方法名
- #root.method.name:当前方法
- #root.target:当前方法所属的对象
- #root.targetClass:当前方法所属的对象的Class对象
- #root.args:实参表, #root.args[0]表示第一个参数
- #result:方法返回值,只能在@Cacheable的unless属性中使用
方式2 调用方法
CacheManager 提供了一系列操作缓存的方法,此处可以自行封装为缓存工具类,示例
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
/**
* 统一缓存服务
*/
@Slf4j
@Service
public class CommonCacheService {
@Resource
private CacheManager cacheManager;
/**
* 清除指定的key,可能会以异步之类的方式延迟执行
*
* @param cacheName 缓存名称
* @param cacheKey 缓存key
*/
public void delayClear(String cacheName, String cacheKey) {
Assert.isTrue(StringUtils.isNotBlank(cacheKey), "cacheKey不能为空");
Cache cache = this.getCacheNotNull(cacheName);
cache.evict(cacheKey);
}
/**
* 清除指定的key,立即执行
*
* @param cacheName 缓存名称
* @param cacheKey 缓存key
*/
public void clear(String cacheName, String cacheKey) {
Assert.isTrue(StringUtils.isNotBlank(cacheKey), "cacheKey不能为空");
Cache cache = this.getCacheNotNull(cacheName);
cache.evictIfPresent(cacheKey);
}
/**
* 清除缓存名称下所有的key,可能会以异步之类的方式延迟执行
*
* @param cacheName 缓存名称
*/
public void delayClearAll(String cacheName) {
Cache cache = this.getCacheNotNull(cacheName);
cache.clear();
}
/**
* 清除缓存名称下所有的key,立即执行
*
* @param cacheName 缓存名称
*/
public void clearAll(String cacheName) {
Cache cache = this.getCacheNotNull(cacheName);
cache.invalidate();
}
/**
* 设置或更新value,可能会以异步之类的方式延迟执行
*
* @param cacheName 缓存名称
* @param cacheKey 缓存key
* @param value 值
*/
public void delayPut(String cacheName, String cacheKey, Object value) {
Assert.isTrue(StringUtils.isNotBlank(cacheKey), "cacheKey不能为空");
Cache cache = this.getCacheNotNull(cacheName);
cache.put(cacheKey, value);
}
/**
* 设置或更新value,立即执行
*
* @param cacheName 缓存名称
* @param cacheKey 缓存key
* @param value 值
*/
public void put(String cacheName, String cacheKey, Object value) {
Assert.isTrue(StringUtils.isNotBlank(cacheKey), "cacheKey不能为空");
Cache cache = this.getCacheNotNull(cacheName);
cache.putIfAbsent(cacheKey, value);
}
/**
* 获取指定key对应的value
*
* @param cacheName 缓存名称
* @param cacheKey 缓存key
* @param clazz 目标类型的class对象
* @param <T> 目标类型
* @return T
*/
@Nullable
public <T> T get(String cacheName, String cacheKey, Class<T> clazz) {
Assert.isTrue(StringUtils.isNotBlank(cacheKey), "cacheKey不能为空");
Cache cache = this.getCacheNotNull(cacheName);
return cache.get(cacheKey, clazz);
}
/**
* 获取指定key对应的value(String类型)
*
* @param cacheName 缓存名称
* @param cacheKey 缓存key
* @return String
*/
@Nullable
public String get(String cacheName, String cacheKey) {
return this.get(cacheName, cacheKey, String.class);
}
private Cache getCacheNotNull(String cacheName) {
Assert.isTrue(StringUtils.isNotBlank(cacheName), "cacheName不能为空");
Cache cache = cacheManager.getCache(cacheName);
Assert.notNull(cache, "指定的cache不存在,cacheName=" + cacheName);
return cache;
}
}
整合缓存实现组件
caffeine
1、pom.xml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2、缓存配置
方式一 yml
spring:
cache:
type: caffeine
cache-names: userInfo,systemConfig
caffeine:
spec: initialCapacity=100,maximumSize=1000,expireAfterAccess=5s
spec是针对所有缓存的设置,不能针对各个缓存分别进行配置,比较死板。
3种缓存淘汰策略
- expireAfterAccess:基于最后访问时间
- expireAfterWrite:基于最后写入时间
- expireAfter:自定义的缓存淘汰策略
方式二 配置类(推荐)
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCache userInfoCache = this.buildCache("userInfo", 60, 100, 10000);
CaffeineCache systemConfigCache = this.buildCache("systemConfig", 30, 10, 1000);
SimpleCacheManager manager = new SimpleCacheManager();
manager.setCaches(Arrays.asList(userInfoCache, systemConfigCache));
return manager;
}
private CaffeineCache buildCache(String cacheName, int expireSecondsAfterWrite, int initialCapacity, long maximumSize) {
return new CaffeineCache(cacheName, Caffeine.newBuilder()
.expireAfterWrite(expireSecondsAfterWrite, TimeUnit.SECONDS)
.initialCapacity(initialCapacity)
.maximumSize(maximumSize)
.build());
}
}
ehcache
1、pom.xml
<!-- ehcache3.x -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.0</version>
</dependency>
<!-- ehcahce 2.x,已不再维护 -->
<!--<dependency>-->
<!-- <groupId>net.sf.ehcache</groupId>-->
<!-- <artifactId>ehcache</artifactId>-->
<!-- <version>2.10.6</version>-->
<!--</dependency>-->
2、resources下新建ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘缓存位置。java.io.tmpdir是系统默认的临时文件目录,也可以使用自定义的路径 -->
<diskStore path="java.io.tmpdir"/>
<!-- 默认缓存管理器 -->
<!-- memoryStoreEvictionPolicy缓存淘汰策略,支持FIFO、LRU、LFU -->
<!-- maxElementsInMemory内存中的元素最大数量,overflowToDisk内存元素数量达到最大值时是否存储到硬盘 -->
<!-- timeToLiveSeconds缓存有效期,timeToIdleSeconds缓存有效期内多久未访问就自动清除 -->
<defaultCache memoryStoreEvictionPolicy="LRU"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToLiveSeconds="10"
timeToIdleSeconds="5"/>
<!-- 自定义缓存管理器 -->
<cache name="userInfo"
memoryStoreEvictionPolicy="LRU"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToLiveSeconds="10"
timeToIdleSeconds="5"/>
<cache name="systemConfig"
memoryStoreEvictionPolicy="LRU"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToLiveSeconds="10"
timeToIdleSeconds="5"/>
</ehcache>
这是ehcache2.x的配置方式,3.x向下兼容2.x的配置方式
3、yml
spring:
cache:
#指定ehcache配置文件位置
jcache:
config: classpath:ehcache.xml
# ehcache:
# config: classpath:ehcache.xml
3.x用jcache.config,2.x使用ehcache.config。3.x向下兼容2.x的配置方式,也可以使用ehcache.config。
redis
1、pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、yml
spring:
#redis配置
redis:
host: 192.168.1.1
#集群则使用 cluster.nodes: 192.168.1.1:6379,192.168.1.2:6379,192.168.1.3:6379
port: 6379
database: 0
timeout: 3000
#缓存配置
cache:
type: REDIS
cache-names: userInfo,systemConfig
redis:
#是否使用key前缀
use-key-prefix: true
key-prefix: mall_
#是否缓存null值
cache-null-values: false
#缓存有效期
time-to-live: 300s