Java framework-cache
ins-framework-cache 缓存框架使用指南
目录
一、框架概述
1.1 框架简介
ins-framework-cache 是一个基于 Spring Cache 抽象和 Redis 的缓存中间件,提供了统一的缓存管理能力。框架封装了 Spring Data Redis,简化了缓存的使用,支持单节点和集群模式的 Redis。
1.2 技术栈
- Spring Cache: Spring 框架的缓存抽象层
- Spring Data Redis: Redis 数据访问框架
- Redis: 内存数据库,作为缓存存储
- Jackson: JSON 序列化框架
- Spring Boot: 2.7.18
1.3 核心特性
- 自动配置: 基于 Spring Boot 自动配置,开箱即用
- 多种缓存管理器: 支持 Redis、内存缓存、无操作缓存
- 灵活配置: 支持自定义缓存过期时间、序列化方式
- 集群支持: 支持 Redis 单节点和集群模式
- 键生成策略: 提供默认的缓存键生成器
二、架构设计
2.1 整体架构
┌─────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ - Service 层业务逻辑 │
│ - 调用 CacheHelper 工具类 │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ CacheHelper 工具类 │
│ - 封装缓存操作(get/put/evict) │
│ - 实现延迟双删策略 │
│ - 提供分布式锁功能 │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ Spring Cache 抽象层 │
│ - CacheManager 接口 │
│ - Cache 接口 │
│ - 缓存注解支持(@Cacheable/@CacheEvict) │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ ins-framework-cache 中间件 │
│ - CacheConfig 配置类 │
│ - CacheUtils 工具类 │
│ - RedisCacheManager Bean │
│ - KeyGenerator Bean │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ Spring Data Redis │
│ - RedisConnectionFactory │
│ - RedisTemplate │
│ - RedisCacheWriter │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ Redis 服务器 │
│ - 单节点模式 / 集群模式 │
│ - 内存存储 │
└─────────────────────────────────────────────────────────┘
2.2 核心组件
2.2.1 CacheConfig 配置类
位置: ins.framework.cache.CacheConfig
职责:
- 配置
CacheManagerBean(默认使用 Redis) - 配置
KeyGeneratorBean(缓存键生成器) - 提供
ConcurrentMapCacheManagerBean(内存缓存) - 提供
NoOpCacheManagerBean(无操作缓存,用于测试)
关键代码:
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Bean
@Override
public CacheManager cacheManager() {
return redisCacheManager();
}
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager() {
// 默认过期时间:1天(86400秒)
RedisCacheConfiguration defaultConfig =
CacheUtils.redisConfigWithTtlInSeconds(86400);
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
defaultConfig,
new HashMap<>()
);
}
}
2.2.2 CacheUtils 工具类
位置: ins.framework.cache.util.CacheUtils
职责:
- 提供 Redis 缓存配置的创建方法
- 配置 JSON 序列化器(Jackson2JsonRedisSerializer)
- 支持自定义过期时间
关键方法:
// 创建带过期时间的 Redis 配置
public static RedisCacheConfiguration redisConfigWithTtlInSeconds(Integer seconds)
// 创建默认 Redis 配置(无过期时间)
public static RedisCacheConfiguration redisDefalutConfig()
序列化配置:
- Key 序列化: 使用 Redis 默认序列化(String)
- Value 序列化: Jackson2JsonRedisSerializer(JSON 格式)
- 支持类型信息:
enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)
2.2.3 缓存键生成器
默认键生成规则:
格式: {类全限定名}.{方法名}.{参数1}.{参数2}...
示例:
- 类: pdfc.monitor.service.impl.SysUserServiceImpl
- 方法: selectByPrimaryKey
- 参数: 1
- 生成的键: pdfc.monitor.service.impl.SysUserServiceImpl.selectByPrimaryKey.1
实现代码:
private static Object generateKey(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder(128);
sb.append(target.getClass().getName());
sb.append('.');
sb.append(method.getName());
for (Object obj : params) {
sb.append('.');
sb.append(obj.toString());
}
return sb.toString();
}
2.3 缓存管理器类型
框架提供了三种缓存管理器:
1. RedisCacheManager(默认)
- 用途: 生产环境,使用 Redis 作为缓存存储
- 特点:
- 支持分布式缓存
- 支持过期时间配置
- 支持集群模式
- 数据持久化(可选)
2. ConcurrentMapCacheManager(内存缓存)
- 用途: 开发/测试环境,单机内存缓存
- 特点:
- 无需 Redis 服务器
- 应用重启后数据丢失
- 性能高,适合单机测试
3. NoOpCacheManager(无操作缓存)
- 用途: 测试环境,禁用缓存功能
- 特点:
- 不存储任何数据
- 所有缓存操作都是空操作
- 用于测试缓存逻辑是否正确
三、工作原理
3.1 Spring Cache 抽象
ins-framework-cache 基于 Spring Cache 抽象层,提供了统一的缓存接口:
public interface Cache {
String getName(); // 缓存名称
Object get(Object key); // 获取缓存值
void put(Object key, Object value); // 写入缓存
void evict(Object key); // 删除缓存
void clear(); // 清空缓存
}
public interface CacheManager {
Cache getCache(String name); // 获取缓存实例
Collection<String> getCacheNames(); // 获取所有缓存名称
}
3.2 缓存操作流程
3.2.1 查询操作(Cache-Aside 模式)
┌─────────┐
│ 请求查询 │
└────┬────┘
│
▼
┌─────────────────┐
│ 1. 查询缓存 │
│ cache.get(key) │
└────┬────────────┘
│
├─── 缓存命中 ────┐
│ │
│ ▼
│ ┌──────────────┐
│ │ 返回缓存数据 │
│ └──────────────┘
│
└─── 缓存未命中 ───┐
│
▼
┌─────────────────┐
│ 2. 查询数据库 │
│ dao.select(id) │
└────┬────────────┘
│
▼
┌─────────────────┐
│ 3. 写入缓存 │
│ cache.put(key) │
└────┬────────────┘
│
▼
┌──────────────┐
│ 返回数据 │
└──────────────┘
代码示例:
public SysUserVo selectByPrimaryKey(Integer id) {
// 1. 先查缓存
SysUserVo cached = cacheHelper.get(CACHE_NAME_SYS_USER, id, SysUserVo.class);
if (cached != null) {
return cached; // 缓存命中,直接返回
}
// 2. 缓存未命中,查询数据库
SysUser po = sysUserDao.selectByPrimaryKey(id);
if (po == null) {
return null;
}
// 3. 转换为VO并写入缓存
SysUserVo vo = BeanConverter.poToVo(po);
cacheHelper.put(CACHE_NAME_SYS_USER, id, vo);
return vo;
}
3.2.2 更新操作(延迟双删策略)
┌─────────┐
│ 请求更新 │
└────┬────┘
│
▼
┌─────────────────┐
│ 1. 第一次删除缓存│
│ cache.evict(key)│
└────┬────────────┘
│
▼
┌─────────────────┐
│ 2. 更新数据库 │
│ dao.update(data)│
└────┬────────────┘
│
▼
┌─────────────────┐
│ 3. 延迟删除缓存 │
│ (异步执行) │
│ Thread.sleep(500)│
│ cache.evict(key)│
└─────────────────┘
延迟双删原理:
- 第一次删除: 在更新数据库之前删除缓存,防止其他线程读取到旧数据
- 更新数据库: 执行数据库更新操作
- 第二次删除: 延迟 500ms 后再次删除缓存,确保数据库更新完成后再清理缓存
为什么需要延迟双删:
- 场景1: 线程A更新数据库,线程B在更新过程中读取了旧数据并写入缓存
- 场景2: 数据库主从复制延迟,从库可能还未同步最新数据
- 解决方案: 延迟删除可以确保数据库更新完成后再删除缓存,避免脏数据
代码示例:
public int updateByPrimaryKey(SysUserVo entity) {
Integer id = entity.getId();
// 使用分布式锁确保原子性
return cacheHelper.executeWithLock("lock:sysUser:update:" + id, 10, () -> {
// 延迟双删策略
Runnable delayedDelete = cacheHelper.delayedDoubleDelete(
CACHE_NAME_SYS_USER, id, 500);
// 1. 更新数据库
int result = sysUserDao.updateByPrimaryKey(po);
// 2. 触发延迟双删(异步执行第二次删除)
if (result > 0) {
delayedDelete.run();
}
return result;
});
}
3.2.3 删除操作(延迟双删策略)
删除操作的流程与更新操作类似:
1. 第一次删除缓存
2. 删除数据库
3. 延迟500ms后第二次删除缓存(异步执行)
代码示例:
public int deleteByPrimaryKey(Integer id) {
// 1. 第一次删除缓存
cacheHelper.evict(CACHE_NAME_SYS_USER, id);
// 2. 删除数据库
int result = sysUserDao.deleteByPrimaryKey(id);
// 3. 延迟后第二次删除缓存(异步执行)
if (result > 0) {
CompletableFuture.runAsync(() -> {
Thread.sleep(500);
cacheHelper.evict(CACHE_NAME_SYS_USER, id);
});
}
return result;
}
3.3 分布式锁机制
为了确保缓存操作的原子性,框架提供了分布式锁功能:
锁的实现:
public <T> T executeWithLock(String lockKey, long timeout, Supplier<T> action) {
String lockValue = String.valueOf(System.currentTimeMillis());
Boolean lockAcquired = false;
try {
// 尝试获取锁(SETNX + 过期时间)
lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, timeout, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lockAcquired)) {
return action.get(); // 执行操作
} else {
throw new RuntimeException("获取分布式锁失败");
}
} finally {
// 释放锁(检查锁值,防止误删其他线程的锁)
if (Boolean.TRUE.equals(lockAcquired)) {
Object currentValue = redisTemplate.opsForValue().get(lockKey);
if (lockValue.equals(String.valueOf(currentValue))) {
redisTemplate.delete(lockKey);
}
}
}
}
锁的特性:
- 原子性: 使用 Redis 的
SETNX命令确保原子性 - 超时机制: 锁自动过期,防止死锁
- 安全性: 释放锁时检查锁值,防止误删其他线程的锁
- 降级处理: Redis 不可用时自动降级,直接执行操作
四、项目使用场景
4.1 项目架构集成
在本项目中,缓存框架的集成架构如下:
┌─────────────────────────────────────────┐
│ Controller 层 (Web) │
│ - 接收 HTTP 请求 │
│ - 调用 Service 层 │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Service 层 (业务逻辑) │
│ - 业务逻辑处理 │
│ - 缓存操作(通过 CacheHelper) │
│ - 数据库操作(通过 DAO) │
└────┬──────────────────┬─────────────────┘
│ │
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│CacheHelper│ │ DAO 层 │
│工具类 │ │ (MyBatis) │
└────┬─────┘ └──────┬───────┘
│ │
│ │
▼ ▼
┌─────────────────────────────┐
│ ins-framework-cache │
│ (CacheManager) │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ Redis 服务器 │
└─────────────────────────────┘
4.2 CacheHelper 工具类
位置: pdfc.monitor.util.CacheHelper
设计目的: 封装缓存操作,提供高级功能(延迟双删、分布式锁)
核心方法:
| 方法 | 功能 | 使用场景 |
|---|---|---|
get() |
从缓存获取数据 | 查询操作 |
put() |
写入缓存 | 新增/查询后写入 |
evict() |
删除缓存 | 更新/删除操作 |
evictBatch() |
批量删除缓存 | 批量删除操作 |
delayedDoubleDelete() |
延迟双删 | 更新/删除操作 |
executeWithLock() |
分布式锁 | 更新操作(确保原子性) |
4.3 使用场景详解
场景1: 查询操作(Cache-Aside 模式)
业务场景: 根据用户ID查询用户信息
实现方式:
@Override
public SysUserVo selectByPrimaryKey(Integer id) {
// 1. 先查缓存
SysUserVo cached = cacheHelper.get(CACHE_NAME_SYS_USER, id, SysUserVo.class);
if (cached != null) {
logger.debug("从缓存获取用户,用户ID: {}", id);
return cached;
}
// 2. 缓存未命中,查询数据库
SysUser po = sysUserDao.selectByPrimaryKey(id);
if (po == null) {
return null;
}
// 3. 转换为VO并写入缓存
SysUserVo vo = BeanConverter.poToVo(po);
cacheHelper.put(CACHE_NAME_SYS_USER, id, vo);
logger.debug("从数据库获取用户并写入缓存,用户ID: {}", id);
return vo;
}
优势:
- 减少数据库查询压力
- 提高响应速度
- 自动缓存未命中的数据
场景2: 新增操作(Write-Through 模式)
业务场景: 创建新用户
实现方式:
@Override
public int insert(SysUserVo entity) {
// 1. 写入数据库
SysUser po = BeanConverter.voToPo(entity);
po.setInsertTimeForHis(new Date());
po.setOperateTimeForHis(new Date());
if (po.getVersion() == null) {
po.setVersion(1);
}
int result = sysUserDao.insert(po);
// 2. 写入缓存
if (result > 0 && po.getId() != null) {
SysUserVo vo = BeanConverter.poToVo(po);
cacheHelper.put(CACHE_NAME_SYS_USER, po.getId(), vo);
logger.info("新增用户成功,已写入缓存,用户ID: {}", po.getId());
}
return result;
}
优势:
- 新增后立即缓存,后续查询可直接命中
- 保证缓存与数据库的一致性
场景3: 更新操作(延迟双删 + 分布式锁)
业务场景: 更新用户信息
实现方式:
@Override
public int updateByPrimaryKey(SysUserVo entity) {
Integer id = entity.getId();
// 使用分布式锁确保缓存操作的原子性
return cacheHelper.executeWithLock("lock:sysUser:update:" + id, 10, () -> {
// 延迟双删策略
Runnable delayedDelete = cacheHelper.delayedDoubleDelete(
CACHE_NAME_SYS_USER, id, 500);
// 1. 更新数据库
SysUser po = BeanConverter.voToPo(entity);
po.setOperateTimeForHis(new Date());
int result = sysUserDao.updateByPrimaryKey(po);
// 2. 触发延迟双删(异步执行第二次删除)
if (result > 0) {
delayedDelete.run();
logger.info("更新用户成功,已执行延迟双删,用户ID: {}", id);
}
return result;
});
}
关键点:
- 分布式锁: 确保同一时间只有一个线程更新缓存
- 延迟双删: 防止脏数据写入缓存
- 异步执行: 第二次删除异步执行,不阻塞主流程
场景4: 删除操作(延迟双删)
业务场景: 删除用户
实现方式:
@Override
public int deleteByPrimaryKey(Integer id) {
// 1. 第一次删除缓存
cacheHelper.evict(CACHE_NAME_SYS_USER, id);
logger.info("删除用户:第一次删除缓存,用户ID: {}", id);
// 2. 删除数据库
int result = sysUserDao.deleteByPrimaryKey(id);
// 3. 延迟后第二次删除缓存(异步执行)
if (result > 0) {
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500); // 延迟500ms
cacheHelper.evict(CACHE_NAME_SYS_USER, id);
logger.info("删除用户:第二次删除缓存(延迟500ms后),用户ID: {}", id);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("删除用户:延迟删除缓存被中断,用户ID: {}", id, e);
}
});
}
return result;
}
关键点:
- 延迟双删: 确保数据库删除完成后再清理缓存
- 异步执行: 不阻塞删除操作的主流程
场景5: 批量查询(缓存预热)
业务场景: 批量查询多个用户
实现方式:
@Override
public List<SysUserVo> selectBatchByPrimaryKeys(List<SysUserVo> idList) {
List<Integer> ids = extractIds(idList);
// 1. 先从缓存获取
List<SysUserVo> resultList = new ArrayList<>();
List<Integer> missIds = new ArrayList<>();
for (Integer id : ids) {
SysUserVo cached = cacheHelper.get(CACHE_NAME_SYS_USER, id, SysUserVo.class);
if (cached != null) {
resultList.add(cached);
} else {
missIds.add(id);
}
}
// 2. 缓存未命中的,查询数据库
if (!missIds.isEmpty()) {
List<SysUser> poList = sysUserDao.selectBatchByPrimaryKeys(missIds);
List<SysUserVo> voList = BeanConverter.poListToVoList(poList);
// 3. 写入缓存
for (SysUserVo vo : voList) {
if (vo != null && vo.getId() != null) {
cacheHelper.put(CACHE_NAME_SYS_USER, vo.getId(), vo);
resultList.add(vo);
}
}
}
return resultList;
}
优势:
- 部分命中缓存,减少数据库查询
- 自动预热缓存,提高后续查询性能
4.4 缓存策略总结
| 操作类型 | 缓存策略 | 实现方式 | 适用场景 |
|---|---|---|---|
| 查询 | Cache-Aside | 先查缓存,未命中查数据库并写入缓存 | 读多写少的场景 |
| 新增 | Write-Through | 写入数据库后同步写入缓存 | 新增后立即需要查询的场景 |
| 更新 | 延迟双删 | 先删缓存 → 更新数据库 → 延迟再删缓存 | 更新频繁的场景 |
| 删除 | 延迟双删 | 先删缓存 → 删除数据库 → 延迟再删缓存 | 删除后需要清理缓存的场景 |
五、配置说明
5.1 Maven 依赖
在 pom.xml 中添加依赖:
<dependency>
<groupId>ins.framework</groupId>
<artifactId>ins-framework-cache</artifactId>
<version>6.2.1-SNAPSHOT</version>
</dependency>
依赖传递:
spring-boot-starter-cache: Spring Cache 支持spring-boot-starter-data-redis: Spring Data Redis 支持jackson-databind: JSON 序列化支持
5.2 Redis 配置
在 application.properties 中配置:
# Redis 缓存配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
spring.redis.timeout=3000
# 连接池配置
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
Redis 集群配置(可选):
# Redis 集群配置
spring.redis.cluster.nodes=192.168.1.1:6379,192.168.1.2:6379,192.168.1.3:6379
spring.redis.cluster.max-redirects=5
5.3 自定义缓存配置
如果需要自定义缓存过期时间,可以创建配置类:
@Configuration
@ConditionalOnBean(value = CacheConfig.class)
public class WebCacheConfig {
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Primary
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager() {
// 默认过期时间:1天
RedisCacheConfiguration defaultConfig =
CacheUtils.redisConfigWithTtlInSeconds(86400);
// 自定义缓存过期时间
Map<String, RedisCacheConfiguration> customConfigMap = new HashMap<>();
customConfigMap.put("sysUser", CacheUtils.redisConfigWithTtlInSeconds(3600)); // 1小时
customConfigMap.put("hotData", CacheUtils.redisConfigWithTtlInSeconds(300)); // 5分钟
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
defaultConfig,
customConfigMap
);
}
}
5.4 RedisTemplate 配置
项目中的 RedisConfig 配置类用于配置 RedisTemplate(用于分布式锁等功能):
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key 序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
六、最佳实践
6.1 缓存键设计
原则:
- 唯一性: 确保不同业务数据的键不冲突
- 可读性: 键名清晰表达业务含义
- 简洁性: 避免过长的键名
本项目中的键设计:
// 缓存名称
public static final String CACHE_NAME_SYS_USER = "sysUser";
// 缓存键格式: {cacheName}::{key}
// 示例: sysUser::1, sysUser::2
6.2 缓存过期时间
建议:
- 热点数据: 5-30分钟
- 普通数据: 1-24小时
- 冷数据: 1-7天
- 配置数据: 可设置较长过期时间或手动刷新
本项目配置:
- 默认过期时间: 1天(86400秒)
- 可根据业务需求调整
6.3 缓存穿透防护
问题: 查询不存在的数据,导致频繁查询数据库
解决方案:
public SysUserVo selectByPrimaryKey(Integer id) {
// 1. 先查缓存
SysUserVo cached = cacheHelper.get(CACHE_NAME_SYS_USER, id, SysUserVo.class);
if (cached != null) {
return cached;
}
// 2. 查询数据库
SysUser po = sysUserDao.selectByPrimaryKey(id);
if (po == null) {
// 缓存空值,防止缓存穿透
cacheHelper.put(CACHE_NAME_SYS_USER, id, new SysUserVo()); // 空对象
return null;
}
// 3. 写入缓存
SysUserVo vo = BeanConverter.poToVo(po);
cacheHelper.put(CACHE_NAME_SYS_USER, id, vo);
return vo;
}
6.4 缓存雪崩防护
问题: 大量缓存同时过期,导致数据库压力激增
解决方案:
- 设置不同的过期时间(添加随机值)
- 使用缓存预热
- 使用分布式锁控制数据库查询
// 添加随机过期时间
long ttl = 3600 + (long)(Math.random() * 600); // 3600-4200秒
cacheHelper.putWithTtl(CACHE_NAME_SYS_USER, id, vo, ttl);
6.5 缓存更新策略选择
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cache-Aside | 实现简单,灵活性高 | 可能出现缓存不一致 | 读多写少 |
| Write-Through | 缓存一致性高 | 写入性能较低 | 写多读少 |
| Write-Behind | 写入性能高 | 可能丢失数据 | 写入频繁,可容忍数据丢失 |
| 延迟双删 | 保证最终一致性 | 实现复杂 | 更新/删除操作 |
本项目选择: Cache-Aside(查询)+ 延迟双删(更新/删除)
6.6 分布式锁使用建议
使用场景:
- 更新操作需要保证原子性
- 防止并发更新导致的数据不一致
注意事项:
- 锁的超时时间要合理(不能太短也不能太长)
- 释放锁时要检查锁值,防止误删
- Redis 不可用时要降级处理
本项目实现:
// 锁超时时间:10秒
// 锁键格式:lock:{业务}:{操作}:{资源ID}
cacheHelper.executeWithLock("lock:sysUser:update:" + id, 10, () -> {
// 执行更新操作
});
七、性能优化
7.1 缓存预热
场景: 系统启动时预加载热点数据
实现方式:
@PostConstruct
public void warmUpCache() {
// 查询热点数据并写入缓存
List<SysUser> hotUsers = sysUserDao.selectHotUsers();
for (SysUser user : hotUsers) {
SysUserVo vo = BeanConverter.poToVo(user);
cacheHelper.put(CACHE_NAME_SYS_USER, user.getId(), vo);
}
}
7.2 批量操作优化
场景: 批量查询时,部分命中缓存,部分查询数据库
优化点:
- 先批量查询缓存
- 只查询缓存未命中的数据
- 批量写入缓存
7.3 异步写入缓存
场景: 写入操作频繁,可以异步写入缓存
实现方式:
// 异步写入缓存,不阻塞主流程
CompletableFuture.runAsync(() -> {
cacheHelper.put(CACHE_NAME_SYS_USER, id, vo);
});
7.4 缓存压缩
场景: 缓存数据较大时,可以压缩存储
实现方式: 使用压缩序列化器(需要自定义)
八、监控和故障排查
8.1 缓存命中率监控
指标:
- 缓存命中次数
- 缓存未命中次数
- 缓存命中率 = 命中次数 / (命中次数 + 未命中次数)
实现方式: 在 CacheHelper 中添加统计功能
8.2 日志记录
关键日志:
- 缓存命中/未命中
- 缓存写入/删除
- 分布式锁获取/释放
- 延迟双删执行
日志级别:
- DEBUG: 正常缓存操作
- INFO: 重要操作(延迟双删、分布式锁)
- WARN: 降级处理
- ERROR: 异常情况
8.3 常见问题排查
问题1: 缓存未生效
可能原因:
- Redis 服务未启动
- Redis 连接配置错误
- CacheManager 未正确注入
排查步骤:
- 检查 Redis 服务状态
- 检查
application.properties配置 - 查看日志中的缓存操作记录
- 检查
CacheManagerBean 是否存在
问题2: 缓存数据不一致
可能原因:
- 延迟双删未正确执行
- 分布式锁获取失败
- 数据库更新失败但缓存已删除
排查步骤:
- 检查日志中的延迟双删执行记录
- 验证分布式锁是否正常工作
- 确保数据库操作在事务中执行
问题3: 缓存性能问题
可能原因:
- 缓存键设计不合理
- 缓存过期时间设置不当
- Redis 内存不足
排查步骤:
- 优化缓存键设计
- 调整缓存过期时间
- 增加 Redis 内存或清理无用缓存
- 使用 Redis 监控工具查看性能指标
九、源码解析
9.1 CacheConfig 源码解析
文件位置: ins.framework.cache.CacheConfig
完整源码:
package ins.framework.cache;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.support.NoOpCacheManager;
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.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import ins.framework.cache.util.CacheUtils;
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
private static final int DEFAULT_BUFFER_SIZE = 128; // 初始Key大小
private static final int SECONDS_OF_ONE_DAY = 86400; // 默认过期时间:1天
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Bean
@Override
public CacheManager cacheManager() {
return redisCacheManager();
}
@Bean
@Override
public KeyGenerator keyGenerator() {
return CacheConfig::generateKey;
}
@Bean(name = "concurrentMapCacheManager")
public ConcurrentMapCacheManager concurrentMapCacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setAllowNullValues(true);
return cacheManager;
}
@Bean(name = "noOpCacheManager")
public NoOpCacheManager noOpCacheManager() {
return new NoOpCacheManager();
}
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager() {
// 未配置的 key 会使用这个默认的1天
RedisCacheConfiguration defaultConfig =
CacheUtils.redisConfigWithTtlInSeconds(SECONDS_OF_ONE_DAY);
Map<String, RedisCacheConfiguration> customConfigMap = new HashMap<>();
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
defaultConfig,
customConfigMap
);
}
private static Object generateKey(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE);
sb.append(target.getClass().getName());
sb.append('.');
sb.append(method.getName());
for (Object obj : params) {
sb.append('.');
sb.append(obj.toString());
}
return sb.toString();
}
}
9.1.1 类注解解析
@EnableCaching:
- 作用: 启用 Spring Cache 功能
- 原理: 启用缓存注解(
@Cacheable、@CacheEvict、@CachePut)的处理 - 效果: Spring 会自动创建
CacheInterceptor拦截器,拦截带有缓存注解的方法
@Configuration:
- 作用: 标识这是一个配置类
- 原理: Spring 会扫描这个类,处理其中的
@Bean方法 - 效果: 将方法返回的对象注册为 Spring Bean
extends CachingConfigurerSupport:
- 作用: 继承 Spring Cache 配置支持类
- 原理: 提供了
cacheManager()和keyGenerator()方法的默认实现 - 效果: 可以重写这些方法来提供自定义的缓存管理器和键生成器
9.1.2 核心方法解析
1. cacheManager() 方法
@Bean
@Override
public CacheManager cacheManager() {
return redisCacheManager();
}
作用: 提供默认的 CacheManager Bean
执行流程:
- Spring 启动时调用此方法
- 返回
RedisCacheManager实例 - Spring 将返回的对象注册为
CacheManager类型的 Bean - 其他组件通过
@Autowired CacheManager注入使用
为什么需要 @Override:
- 重写父类
CachingConfigurerSupport的方法 - 提供自定义的缓存管理器实现
2. keyGenerator() 方法
@Bean
@Override
public KeyGenerator keyGenerator() {
return CacheConfig::generateKey;
}
作用: 提供默认的缓存键生成器
方法引用 CacheConfig::generateKey:
- 这是 Java 8 的方法引用语法
- 等价于:
(target, method, params) -> CacheConfig.generateKey(target, method, params) - 返回一个
KeyGenerator函数式接口的实现
使用场景: 当使用 @Cacheable 注解但未指定 key 属性时,会使用这个生成器
3. redisCacheManager() 方法
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager() {
// 默认配置:过期时间1天
RedisCacheConfiguration defaultConfig =
CacheUtils.redisConfigWithTtlInSeconds(SECONDS_OF_ONE_DAY);
// 自定义配置映射(可以为不同的缓存名称设置不同的过期时间)
Map<String, RedisCacheConfiguration> customConfigMap = new HashMap<>();
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
defaultConfig,
customConfigMap
);
}
参数解析:
-
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory):- 作用: 创建非阻塞的 Redis 缓存写入器
- 原理: 使用
RedisConnectionFactory创建 Redis 连接 - 特点: 非阻塞写入,性能更高,但可能出现并发写入问题(适合读多写少场景)
-
defaultConfig:- 作用: 默认的缓存配置
- 内容: 包含过期时间、序列化方式等
- 使用: 当缓存名称不在
customConfigMap中时,使用此配置
-
customConfigMap:- 作用: 自定义缓存配置映射
- 格式:
Map<缓存名称, 缓存配置> - 示例:
{"sysUser": 3600秒配置, "hotData": 300秒配置}
RedisCacheManager 工作原理:
RedisCacheManager
├── 根据缓存名称查找配置
│ ├── 在 customConfigMap 中查找 → 使用自定义配置
│ └── 未找到 → 使用 defaultConfig
├── 创建 RedisCache 实例
└── 返回 Cache 接口实现
4. generateKey() 方法
private static Object generateKey(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); // 初始容量128
sb.append(target.getClass().getName()); // 类全限定名
sb.append('.'); // 分隔符
sb.append(method.getName()); // 方法名
for (Object obj : params) { // 遍历参数
sb.append('.');
sb.append(obj.toString()); // 参数值
}
return sb.toString();
}
键生成规则:
格式: {类全限定名}.{方法名}.{参数1}.{参数2}...
示例1:
- 类: pdfc.monitor.service.impl.SysUserServiceImpl
- 方法: selectByPrimaryKey
- 参数: [1]
- 生成的键: pdfc.monitor.service.impl.SysUserServiceImpl.selectByPrimaryKey.1
示例2:
- 类: pdfc.monitor.service.impl.SysUserServiceImpl
- 方法: selectBatchByPrimaryKeys
- 参数: [1, 2, 3]
- 生成的键: pdfc.monitor.service.impl.SysUserServiceImpl.selectBatchByPrimaryKeys.1.2.3
设计考虑:
- 唯一性: 类名+方法名+参数确保键的唯一性
- 可读性: 键名清晰表达方法调用信息
- 性能: 使用
StringBuilder提高字符串拼接性能
5. concurrentMapCacheManager() 方法
@Bean(name = "concurrentMapCacheManager")
public ConcurrentMapCacheManager concurrentMapCacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setAllowNullValues(true); // 允许缓存 null 值
return cacheManager;
}
作用: 提供内存缓存管理器(用于开发/测试环境)
特点:
- 存储: 使用
ConcurrentHashMap存储,线程安全 - 作用域: 应用内,不跨进程
- 持久化: 应用重启后数据丢失
- 性能: 内存访问,性能极高
使用场景:
- 开发环境(无需 Redis)
- 单元测试
- 性能测试(避免网络延迟)
6. noOpCacheManager() 方法
@Bean(name = "noOpCacheManager")
public NoOpCacheManager noOpCacheManager() {
return new NoOpCacheManager();
}
作用: 提供无操作缓存管理器(用于测试)
特点:
- 操作: 所有缓存操作都是空操作(不存储任何数据)
- 用途: 测试缓存逻辑是否正确,而不实际使用缓存
使用场景:
- 集成测试(验证业务逻辑,不关心缓存)
- 性能测试(排除缓存影响)
9.2 CacheUtils 源码解析
文件位置: ins.framework.cache.util.CacheUtils
完整源码:
package ins.framework.cache.util;
import java.time.Duration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
public final class CacheUtils {
private CacheUtils() {
// 工具类,禁止实例化
}
/**
* 获取Redis配置,参数为超时时间,单位为秒
*/
public static RedisCacheConfiguration redisConfigWithTtlInSeconds(Integer seconds) {
RedisCacheConfiguration redisCacheConfiguration = redisDefalutConfig();
return redisCacheConfiguration.entryTtl(Duration.ofSeconds(seconds));
}
/**
* 获取Redis配置(未设置超时)
*/
public static RedisCacheConfiguration redisDefalutConfig() {
// 1. 创建 Jackson JSON 序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
// 2. 配置 ObjectMapper
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 3. 创建默认 Redis 缓存配置
RedisCacheConfiguration redisCacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig();
// 4. 设置值序列化器
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
jackson2JsonRedisSerializer
)
);
return redisCacheConfiguration;
}
}
9.2.1 类设计解析
public final class CacheUtils:
final: 禁止继承,工具类设计模式- 私有构造器: 禁止实例化,只能通过静态方法调用
9.2.2 redisDefalutConfig() 方法详解
作用: 创建默认的 Redis 缓存配置(包含序列化设置)
步骤解析:
步骤1: 创建 JSON 序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
作用: 创建用于序列化/反序列化 Redis 值的序列化器
为什么使用 JSON:
- 可读性: JSON 格式人类可读,便于调试
- 跨语言: JSON 是通用格式,其他语言也可以读取
- 灵活性: 支持复杂对象结构
步骤2: 配置 ObjectMapper
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
配置解析:
-
setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY):- 作用: 设置所有属性(字段、getter、setter)都可见
- 效果: 序列化时包含所有字段,不受访问修饰符限制
-
enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL):- 作用: 启用类型信息存储
- 原理: 在 JSON 中添加
@class字段,存储类的全限定名 - 效果: 反序列化时能够正确还原对象类型
- 示例:
{ "@class": "pdfc.monitor.domain.sys.vo.SysUserVo", "id": 1, "name": "张三" }
为什么需要类型信息:
- Java 反序列化时需要知道对象的实际类型
- 特别是多态场景(父类引用指向子类对象)
步骤3: 创建默认配置
RedisCacheConfiguration redisCacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig();
默认配置包含:
- Key 序列化:
StringRedisSerializer(字符串) - Value 序列化:
JdkSerializationRedisSerializer(Java 序列化) - 前缀:
""(空字符串) - 过期时间: 无(永不过期)
- 空值处理: 不允许 null 值
步骤4: 设置值序列化器
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
jackson2JsonRedisSerializer
)
);
作用: 将默认的 Java 序列化器替换为 JSON 序列化器
SerializationPair:
- 作用: 封装序列化和反序列化器对
- 原理: 提供
read()和write()方法 - 效果: Redis 存储时使用 JSON 格式
9.2.3 redisConfigWithTtlInSeconds() 方法详解
public static RedisCacheConfiguration redisConfigWithTtlInSeconds(Integer seconds) {
RedisCacheConfiguration redisCacheConfiguration = redisDefalutConfig();
return redisCacheConfiguration.entryTtl(Duration.ofSeconds(seconds));
}
作用: 创建带过期时间的 Redis 缓存配置
执行流程:
- 调用
redisDefalutConfig()获取基础配置 - 设置过期时间为指定的秒数
- 返回配置对象
entryTtl(Duration.ofSeconds(seconds)):
- 作用: 设置缓存条目的生存时间(TTL)
- 原理: Redis 会自动删除过期的键
- 效果: 缓存数据在指定时间后自动失效
使用示例:
// 创建过期时间为1小时的配置
RedisCacheConfiguration config1 =
CacheUtils.redisConfigWithTtlInSeconds(3600);
// 创建过期时间为5分钟的配置
RedisCacheConfiguration config2 =
CacheUtils.redisConfigWithTtlInSeconds(300);
9.3 CacheHelper 源码解析
文件位置: pdfc.monitor.util.CacheHelper
设计目的: 封装缓存操作,提供高级功能(延迟双删、分布式锁)
9.3.1 类结构解析
@Component
public class CacheHelper {
private static final Logger logger = LogManager.getLogger(CacheHelper.class);
@Autowired(required = false)
private CacheManager cacheManager;
@Autowired(required = false)
private RedisTemplate<String, Object> redisTemplate;
public static final String CACHE_NAME_SYS_USER = "sysUser";
private static final long DEFAULT_DELAY_MILLIS = 500;
}
设计要点:
@Component: 注册为 Spring Bean,可被其他组件注入@Autowired(required = false): 依赖可选,如果 Bean 不存在不会报错- 降级处理: 当
CacheManager或RedisTemplate不可用时,方法会优雅降级
9.3.2 getCache() 方法解析
public Cache getCache(String cacheName) {
if (cacheManager == null) {
logger.warn("CacheManager 不可用,缓存功能已禁用");
return null;
}
return cacheManager.getCache(cacheName);
}
作用: 获取指定名称的缓存实例
执行流程:
- 检查
CacheManager是否可用 - 如果不可用,记录警告日志并返回
null - 如果可用,调用
CacheManager.getCache()获取缓存实例
为什么需要检查:
- 测试环境可能没有配置 Redis
- 系统启动时 Redis 可能未就绪
- 提供降级处理,避免系统崩溃
9.3.3 get() 方法解析
public <T> T get(String cacheName, Object key, Class<T> type) {
try {
Cache cache = getCache(cacheName);
if (cache != null) {
Cache.ValueWrapper wrapper = cache.get(key);
if (wrapper != null) {
Object value = wrapper.get();
if (value != null && type.isInstance(value)) {
logger.debug("从缓存获取数据,cacheName: {}, key: {}", cacheName, key);
return type.cast(value);
}
}
}
} catch (Exception e) {
logger.error("从缓存获取数据异常,cacheName: {}, key: {}", cacheName, key, e);
}
return null;
}
执行流程:
1. 获取缓存实例
├── cacheManager == null → 返回 null
└── cacheManager != null → 获取 Cache 实例
2. 查询缓存值
├── cache == null → 返回 null
└── cache != null → 调用 cache.get(key)
3. 处理查询结果
├── wrapper == null → 缓存未命中,返回 null
└── wrapper != null → 获取值
├── value == null → 返回 null
├── 类型不匹配 → 返回 null
└── 类型匹配 → 类型转换并返回
关键点:
- 类型安全: 使用泛型
<T>和Class<T>确保类型安全 - 类型检查:
type.isInstance(value)检查值是否为指定类型 - 类型转换:
type.cast(value)安全地转换类型 - 异常处理: 捕获所有异常,避免影响业务逻辑
ValueWrapper 的作用:
- Spring Cache 的包装类,用于处理 null 值
get()方法返回包装后的值,可以区分"缓存未命中"和"缓存值为 null"
9.3.4 put() 方法解析
public void put(String cacheName, Object key, Object value) {
try {
Cache cache = getCache(cacheName);
if (cache != null) {
cache.put(key, value);
logger.debug("数据写入缓存,cacheName: {}, key: {}", cacheName, key);
}
} catch (Exception e) {
logger.error("数据写入缓存异常,cacheName: {}, key: {}", cacheName, key, e);
}
}
执行流程:
- 获取缓存实例
- 如果缓存可用,调用
cache.put()写入 - 记录调试日志
- 捕获异常,避免影响业务
特点:
- 非阻塞: 写入失败不影响业务逻辑
- 日志记录: 记录操作日志,便于排查问题
9.3.5 evict() 方法解析
public void evict(String cacheName, Object key) {
try {
Cache cache = getCache(cacheName);
if (cache != null) {
cache.evict(key);
logger.debug("删除缓存,cacheName: {}, key: {}", cacheName, key);
}
} catch (Exception e) {
logger.error("删除缓存异常,cacheName: {}, key: {}", cacheName, key, e);
}
}
作用: 删除指定键的缓存
使用场景:
- 更新操作前删除缓存
- 删除操作后清理缓存
9.3.6 delayedDoubleDelete() 方法解析
public Runnable delayedDoubleDelete(String cacheName, Object key, long delayMillis) {
// 第一次删除缓存
evict(cacheName, key);
logger.info("延时双删:第一次删除缓存,cacheName: {}, key: {}", cacheName, key);
// 返回数据库操作函数
return () -> {
// 延时后第二次删除缓存(异步执行)
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(delayMillis);
evict(cacheName, key);
logger.info("延时双删:第二次删除缓存(延时{}ms后),cacheName: {}, key: {}",
delayMillis, cacheName, key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("延时双删:延时删除缓存被中断,cacheName: {}, key: {}", cacheName, key, e);
} catch (Exception e) {
logger.error("延时双删:延时删除缓存异常,cacheName: {}, key: {}", cacheName, key, e);
}
});
};
}
设计模式: 函数式编程 + 异步执行
执行流程:
1. 第一次删除缓存(同步执行)
└── evict(cacheName, key)
2. 返回 Runnable 对象(延迟执行函数)
└── 调用者执行数据库操作
3. 调用返回的 Runnable(触发延迟删除)
└── CompletableFuture.runAsync() 异步执行
└── Thread.sleep(delayMillis) 延迟
└── 第二次删除缓存
为什么返回 Runnable:
- 分离关注点: 缓存删除和数据库操作分离
- 灵活性: 调用者可以控制何时触发延迟删除
- 可测试性: 可以单独测试延迟删除逻辑
异步执行的原因:
- 不阻塞主流程: 延迟删除在后台线程执行
- 提高性能: 不等待延迟时间,立即返回
使用示例:
// 1. 获取延迟删除函数
Runnable delayedDelete = cacheHelper.delayedDoubleDelete(
CACHE_NAME_SYS_USER, id, 500);
// 2. 执行数据库操作
int result = sysUserDao.updateByPrimaryKey(po);
// 3. 触发延迟删除(异步执行第二次删除)
if (result > 0) {
delayedDelete.run();
}
9.3.7 executeWithLock() 方法解析
public <T> T executeWithLock(String lockKey, long timeout, Supplier<T> action) {
// 降级处理:Redis 不可用时直接执行
if (redisTemplate == null) {
logger.warn("RedisTemplate 不可用,跳过分布式锁,直接执行操作,lockKey: {}", lockKey);
return action.get();
}
String lockValue = String.valueOf(System.currentTimeMillis());
Boolean lockAcquired = false;
try {
// 尝试获取锁(SETNX + 过期时间)
lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, timeout, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lockAcquired)) {
logger.debug("获取分布式锁成功,lockKey: {}", lockKey);
return action.get(); // 执行操作
} else {
logger.warn("获取分布式锁失败,lockKey: {}", lockKey);
throw new RuntimeException("获取分布式锁失败");
}
} catch (Exception e) {
logger.error("执行分布式锁操作异常,lockKey: {}", lockKey, e);
throw new RuntimeException("执行分布式锁操作异常", e);
} finally {
// 释放锁(检查锁值,防止误删)
if (Boolean.TRUE.equals(lockAcquired) && redisTemplate != null) {
try {
Object currentValue = redisTemplate.opsForValue().get(lockKey);
if (lockValue.equals(String.valueOf(currentValue))) {
redisTemplate.delete(lockKey);
logger.debug("释放分布式锁成功,lockKey: {}", lockKey);
}
} catch (Exception e) {
logger.error("释放分布式锁异常,lockKey: {}", lockKey, e);
}
}
}
}
分布式锁实现原理:
1. 获取锁
lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, timeout, TimeUnit.SECONDS);
Redis 命令: SET key value NX EX timeout
参数解析:
lockKey: 锁的键,格式如lock:sysUser:update:1lockValue: 锁的值,使用时间戳确保唯一性NX: 仅当键不存在时设置(setIfAbsent)EX timeout: 设置过期时间(秒)
为什么需要过期时间:
- 防止死锁: 如果持有锁的线程崩溃,锁会自动释放
- 避免长时间占用: 限制锁的最大持有时间
2. 执行操作
if (Boolean.TRUE.equals(lockAcquired)) {
return action.get(); // 执行传入的操作
}
设计模式: 函数式编程,将操作作为参数传入
3. 释放锁
Object currentValue = redisTemplate.opsForValue().get(lockKey);
if (lockValue.equals(String.valueOf(currentValue))) {
redisTemplate.delete(lockKey);
}
为什么需要检查锁值:
- 防止误删: 如果锁已过期并被其他线程获取,不应该删除
- 安全性: 确保只删除自己持有的锁
场景示例:
时间线:
T1: 线程A获取锁,lockValue = "1000"
T2: 线程A执行操作(耗时5秒)
T3: 锁过期(3秒后),线程B获取锁,lockValue = "4000"
T4: 线程A操作完成,尝试释放锁
├── 检查 lockValue == "1000"
├── 当前 lockValue == "4000"
└── 不删除锁(避免误删线程B的锁)
降级处理:
if (redisTemplate == null) {
return action.get(); // 直接执行,不使用锁
}
原因: 测试环境可能没有 Redis,需要降级处理
9.3.8 evictBatch() 方法解析
public void evictBatch(String cacheName, Iterable<?> keys) {
if (keys == null) {
return;
}
for (Object key : keys) {
evict(cacheName, key);
}
logger.debug("批量删除缓存,cacheName: {}, count: {}", cacheName,
keys instanceof Collection ? ((Collection<?>) keys).size() : "unknown");
}
作用: 批量删除缓存
实现方式: 循环调用 evict() 方法
优化建议: 可以使用 Redis 的 DEL 命令批量删除(需要直接操作 Redis)
使用场景: 批量删除操作后清理缓存
十、多级缓存与多缓存数据库支持
10.1 框架扩展性分析
10.1.1 架构设计优势
ins-framework-cache 基于 Spring Cache 抽象层设计,具有高度的扩展性:
- 接口抽象: 使用
CacheManager和Cache接口,不依赖具体实现 - 可替换性: 可以通过配置覆盖默认的
CacheManager - 多实现支持: 框架已提供多种
CacheManager实现(Redis、ConcurrentMap、NoOp)
10.1.2 当前支持的缓存类型
从 CacheConfig 源码分析,框架当前提供:
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
// 1. Redis 缓存(默认)
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager() { ... }
// 2. 内存缓存(开发/测试)
@Bean(name = "concurrentMapCacheManager")
public ConcurrentMapCacheManager concurrentMapCacheManager() { ... }
// 3. 无操作缓存(测试)
@Bean(name = "noOpCacheManager")
public NoOpCacheManager noOpCacheManager() { ... }
}
10.1.3 扩展机制
框架支持通过覆盖配置来使用其他缓存实现:
@Configuration
@ConditionalOnBean(value = CacheConfig.class)
public class CustomCacheConfig {
@Bean
@Primary // 覆盖默认的 CacheManager
public CacheManager customCacheManager() {
// 返回自定义的 CacheManager
return new CustomCacheManager();
}
}
10.2 Spring Cache 支持的缓存实现
Spring Cache 抽象层理论上可以支持任何实现了 Cache 和 CacheManager 接口的实现。以下是常见的缓存实现:
10.2.1 官方支持的缓存实现
| 缓存类型 | 依赖包 | 适用场景 | 性能 | 分布式支持 |
|---|---|---|---|---|
| Redis | spring-boot-starter-data-redis |
生产环境,分布式缓存 | ⭐⭐⭐⭐⭐ | ✅ |
| Caffeine | com.github.ben-manes.caffeine:caffeine |
本地缓存,高性能 | ⭐⭐⭐⭐⭐ | ❌ |
| EhCache | net.sf.ehcache:ehcache |
本地/分布式缓存 | ⭐⭐⭐⭐ | ✅ |
| Hazelcast | com.hazelcast:hazelcast |
分布式缓存,内存网格 | ⭐⭐⭐⭐ | ✅ |
| Infinispan | org.infinispan:infinispan-core |
分布式缓存,数据网格 | ⭐⭐⭐⭐ | ✅ |
| ConcurrentMap | Spring 内置 | 开发/测试,单机 | ⭐⭐⭐ | ❌ |
| NoOp | Spring 内置 | 测试,禁用缓存 | - | - |
10.2.2 需要自定义实现的缓存
| 缓存类型 | 实现难度 | 适用场景 | 说明 |
|---|---|---|---|
| MongoDB | 中等 | 文档缓存,复杂查询 | 需要实现 Cache 接口 |
| Elasticsearch | 较高 | 全文搜索缓存 | 需要实现 Cache 接口 |
| Cassandra | 中等 | 列式存储缓存 | 需要实现 Cache 接口 |
| Memcached | 简单 | 简单键值缓存 | 可以使用第三方库 |
10.3 多级缓存架构设计
10.3.1 多级缓存概念
多级缓存是指将缓存分为多个层级,通常包括:
-
L1 缓存(本地缓存):
- 存储位置:应用进程内存
- 特点:访问速度最快,但容量有限
- 实现:Caffeine、Guava Cache、ConcurrentHashMap
-
L2 缓存(分布式缓存):
- 存储位置:独立的缓存服务器
- 特点:容量大,支持分布式,但访问速度较慢
- 实现:Redis、EhCache、Hazelcast
-
L3 缓存(持久化缓存)(可选):
- 存储位置:数据库或文件系统
- 特点:容量最大,但访问速度最慢
- 实现:MongoDB、Elasticsearch、PostgreSQL
10.3.2 多级缓存架构图
┌─────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ - Service 层业务逻辑 │
│ - 调用 CacheHelper 工具类 │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ CacheHelper 工具类 │
│ - 封装多级缓存操作 │
│ - 实现缓存穿透策略 │
└────────────────────┬────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ L1: 本地缓存 │ │ L2: 分布式缓存 │
│ (Caffeine) │ │ (Redis) │
│ - 快速访问 │ │ - 大容量 │
│ - 小容量 │ │ - 分布式 │
└─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ L3: 持久化缓存 │
│ (MongoDB/ES) │
│ - 超大容量 │
│ - 复杂查询 │
└─────────────────┘
10.3.3 多级缓存查询流程
查询请求
│
▼
┌─────────────────┐
│ 1. 查询 L1 缓存 │
│ (Caffeine) │
└────┬────────────┘
│
├─── 命中 ────┐
│ │
│ ▼
│ ┌──────────────┐
│ │ 返回数据 │
│ └──────────────┘
│
└─── 未命中 ───┐
│
▼
┌─────────────────┐
│ 2. 查询 L2 缓存 │
│ (Redis) │
└────┬────────────┘
│
├─── 命中 ────┐
│ │
│ ▼
│ ┌─────────────────┐
│ │ 写入 L1 缓存 │
│ │ 返回数据 │
│ └─────────────────┘
│
└─── 未命中 ───┐
│
▼
┌─────────────────┐
│ 3. 查询 L3 缓存 │
│ (MongoDB/ES) │
└────┬────────────┘
│
├─── 命中 ────┐
│ │
│ ▼
│ ┌─────────────────┐
│ │ 写入 L2 和 L1 │
│ │ 返回数据 │
│ └─────────────────┘
│
└─── 未命中 ───┐
│
▼
┌─────────────────┐
│ 4. 查询数据库 │
│ (PostgreSQL) │
└────┬────────────┘
│
▼
┌─────────────────┐
│ 写入 L3、L2、L1 │
│ 返回数据 │
└─────────────────┘
10.4 集成其他缓存数据库
10.4.1 Caffeine 本地缓存
Caffeine 是一个高性能的 Java 本地缓存库,性能优于 Guava Cache。
1. 添加依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2. 配置 CaffeineCacheManager
@Configuration
@ConditionalOnBean(value = CacheConfig.class)
public class CaffeineCacheConfig {
@Bean
@Primary
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 配置缓存规格
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.maximumSize(10000) // 最大缓存数量
.expireAfterWrite(1, TimeUnit.HOURS) // 写入后1小时过期
.expireAfterAccess(30, TimeUnit.MINUTES) // 访问后30分钟过期
.recordStats(); // 启用统计
cacheManager.setCaffeine(caffeine);
cacheManager.setCacheNames(Arrays.asList("sysUser", "hotData"));
cacheManager.setAllowNullValues(true);
return cacheManager;
}
}
3. 多级缓存实现(Caffeine + Redis)
@Configuration
public class MultiLevelCacheConfig {
@Bean
@Primary
public CacheManager multiLevelCacheManager(
RedisConnectionFactory redisConnectionFactory) {
// L1: Caffeine 本地缓存
CaffeineCacheManager l1CacheManager = new CaffeineCacheManager();
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES);
l1CacheManager.setCaffeine(caffeine);
// L2: Redis 分布式缓存
RedisCacheManager l2CacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(CacheUtils.redisConfigWithTtlInSeconds(3600))
.build();
// 组合多级缓存
return new CompositeCacheManager(l1CacheManager, l2CacheManager);
}
}
注意: CompositeCacheManager 会按顺序查询所有 CacheManager,第一个命中的返回结果。
10.4.2 EhCache 分布式缓存
EhCache 是一个成熟的 Java 缓存框架,支持本地和分布式缓存。
1. 添加依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.9.2</version>
</dependency>
2. 配置 ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd">
<cache name="sysUser"
maxEntriesLocalHeap="10000"
timeToLiveSeconds="3600"
timeToIdleSeconds="1800"
memoryStoreEvictionPolicy="LRU">
</cache>
</ehcache>
3. 配置 EhCacheCacheManager
@Configuration
public class EhCacheConfig {
@Bean
public CacheManager ehCacheCacheManager() {
EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
factory.setConfigLocation(new ClassPathResource("ehcache.xml"));
factory.setShared(true);
factory.afterPropertiesSet();
EhCacheCacheManager cacheManager = new EhCacheCacheManager();
cacheManager.setCacheManager(factory.getObject());
return cacheManager;
}
}
10.4.3 MongoDB 作为缓存
MongoDB 可以作为持久化缓存使用,特别适合存储复杂文档结构的数据。
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2. 实现 MongoDB Cache
public class MongoCache implements Cache {
private final String name;
private final MongoTemplate mongoTemplate;
private final long ttl; // 过期时间(秒)
public MongoCache(String name, MongoTemplate mongoTemplate, long ttl) {
this.name = name;
this.mongoTemplate = mongoTemplate;
this.ttl = ttl;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return mongoTemplate;
}
@Override
public ValueWrapper get(Object key) {
CacheDocument doc = mongoTemplate.findById(key.toString(),
CacheDocument.class, name);
if (doc == null) {
return null;
}
// 检查是否过期
if (System.currentTimeMillis() > doc.getExpireTime()) {
evict(key);
return null;
}
return () -> doc.getValue();
}
@Override
public <T> T get(Object key, Class<T> type) {
ValueWrapper wrapper = get(key);
return wrapper != null ? (T) wrapper.get() : null;
}
@Override
public void put(Object key, Object value) {
CacheDocument doc = new CacheDocument();
doc.setId(key.toString());
doc.setValue(value);
doc.setExpireTime(System.currentTimeMillis() + ttl * 1000);
doc.setCreateTime(System.currentTimeMillis());
mongoTemplate.save(doc, name);
}
@Override
public void evict(Object key) {
mongoTemplate.remove(Query.query(Criteria.where("_id").is(key.toString())),
name);
}
@Override
public void clear() {
mongoTemplate.dropCollection(name);
}
}
// 缓存文档实体
@Document(collection = "cache")
public class CacheDocument {
@Id
private String id;
private Object value;
private Long expireTime;
private Long createTime;
// getters and setters
}
3. 配置 MongoDB CacheManager
@Configuration
public class MongoCacheConfig {
@Autowired
private MongoTemplate mongoTemplate;
@Bean
public CacheManager mongoCacheManager() {
MongoCacheManager cacheManager = new MongoCacheManager();
cacheManager.setMongoTemplate(mongoTemplate);
cacheManager.setDefaultTtl(3600); // 默认1小时过期
return cacheManager;
}
}
10.4.4 Elasticsearch 作为缓存
Elasticsearch 可以作为全文搜索缓存,适合存储需要复杂查询的数据。
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2. 实现 Elasticsearch Cache
public class ElasticsearchCache implements Cache {
private final String name;
private final ElasticsearchRestTemplate esTemplate;
private final long ttl;
public ElasticsearchCache(String name, ElasticsearchRestTemplate esTemplate, long ttl) {
this.name = name;
this.esTemplate = esTemplate;
this.ttl = ttl;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return esTemplate;
}
@Override
public ValueWrapper get(Object key) {
GetRequest getRequest = new GetRequest(name, key.toString());
GetResponse response = esTemplate.getClient().get(getRequest, RequestOptions.DEFAULT);
if (!response.isExists()) {
return null;
}
Map<String, Object> source = response.getSourceAsMap();
Long expireTime = (Long) source.get("expireTime");
// 检查是否过期
if (System.currentTimeMillis() > expireTime) {
evict(key);
return null;
}
return () -> source.get("value");
}
@Override
public <T> T get(Object key, Class<T> type) {
ValueWrapper wrapper = get(key);
return wrapper != null ? (T) wrapper.get() : null;
}
@Override
public void put(Object key, Object value) {
Map<String, Object> document = new HashMap<>();
document.put("value", value);
document.put("expireTime", System.currentTimeMillis() + ttl * 1000);
document.put("createTime", System.currentTimeMillis());
IndexRequest request = new IndexRequest(name)
.id(key.toString())
.source(document);
esTemplate.getClient().index(request, RequestOptions.DEFAULT);
}
@Override
public void evict(Object key) {
DeleteRequest request = new DeleteRequest(name, key.toString());
esTemplate.getClient().delete(request, RequestOptions.DEFAULT);
}
@Override
public void clear() {
// 删除索引
DeleteIndexRequest request = new DeleteIndexRequest(name);
esTemplate.getClient().indices().delete(request, RequestOptions.DEFAULT);
}
}
3. 配置 Elasticsearch CacheManager
@Configuration
public class ElasticsearchCacheConfig {
@Autowired
private ElasticsearchRestTemplate esTemplate;
@Bean
public CacheManager elasticsearchCacheManager() {
ElasticsearchCacheManager cacheManager = new ElasticsearchCacheManager();
cacheManager.setEsTemplate(esTemplate);
cacheManager.setDefaultTtl(3600);
return cacheManager;
}
}
10.5 多级缓存实现方案
10.5.1 方案一:CompositeCacheManager(推荐)
使用 Spring 提供的 CompositeCacheManager 组合多个缓存管理器:
@Configuration
public class MultiLevelCacheConfig {
@Bean
@Primary
public CacheManager multiLevelCacheManager(
RedisConnectionFactory redisConnectionFactory,
MongoTemplate mongoTemplate) {
// L1: Caffeine 本地缓存(最快)
CaffeineCacheManager l1 = new CaffeineCacheManager();
l1.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
// L2: Redis 分布式缓存(快速)
RedisCacheManager l2 = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(CacheUtils.redisConfigWithTtlInSeconds(3600))
.build();
// L3: MongoDB 持久化缓存(慢速,但容量大)
MongoCacheManager l3 = new MongoCacheManager(mongoTemplate);
l3.setDefaultTtl(86400); // 1天
// 组合:按顺序查询,第一个命中即返回
return new CompositeCacheManager(l1, l2, l3);
}
}
查询顺序: L1 → L2 → L3 → 数据库
10.5.2 方案二:自定义多级缓存管理器
实现自定义的多级缓存管理器,支持更精细的控制:
public class MultiLevelCacheManager implements CacheManager {
private final List<CacheManager> cacheManagers;
public MultiLevelCacheManager(CacheManager... cacheManagers) {
this.cacheManagers = Arrays.asList(cacheManagers);
}
@Override
public Cache getCache(String name) {
List<Cache> caches = new ArrayList<>();
for (CacheManager manager : cacheManagers) {
Cache cache = manager.getCache(name);
if (cache != null) {
caches.add(cache);
}
}
return new MultiLevelCache(name, caches);
}
@Override
public Collection<String> getCacheNames() {
Set<String> names = new HashSet<>();
for (CacheManager manager : cacheManagers) {
names.addAll(manager.getCacheNames());
}
return names;
}
}
public class MultiLevelCache implements Cache {
private final String name;
private final List<Cache> caches; // 按优先级排序
public MultiLevelCache(String name, List<Cache> caches) {
this.name = name;
this.caches = caches;
}
@Override
public ValueWrapper get(Object key) {
// 按顺序查询
for (Cache cache : caches) {
ValueWrapper wrapper = cache.get(key);
if (wrapper != null) {
// 命中后,写入前面的缓存(缓存预热)
int index = caches.indexOf(cache);
for (int i = 0; i < index; i++) {
caches.get(i).put(key, wrapper.get());
}
return wrapper;
}
}
return null;
}
@Override
public void put(Object key, Object value) {
// 写入所有缓存
for (Cache cache : caches) {
cache.put(key, value);
}
}
@Override
public void evict(Object key) {
// 删除所有缓存
for (Cache cache : caches) {
cache.evict(key);
}
}
@Override
public void clear() {
// 清空所有缓存
for (Cache cache : caches) {
cache.clear();
}
}
// ... 其他方法
}
10.5.3 方案三:在 CacheHelper 中实现多级缓存
修改 CacheHelper 工具类,支持多级缓存查询:
@Component
public class MultiLevelCacheHelper {
@Autowired(required = false)
private CacheManager l1CacheManager; // Caffeine
@Autowired(required = false)
private CacheManager l2CacheManager; // Redis
@Autowired(required = false)
private CacheManager l3CacheManager; // MongoDB/ES
public <T> T get(String cacheName, Object key, Class<T> type) {
// L1: 查询本地缓存
T value = getFromCache(l1CacheManager, cacheName, key, type);
if (value != null) {
logger.debug("L1缓存命中,cacheName: {}, key: {}", cacheName, key);
return value;
}
// L2: 查询 Redis
value = getFromCache(l2CacheManager, cacheName, key, type);
if (value != null) {
logger.debug("L2缓存命中,写入L1,cacheName: {}, key: {}", cacheName, key);
putToCache(l1CacheManager, cacheName, key, value); // 写入L1
return value;
}
// L3: 查询 MongoDB/ES
value = getFromCache(l3CacheManager, cacheName, key, type);
if (value != null) {
logger.debug("L3缓存命中,写入L2和L1,cacheName: {}, key: {}", cacheName, key);
putToCache(l2CacheManager, cacheName, key, value); // 写入L2
putToCache(l1CacheManager, cacheName, key, value); // 写入L1
return value;
}
return null;
}
public void put(String cacheName, Object key, Object value) {
// 写入所有缓存层级
putToCache(l1CacheManager, cacheName, key, value);
putToCache(l2CacheManager, cacheName, key, value);
putToCache(l3CacheManager, cacheName, key, value);
}
private <T> T getFromCache(CacheManager manager, String cacheName,
Object key, Class<T> type) {
if (manager == null) {
return null;
}
Cache cache = manager.getCache(cacheName);
if (cache != null) {
Cache.ValueWrapper wrapper = cache.get(key);
if (wrapper != null) {
Object value = wrapper.get();
if (value != null && type.isInstance(value)) {
return type.cast(value);
}
}
}
return null;
}
private void putToCache(CacheManager manager, String cacheName,
Object key, Object value) {
if (manager != null) {
Cache cache = manager.getCache(cacheName);
if (cache != null) {
cache.put(key, value);
}
}
}
}
10.6 项目适用性分析
10.6.1 当前项目分析
项目特点:
- 使用
ins-framework-cache中间件 - 基于 Spring Cache 抽象层
- 当前使用 Redis 作为缓存
- 支持通过配置覆盖
CacheManager
扩展可行性: ✅ 完全可行
10.6.2 适用场景分析
| 缓存类型 | 适用场景 | 项目是否适用 | 推荐度 |
|---|---|---|---|
| Redis | 分布式缓存,高性能 | ✅ 已使用 | ⭐⭐⭐⭐⭐ |
| Caffeine | 本地缓存,减少网络开销 | ✅ 适合 | ⭐⭐⭐⭐⭐ |
| EhCache | 本地/分布式缓存 | ✅ 适合 | ⭐⭐⭐⭐ |
| MongoDB | 文档缓存,复杂查询 | ⚠️ 需评估 | ⭐⭐⭐ |
| Elasticsearch | 全文搜索缓存 | ⚠️ 需评估 | ⭐⭐ |
10.6.3 推荐方案
方案一:Caffeine + Redis(推荐)
L1: Caffeine(本地缓存)
↓ 未命中
L2: Redis(分布式缓存)
↓ 未命中
数据库
优势:
- ✅ 减少 Redis 网络开销
- ✅ 提高响应速度
- ✅ 实现简单
- ✅ 性能最优
配置示例:
@Configuration
@ConditionalOnBean(value = CacheConfig.class)
public class MultiLevelCacheConfig {
@Bean
@Primary
public CacheManager multiLevelCacheManager(
RedisConnectionFactory redisConnectionFactory) {
// L1: Caffeine
CaffeineCacheManager l1 = new CaffeineCacheManager();
l1.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats());
// L2: Redis
RedisCacheManager l2 = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(CacheUtils.redisConfigWithTtlInSeconds(3600))
.build();
return new CompositeCacheManager(l1, l2);
}
}
方案二:Redis + MongoDB(持久化缓存)
适用于需要长期存储缓存数据的场景。
L1: Redis(热数据)
↓ 未命中
L2: MongoDB(冷数据)
↓ 未命中
数据库
10.6.4 实施建议
-
第一阶段: 引入 Caffeine 作为 L1 缓存
- 减少 Redis 压力
- 提高响应速度
- 风险低,收益高
-
第二阶段: 根据业务需求考虑 L3 缓存
- 如果数据量大,考虑 MongoDB
- 如果需要全文搜索,考虑 Elasticsearch
-
注意事项:
- 多级缓存的一致性需要特别处理
- 延迟双删策略需要应用到所有缓存层级
- 监控各层缓存的命中率
十一、总结
11.1 框架优势
- 开箱即用: 基于 Spring Boot 自动配置,无需复杂配置
- 灵活扩展: 支持自定义缓存管理器、过期时间等
- 高性能: 基于 Redis,支持高并发访问
- 易用性: 提供统一的缓存接口,简化使用
11.2 项目实践总结
在本项目中,我们实现了:
- ✅ 完整的缓存策略: 查询、新增、更新、删除都实现了缓存操作
- ✅ 延迟双删策略: 确保更新/删除操作的缓存一致性
- ✅ 分布式锁: 保证更新操作的原子性
- ✅ 降级处理: Redis 不可用时自动降级,不影响业务
- ✅ 完善的测试: 6个测试用例覆盖所有缓存场景
11.3 注意事项
- 缓存一致性: 使用延迟双删策略保证最终一致性
- 性能优化: 合理设置过期时间,避免缓存穿透和雪崩
- 监控告警: 监控缓存命中率和性能指标
- 降级处理: 确保 Redis 不可用时系统仍能正常运行
11.4 参考资料
文档版本: 1.0
最后更新: 2025-11-25
作者: Allen
更多推荐

所有评论(0)