Skip to content

Redis缓存 (redis)

概述

ruoyi-common-redis 是一个基于 Redisson 实现的综合性Redis缓存模块,提供了完整的缓存解决方案,包括基础缓存操作、分布式锁、队列管理、ID生成器等功能。该模块采用二级缓存架构,结合Redis分布式缓存和Caffeine本地缓存,为应用提供高性能的缓存服务。

核心特性

  • 🚀 高性能二级缓存:Redis + Caffeine 双重缓存架构
  • 🔒 分布式锁:基于 Redisson 的分布式锁机制
  • 📊 分布式ID生成:支持多种格式的唯一ID生成
  • 🎯 队列管理:普通队列、延迟队列、优先队列等
  • ⚡ 限流控制:基于令牌桶的限流机制
  • 📡 发布订阅:Redis消息发布订阅功能
  • 🔧 灵活配置:支持单机和集群模式
  • 💡 智能序列化:JSON序列化,支持Java8时间类型

架构设计

mermaid
graph TB
    A[应用层] --> B[CacheUtils工具层]
    B --> C[PlusSpringCacheManager]
    C --> D[CaffeineCacheDecorator]
    D --> E[Caffeine本地缓存]
    D --> F[Redis分布式缓存]
    
    A --> G[RedisUtils工具层]
    G --> H[RedissonClient]
    
    A --> I[SequenceUtils]
    I --> H
    
    A --> J[QueueUtils]
    J --> H

快速开始

1. 添加依赖

xml
<dependency>
    <groupId>plus.ruoyi</groupId>
    <artifactId>ruoyi-common-redis</artifactId>
</dependency>

2. 配置文件

开发环境配置示例

yaml
################## Redis缓存配置 ##################
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
  redis:
    # 地址
    host: localhost
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # redis 密码必须配置
#   password: ruoyi123
    # 连接超时时间
    timeout: 10s
    # 是否开启ssl
    ssl.enabled: false

# redisson 配置
redisson:
  # redis key前缀(使用应用ID作为前缀)
  keyPrefix: ${app.id}
  # 线程池数量(开发环境适中配置)
  threads: 4
  # Netty线程池数量
  nettyThreads: 8
  # 单节点配置
  singleServerConfig:
    # 客户端名称(使用应用ID)
    clientName: ${app.id}
    # 最小空闲连接数(开发环境较小值)
    connectionMinimumIdleSize: 8
    # 连接池大小(开发环境适中配置)
    connectionPoolSize: 32
    # 连接空闲超时,单位:毫秒
    idleConnectionTimeout: 10000
    # 命令等待超时,单位:毫秒
    timeout: 3000
    # 发布和订阅连接池大小
    subscriptionConnectionPoolSize: 50

生产环境配置建议

yaml
spring.data:
  redis:
    host: your-redis-host
    port: 6379
    database: 0
    password: your-secure-password
    timeout: 10s
    ssl.enabled: true  # 生产环境建议开启SSL

redisson:
  keyPrefix: ${app.id}
  threads: 16        # 生产环境可适当增加
  nettyThreads: 32   # 生产环境可适当增加
  singleServerConfig:
    clientName: ${app.id}
    connectionMinimumIdleSize: 16  # 生产环境增加连接数
    connectionPoolSize: 64         # 生产环境增加连接池大小
    idleConnectionTimeout: 10000
    timeout: 3000
    subscriptionConnectionPoolSize: 50

配置说明

核心配置参数

Spring Data Redis 配置

  • spring.data.redis.host: Redis服务器地址(开发环境通常为localhost)
  • spring.data.redis.port: Redis端口(默认6379)
  • spring.data.redis.database: 数据库索引(0-15,默认为0)
  • spring.data.redis.password: 连接密码(开发环境可选,生产环境必须)
  • spring.data.redis.timeout: 连接超时时间
  • spring.data.redis.ssl.enabled: 是否启用SSL(生产环境建议开启)

Redisson 配置

  • redisson.keyPrefix: Redis key前缀,支持使用${app.id}占位符
  • redisson.threads: 用于执行各种Redis操作的线程池大小
  • redisson.nettyThreads: Netty框架使用的线程池大小

单机服务器配置

  • clientName: 客户端连接名称,便于监控识别
  • connectionMinimumIdleSize: 最小空闲连接数,保持一定的连接预热
  • connectionPoolSize: 连接池最大连接数
  • idleConnectionTimeout: 连接空闲超时时间(毫秒)
  • timeout: 单个Redis操作的超时时间(毫秒)
  • subscriptionConnectionPoolSize: 发布订阅专用连接池大小

