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 核心特性

  1. 自动配置: 基于 Spring Boot 自动配置,开箱即用
  2. 多种缓存管理器: 支持 Redis、内存缓存、无操作缓存
  3. 灵活配置: 支持自定义缓存过期时间、序列化方式
  4. 集群支持: 支持 Redis 单节点和集群模式
  5. 键生成策略: 提供默认的缓存键生成器

二、架构设计

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

职责:

  • 配置 CacheManager Bean(默认使用 Redis)
  • 配置 KeyGenerator Bean(缓存键生成器)
  • 提供 ConcurrentMapCacheManager Bean(内存缓存)
  • 提供 NoOpCacheManager Bean(无操作缓存,用于测试)

关键代码:

@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)│
└─────────────────┘

延迟双删原理:

  1. 第一次删除: 在更新数据库之前删除缓存,防止其他线程读取到旧数据
  2. 更新数据库: 执行数据库更新操作
  3. 第二次删除: 延迟 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 未正确注入

排查步骤:

  1. 检查 Redis 服务状态
  2. 检查 application.properties 配置
  3. 查看日志中的缓存操作记录
  4. 检查 CacheManager Bean 是否存在
问题2: 缓存数据不一致

可能原因:

  • 延迟双删未正确执行
  • 分布式锁获取失败
  • 数据库更新失败但缓存已删除

排查步骤:

  1. 检查日志中的延迟双删执行记录
  2. 验证分布式锁是否正常工作
  3. 确保数据库操作在事务中执行
问题3: 缓存性能问题

可能原因:

  • 缓存键设计不合理
  • 缓存过期时间设置不当
  • Redis 内存不足

排查步骤:

  1. 优化缓存键设计
  2. 调整缓存过期时间
  3. 增加 Redis 内存或清理无用缓存
  4. 使用 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

执行流程:

  1. Spring 启动时调用此方法
  2. 返回 RedisCacheManager 实例
  3. Spring 将返回的对象注册为 CacheManager 类型的 Bean
  4. 其他组件通过 @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
    );
}

参数解析:

  1. RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory):

    • 作用: 创建非阻塞的 Redis 缓存写入器
    • 原理: 使用 RedisConnectionFactory 创建 Redis 连接
    • 特点: 非阻塞写入,性能更高,但可能出现并发写入问题(适合读多写少场景)
  2. defaultConfig:

    • 作用: 默认的缓存配置
    • 内容: 包含过期时间、序列化方式等
    • 使用: 当缓存名称不在 customConfigMap 中时,使用此配置
  3. 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);

配置解析:

  1. setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY):

    • 作用: 设置所有属性(字段、getter、setter)都可见
    • 效果: 序列化时包含所有字段,不受访问修饰符限制
  2. 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 缓存配置

执行流程:

  1. 调用 redisDefalutConfig() 获取基础配置
  2. 设置过期时间为指定的秒数
  3. 返回配置对象

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;
}

设计要点:

  1. @Component: 注册为 Spring Bean,可被其他组件注入
  2. @Autowired(required = false): 依赖可选,如果 Bean 不存在不会报错
  3. 降级处理: 当 CacheManagerRedisTemplate 不可用时,方法会优雅降级
9.3.2 getCache() 方法解析
public Cache getCache(String cacheName) {
    if (cacheManager == null) {
        logger.warn("CacheManager 不可用,缓存功能已禁用");
        return null;
    }
    return cacheManager.getCache(cacheName);
}

作用: 获取指定名称的缓存实例

执行流程:

  1. 检查 CacheManager 是否可用
  2. 如果不可用,记录警告日志并返回 null
  3. 如果可用,调用 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
       └── 类型匹配 → 类型转换并返回

关键点:

  1. 类型安全: 使用泛型 <T>Class<T> 确保类型安全
  2. 类型检查: type.isInstance(value) 检查值是否为指定类型
  3. 类型转换: type.cast(value) 安全地转换类型
  4. 异常处理: 捕获所有异常,避免影响业务逻辑

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);
    }
}

执行流程:

  1. 获取缓存实例
  2. 如果缓存可用,调用 cache.put() 写入
  3. 记录调试日志
  4. 捕获异常,避免影响业务

特点:

  • 非阻塞: 写入失败不影响业务逻辑
  • 日志记录: 记录操作日志,便于排查问题
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:1
  • lockValue: 锁的值,使用时间戳确保唯一性
  • 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 抽象层设计,具有高度的扩展性:

  1. 接口抽象: 使用 CacheManagerCache 接口,不依赖具体实现
  2. 可替换性: 可以通过配置覆盖默认的 CacheManager
  3. 多实现支持: 框架已提供多种 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 抽象层理论上可以支持任何实现了 CacheCacheManager 接口的实现。以下是常见的缓存实现:

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 多级缓存概念

多级缓存是指将缓存分为多个层级,通常包括:

  1. L1 缓存(本地缓存):

    • 存储位置:应用进程内存
    • 特点:访问速度最快,但容量有限
    • 实现:Caffeine、Guava Cache、ConcurrentHashMap
  2. L2 缓存(分布式缓存):

    • 存储位置:独立的缓存服务器
    • 特点:容量大,支持分布式,但访问速度较慢
    • 实现:Redis、EhCache、Hazelcast
  3. 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 实施建议
  1. 第一阶段: 引入 Caffeine 作为 L1 缓存

    • 减少 Redis 压力
    • 提高响应速度
    • 风险低,收益高
  2. 第二阶段: 根据业务需求考虑 L3 缓存

    • 如果数据量大,考虑 MongoDB
    • 如果需要全文搜索,考虑 Elasticsearch
  3. 注意事项:

    • 多级缓存的一致性需要特别处理
    • 延迟双删策略需要应用到所有缓存层级
    • 监控各层缓存的命中率

十一、总结

11.1 框架优势

  1. 开箱即用: 基于 Spring Boot 自动配置,无需复杂配置
  2. 灵活扩展: 支持自定义缓存管理器、过期时间等
  3. 高性能: 基于 Redis,支持高并发访问
  4. 易用性: 提供统一的缓存接口,简化使用

11.2 项目实践总结

在本项目中,我们实现了:

  1. 完整的缓存策略: 查询、新增、更新、删除都实现了缓存操作
  2. 延迟双删策略: 确保更新/删除操作的缓存一致性
  3. 分布式锁: 保证更新操作的原子性
  4. 降级处理: Redis 不可用时自动降级,不影响业务
  5. 完善的测试: 6个测试用例覆盖所有缓存场景

11.3 注意事项

  1. 缓存一致性: 使用延迟双删策略保证最终一致性
  2. 性能优化: 合理设置过期时间,避免缓存穿透和雪崩
  3. 监控告警: 监控缓存命中率和性能指标
  4. 降级处理: 确保 Redis 不可用时系统仍能正常运行

11.4 参考资料


文档版本: 1.0
最后更新: 2025-11-25
作者: Allen

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