环境差异配置

开发环境特点

  • 较小的连接池配置,节省资源
  • 可以不设置密码,简化开发
  • 关闭SSL,减少配置复杂度
  • 使用localhost本地Redis

生产环境建议

  • 增加连接池大小,提高并发能力
  • 必须设置强密码
  • 开启SSL加密传输
  • 使用独立的Redis服务器或集群

1. CacheUtils - 通用缓存工具

提供基于Spring Cache抽象的统一缓存操作接口。

基本用法

java
// 存储缓存
CacheUtils.put("userCache", "user:123", userObj);

// 获取缓存
User user = CacheUtils.get("userCache", "user:123");

// 删除缓存
CacheUtils.evict("userCache", "user:123");

// 清空缓存组
CacheUtils.clear("userCache");

2. RedisUtils - Redis操作工具

基于Redisson实现的完整Redis操作工具类。

基础缓存操作

java
// 设置缓存(永不过期)
RedisUtils.setCacheObject("user:123", user);

// 设置缓存并指定过期时间
RedisUtils.setCacheObject("user:123", user, Duration.ofHours(1));

// 获取缓存
User user = RedisUtils.getCacheObject("user:123");

// 删除缓存
RedisUtils.deleteObject("user:123");

// 检查是否存在
boolean exists = RedisUtils.isExistsObject("user:123");

// 设置过期时间
RedisUtils.expire("user:123", Duration.ofMinutes(30));

条件设置操作

java
// 仅当key不存在时设置
boolean success = RedisUtils.setObjectIfAbsent("user:123", user, Duration.ofHours(1));

// 仅当key存在时设置
boolean success = RedisUtils.setObjectIfExists("user:123", user, Duration.ofHours(1));

集合操作

List操作

java
// 缓存List数据
List<String> dataList = Arrays.asList("item1", "item2", "item3");
RedisUtils.setCacheList("myList", dataList);

// 追加单个数据
RedisUtils.addCacheList("myList", "item4");

// 获取完整List
List<String> list = RedisUtils.getCacheList("myList");

// 获取指定范围
List<String> range = RedisUtils.getCacheListRange("myList", 0, 10);

Set操作

java
// 缓存Set数据
Set<String> dataSet = Sets.newHashSet("item1", "item2");
RedisUtils.setCacheSet("mySet", dataSet);

// 添加元素
RedisUtils.addCacheSet("mySet", "item3");

// 获取Set数据
Set<String> set = RedisUtils.getCacheSet("mySet");

Map操作

java
// 缓存Map数据
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("name", "张三");
dataMap.put("age", 25);
RedisUtils.setCacheMap("userInfo", dataMap);

// 设置Map中的单个值
RedisUtils.setCacheMapValue("userInfo", "city", "北京");

// 获取Map中的单个值
String name = RedisUtils.getCacheMapValue("userInfo", "name");

// 获取完整Map
Map<String, Object> map = RedisUtils.getCacheMap("userInfo");

// 删除Map中的值
RedisUtils.delCacheMapValue("userInfo", "age");

发布订阅

java
// 发布消息
RedisUtils.publish("news", "重要通知内容");

// 订阅消息
RedisUtils.subscribe("news", String.class, message -> {
    System.out.println("收到消息: " + message);
    // 处理消息逻辑
});

限流控制

java
// 限流:每秒最多10个请求
long remaining = RedisUtils.rateLimiter("api:login", 
    RateType.OVERALL, 10, 1);

if (remaining == -1) {
    throw new RuntimeException("请求过于频繁,请稍后再试");
}

原子操作

java
// 设置原子值
RedisUtils.setAtomicValue("counter", 100L);

// 原子递增
long newValue = RedisUtils.incrAtomicValue("counter");

// 原子递减
long newValue = RedisUtils.decrAtomicValue("counter");

// 获取当前值
long currentValue = RedisUtils.getAtomicValue("counter");

3. SequenceUtils - 分布式ID生成器

基于Redisson的分布式ID生成工具,支持多种ID格式。

基础ID生成

java
// 生成基础ID(使用默认初始值1和步长1)
long id = SequenceUtils.nextId("order", Duration.ofDays(1));

// 生成ID字符串
String idStr = SequenceUtils.nextIdStr("order", Duration.ofDays(1));

// 自定义初始值和步长
long customId = SequenceUtils.nextId("custom", Duration.ofHours(1), 1000L, 2L);

补零格式化

java
// 生成6位补零的序列号:000001, 000002...
String seqNo = SequenceUtils.nextPaddedIdStr("seq", Duration.ofHours(1), 6);

带日期前缀的ID

java
// 生成带当前日期的ID:20241210001
String dateId = SequenceUtils.nextIdDate();

// 生成带业务前缀和日期的ID:ORD20241210001
String orderId = SequenceUtils.nextIdDate("ORD");

带日期时间前缀的ID

java
// 生成带当前日期时间的ID:20241210103001
String datetimeId = SequenceUtils.nextIdDateTime();

// 生成带业务前缀和日期时间的ID:SN20241210103001
String serialNo = SequenceUtils.nextIdDateTime("SN");

4. QueueUtils - 分布式队列管理

支持多种类型的分布式队列操作。

普通阻塞队列

java
// 添加数据到队列
QueueUtils.addQueueObject("taskQueue", taskData);

// 从队列获取数据(非阻塞)
TaskData task = QueueUtils.getQueueObject("taskQueue");

// 删除指定数据
QueueUtils.removeQueueObject("taskQueue", taskData);

// 销毁队列
QueueUtils.destroyQueue("taskQueue");

延迟队列

java
// 添加延迟任务(5分钟后执行)
QueueUtils.addDelayedQueueObject("delayQueue", task, 5, TimeUnit.MINUTES);

// 默认毫秒单位
QueueUtils.addDelayedQueueObject("delayQueue", task, 300000);

// 获取已到期的任务
TaskData task = QueueUtils.getDelayedQueueObject("delayQueue");

// 删除延迟任务
QueueUtils.removeDelayedQueueObject("delayQueue", task);

优先队列

java
// 任务需要实现Comparable接口
public class PriorityTask implements Comparable<PriorityTask> {
    private int priority;
    private String content;
    
    @Override
    public int compareTo(PriorityTask other) {
        return Integer.compare(other.priority, this.priority); // 高优先级在前
    }
}

// 添加优先级任务
PriorityTask highPriorityTask = new PriorityTask(10, "重要任务");
QueueUtils.addPriorityQueueObject("priorityQueue", highPriorityTask);

// 获取最高优先级任务
PriorityTask task = QueueUtils.getPriorityQueueObject("priorityQueue");

有界队列

java
// 设置队列容量
QueueUtils.trySetBoundedQueueCapacity("boundedQueue", 100);

// 添加数据(队列满时返回false)
boolean success = QueueUtils.addBoundedQueueObject("boundedQueue", data);

// 获取数据
Object data = QueueUtils.getBoundedQueueObject("boundedQueue");

队列订阅

java
// 订阅普通队列
QueueUtils.subscribeBlockingQueue("taskQueue", 
    message -> {
        // 异步处理消息
        return CompletableFuture.runAsync(() -> {
            System.out.println("处理任务: " + message);
        });
    }, false);

// 订阅延迟队列
QueueUtils.subscribeBlockingQueue("delayQueue", 
    message -> {
        return CompletableFuture.runAsync(() -> {
            System.out.println("处理延迟任务: " + message);
        });
    }, true);

二级缓存架构

缓存层级

  1. 一级缓存(Caffeine):JVM本地缓存,访问速度最快
  2. 二级缓存(Redis):分布式缓存,支持集群共享

缓存策略

读取策略

java
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(Long userId) {
    // 1. 先查Caffeine本地缓存
    // 2. 未命中则查Redis
    // 3. Redis命中则回写Caffeine
    // 4. 都未命中则执行方法并缓存结果
    return userService.findById(userId);
}

更新策略

java
@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
    // 1. 执行更新操作
    // 2. 清除Caffeine中的旧数据
    // 3. 更新Redis缓存
    return userService.update(user);
}

删除策略

java
@CacheEvict(value = "userCache", key = "#userId")
public void deleteUser(Long userId) {
    // 1. 执行删除操作
    // 2. 同时清除两级缓存
    userService.delete(userId);
}

动态缓存配置

支持在缓存名称中动态配置参数:

java
// 格式:cacheName#ttl#maxIdleTime#maxSize#local
@Cacheable("userCache#30s#10s#1000#1")
public User getUser(Long id) {
    return userService.findById(id);
}

参数说明:

  • ttl: 存活时间
  • maxIdleTime: 最大空闲时间
  • maxSize: 最大缓存条目数
  • local: 是否启用本地缓存(1=启用,0=禁用)

分布式锁

Lock4j注解方式

java
@Component
public class OrderService {
    
    // 基础锁
    @Lock4j(keys = "#orderId")
    public void processOrder(String orderId) {
        // 业务逻辑
    }
    
    // 自定义锁名称和超时时间
    @Lock4j(keys = "'order:' + #orderId", expire = 30000)
    public void processOrderWithCustom(String orderId) {
        // 业务逻辑
    }
    
    // 多参数锁
    @Lock4j(keys = {"#userId", "#orderId"})
    public void processUserOrder(String userId, String orderId) {
        // 业务逻辑
    }
}

编程方式

java
public void manualLock() {
    RLock lock = RedisUtils.getLock("manual:lock:key");
    try {
        // 尝试获取锁,最多等待10秒,锁定30秒后自动释放
        boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
        if (acquired) {
            // 执行需要同步的业务逻辑
            doSomething();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

监听机制

缓存对象监听

java
// 监听对象变化
RedisUtils.addObjectListener("user:123", new ObjectListener() {
    @Override
    public void onUpdated(String name, Object object) {
        System.out.println("对象已更新: " + name);
    }
    
    @Override
    public void onDeleted(String name, Object object) {
        System.out.println("对象已删除: " + name);
    }
    
    @Override
    public void onExpired(String name, Object object) {
        System.out.println("对象已过期: " + name);
    }
});

集合监听

java
// 监听List变化
RedisUtils.addListListener("myList", new ObjectListener() {
    @Override
    public void onUpdated(String name, Object object) {
        System.out.println("列表已更新: " + name);
    }
});

// 监听Set变化
RedisUtils.addSetListener("mySet", new ObjectListener() {
    @Override
    public void onUpdated(String name, Object object) {
        System.out.println("集合已更新: " + name);
    }
});

// 监听Map变化
RedisUtils.addMapListener("myMap", new ObjectListener() {
    @Override
    public void onUpdated(String name, Object object) {
        System.out.println("映射已更新: " + name);
    }
});

集群配置

Redis集群配置

注意:单机与集群配置只能启用其中一种,需要注释掉另一种配置

yaml
# Redis集群配置示例(注释掉单机配置后使用)
spring.data:
  redis:
    cluster:
      nodes:
        - 192.168.1.100:6379
        - 192.168.1.101:6379
        - 192.168.1.102:6379
        - 192.168.1.103:6379
        - 192.168.1.104:6379
        - 192.168.1.105:6379
      max-redirects: 3
    password: your-cluster-password
    timeout: 10s

redisson:
  keyPrefix: ${app.id}
  threads: 16
  nettyThreads: 32
  # 集群配置(替换singleServerConfig)
  clusterServersConfig:
    clientName: ${app.id}
    masterConnectionMinimumIdleSize: 16
    masterConnectionPoolSize: 32
    slaveConnectionMinimumIdleSize: 16
    slaveConnectionPoolSize: 32
    idleConnectionTimeout: 10000
    timeout: 3000
    subscriptionConnectionPoolSize: 50
    readMode: "SLAVE"           # 读取模式:SLAVE、MASTER、MASTER_SLAVE
    subscriptionMode: "MASTER"  # 订阅模式:SLAVE、MASTER

核心组件

最佳实践

1. 缓存设计原则

选择合适的过期时间

java
// 用户信息:1小时过期
RedisUtils.setCacheObject("user:" + userId, user, Duration.ofHours(1));

// 配置信息:1天过期
RedisUtils.setCacheObject("config:" + key, config, Duration.ofDays(1));

// 临时token:15分钟过期
RedisUtils.setCacheObject("token:" + token, userInfo, Duration.ofMinutes(15));

使用合理的Key命名

java
// 推荐的命名规范
String userKey = "user:profile:" + userId;
String orderKey = "order:detail:" + orderId;
String configKey = "system:config:" + configName;

// 避免的命名方式
String badKey1 = userId; // 太简单,容易冲突
String badKey2 = "user_profile_" + userId; // 分隔符不统一

2. 性能优化

批量操作

java
// 批量删除
Collection<String> keys = Arrays.asList("key1", "key2", "key3");
RedisUtils.deleteObject(keys);

// 批量获取Map值
Set<String> hKeys = Sets.newHashSet("field1", "field2", "field3");
Map<String, Object> values = RedisUtils.getMultiCacheMapValue("myMap", hKeys);

合理使用二级缓存

java
// 对于频繁访问的热点数据,启用本地缓存
@Cacheable("hotData#1h#30m#1000#1")  // 启用本地缓存
public HotData getHotData(String key) {
    return dataService.findByKey(key);
}

// 对于更新频繁的数据,禁用本地缓存
@Cacheable("frequentData#30m#10m#500#0")  // 禁用本地缓存
public FrequentData getFrequentData(String key) {
    return dataService.findByKey(key);
}

3. 错误处理

Redis连接异常处理

java
try {
    User user = RedisUtils.getCacheObject("user:" + userId);
    if (user == null) {
        // 缓存未命中,查询数据库
        user = userService.findById(userId);
        if (user != null) {
            RedisUtils.setCacheObject("user:" + userId, user, Duration.ofHours(1));
        }
    }
    return user;
} catch (Exception e) {
    log.warn("Redis操作异常,降级到数据库查询", e);
    return userService.findById(userId);
}

分布式锁异常处理

java
@Lock4j(keys = "#orderId", acquireTimeout = 5000, expire = 30000)
public void processOrder(String orderId) {
    try {
        // 业务逻辑
        orderService.process(orderId);
    } catch (LockFailureException e) {
        log.warn("获取订单锁失败: {}", orderId);
        throw new BusinessException("订单正在处理中,请稍后再试");
    } catch (Exception e) {
        log.error("处理订单异常: {}", orderId, e);
        throw new BusinessException("订单处理失败");
    }
}

4. 监控与运维

关键指标监控

java
// 自定义监控指标
@Component
public class RedisMonitor {
    
    @EventListener
    public void onCacheHit(CacheHitEvent event) {
        // 记录缓存命中率
        meterRegistry.counter("cache.hit", "cache", event.getCacheName()).increment();
    }
    
    @EventListener
    public void onCacheMiss(CacheMissEvent event) {
        // 记录缓存未命中
        meterRegistry.counter("cache.miss", "cache", event.getCacheName()).increment();
    }
    
    @Scheduled(fixedRate = 60000) // 每分钟检查一次
    public void checkRedisHealth() {
        try {
            RedisUtils.setCacheObject("health:check", System.currentTimeMillis());
            log.debug("Redis健康检查正常");
        } catch (Exception e) {
            log.error("Redis健康检查失败", e);
            // 发送告警
            alertService.sendAlert("Redis连接异常");
        }
    }
}

常见问题

1. 序列化问题

问题:存储自定义对象时出现序列化异常

解决方案

java
// 确保对象实现Serializable接口
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ... 字段定义
}

// 或者使用JSON序列化友好的对象
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    // 使用Jackson注解处理序列化
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    // ... 其他字段
}

2. 缓存穿透

问题:大量请求查询不存在的数据,导致缓存无法生效

解决方案

java
public User getUserById(Long userId) {
    // 先查缓存
    User user = RedisUtils.getCacheObject("user:" + userId);
    if (user != null) {
        return user;
    }
    
    // 查数据库
    user = userService.findById(userId);
    
    // 即使查询结果为空也要缓存,设置较短过期时间
    if (user == null) {
        RedisUtils.setCacheObject("user:" + userId, "NULL", Duration.ofMinutes(5));
        return null;
    }
    
    // 缓存正常数据
    RedisUtils.setCacheObject("user:" + userId, user, Duration.ofHours(1));
    return user;
}

3. 缓存雪崩

问题:大量缓存在同一时间失效,导致数据库压力激增

解决方案

java
public User getUserById(Long userId) {
    // 添加随机过期时间,避免同时失效
    long baseExpireTime = Duration.ofHours(1).toMillis();
    long randomExpireTime = baseExpireTime + (long)(Math.random() * 600000); // 加随机10分钟
    
    User user = userService.findById(userId);
    RedisUtils.setCacheObject("user:" + userId, user, Duration.ofMillis(randomExpireTime));
    return user;
}

4. 内存优化

问题:Caffeine本地缓存占用过多内存

解决方案

java
// 调整Caffeine配置
@Bean
public Cache<Object, Object> caffeine() {
    return Caffeine.newBuilder()
        .maximumSize(500)  // 减少最大缓存数量
        .expireAfterWrite(15, TimeUnit.SECONDS)  // 缩短过期时间
        .expireAfterAccess(5, TimeUnit.SECONDS)  // 增加访问过期时间
        .build();
}

// 或者针对特定业务禁用本地缓存
@Cacheable("largeData#1h#30m#100#0")  // 最后一位设为0禁用本地缓存
public LargeData getLargeData(String key) {
    return dataService.findByKey(key);
}

总结

Redis模块提供了完整的缓存解决方案,通过二级缓存架构、分布式锁、队列管理等功能,为应用提供高性能、高可用的缓存服务。在使用过程中要注意合理设置过期时间、选择合适的数据结构、处理异常情况,并进行必要的监控,以确保系统的稳定性和性能。