如何在Java中整合Redis?
一、Redis 基础认知:为什么选择 Redis?
1.1 Redis 的核心特性
高性能
- 内存存储机制:Redis 将数据完全存储在内存中,数据读写操作直接在内存中进行,避免了磁盘I/O的瓶颈。内存读写速度通常是磁盘的100倍以上(内存访问约100ns,SSD约100μs)。
- 单线程模型:采用单线程处理命令请求,避免了多线程上下文切换和锁竞争的开销,同时通过I/O多路复用技术(如epoll)实现高并发处理。
- 性能基准:在标准服务器配置(如8核CPU,16GB内存)下,Redis单节点可达到:
- 读操作(QPS):10万-15万次/秒
- 写操作(QPS):8万-12万次/秒
- 特别优化的场景下甚至可达20万+ QPS
丰富的数据结构
- String:最基本类型,最大512MB,常用于缓存简单数据或计数器
- Hash:键值对集合,适合存储对象(如用户信息:user:1001 {name:"张三",age:28})
- List:双向链表,可实现消息队列(LPUSH+BRPOP)或最新N条记录
- Set:无序集合,支持交并差运算,适合标签系统
- Sorted Set:带权重的有序集合,可用于排行榜(ZADD game:score 100 "player1")
- Bitmap:位图操作,适合签到统计(SETBIT sign:202301 100 1)
- HyperLogLog:基数统计,误差率0.81%,适合UV统计(PFADD uv:20230101 "user1")
持久化机制
RDB(快照):
- 定时将内存数据生成二进制快照文件(dump.rdb)
- 配置示例:save 900 1(900秒内至少1次修改触发保存)
- 优点:文件紧凑,恢复速度快
- 缺点:可能丢失最后一次快照后的数据
AOF(Append Only File):
- 记录所有写操作命令(类似MySQL的binlog)
- 提供三种同步策略:
- always:每次写操作都同步
- everysec:每秒同步(默认)
- no:由操作系统决定
- 优点:数据安全性高,最多丢失1秒数据
- 缺点:文件较大,恢复速度较慢
混合模式:Redis 4.0+支持RDB+AOF混合持久化,结合两者优势
高可用与分布式
主从复制:
- 一个主节点(Master)可配置多个从节点(Slave)
- 从节点异步复制主节点数据
- 读写分离:写操作走主节点,读操作可分散到从节点
哨兵模式(Sentinel):
- 由2个以上哨兵节点监控Redis集群状态
- 自动故障转移:主节点宕机时,自动将从节点提升为主节点
- 提供配置中心和通知机制
Redis Cluster:
- 官方分布式解决方案(Redis 3.0+)
- 数据分片存储:16384个哈希槽分配到不同节点
- 节点间使用Gossip协议通信
- 支持自动故障转移和请求重定向
多语言支持
- Java:Jedis、Lettuce、Redisson
- Python:redis-py
- Go:go-redis
- Node.js:ioredis
- 所有客户端库均遵循Redis协议规范,支持连接池、管道等高级特性
1.2 Redis 在 Java 项目中的典型应用场景
缓存热点数据
- 实现方式:
// Spring Cache示例 @Cacheable(value="products", key="#productId") public Product getProduct(String productId) {return productDao.findById(productId); } - 缓存策略:
- 缓存穿透:布隆过滤器或空值缓存
- 缓存雪崩:随机过期时间
- 缓存击穿:互斥锁更新
分布式锁
原生实现:
// SETNX实现 String lockKey = "order_lock_"+orderId; String clientId = UUID.randomUUID().toString(); try {Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);if(result) {// 执行业务逻辑} else {// 获取锁失败} } finally {// 使用Lua脚本保证原子性String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), clientId); }Redisson实现:
RLock lock = redissonClient.getLock("order_lock_"+orderId); try {// 尝试加锁,最多等待100秒,上锁后30秒自动解锁if(lock.tryLock(100, 30, TimeUnit.SECONDS)) {// 执行业务逻辑} } finally {lock.unlock(); }
计数器与限流
计数器示例:
// 文章阅读量计数 redisTemplate.opsForValue().increment("article:read:count:"+articleId);// 获取阅读量 Long count = redisTemplate.opsForValue().increment("article:read:count:"+articleId, 0);限流实现(滑动窗口算法):
public boolean isAllowed(String key, int maxCount, int period) {long now = System.currentTimeMillis();long windowStart = now - period * 1000;// 删除旧时间戳redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);// 获取当前请求数long count = redisTemplate.opsForZSet().zCard(key);if(count < maxCount) {// 添加当前请求redisTemplate.opsForZSet().add(key, String.valueOf(now), now);return true;}return false; }
消息队列
List实现简单队列:
// 生产者 redisTemplate.opsForList().leftPush("queue:order", orderJson);// 消费者 String orderJson = redisTemplate.opsForList().rightPop("queue:order", 30, TimeUnit.SECONDS);Redis Stream实现(Redis 5.0+):
// 创建消费者组 redisTemplate.opsForStream().createGroup("order_stream", "order_group");// 生产者 Map<String, String> message = new HashMap<>(); message.put("orderId", "1001"); message.put("amount", "99.9"); redisTemplate.opsForStream().add("order_stream", message);// 消费者 List<MapRecord<String, String, String>> records = redisTemplate.opsForStream().read(Consumer.from("order_group", "consumer1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(1)),StreamOffset.create("order_stream", ReadOffset.lastConsumed()));
分布式Session
Spring Session集成:
<!-- pom.xml --> <dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId> </dependency># application.properties spring.session.store-type=redis spring.session.timeout=1800 # 30分钟// 自动将Session存储到Redis @RestController @SessionAttributes("user") public class UserController {@PostMapping("/login")public String login(@RequestParam String username, Model model) {model.addAttribute("user", username);return "login success";} }Session共享效果:
- 用户登录后,Session信息存储在Redis
- 同一用户的请求可路由到集群中任意节点
- 各服务通过统一Session ID获取用户状态
- 默认使用Jackson序列化Session对象
二、环境搭建:Redis 服务与 Java 开发环境准备
2.1 Redis 服务端部署(以 Linux 为例)
步骤 1:下载并解压 Redis
下载 Redis(版本可根据需求选择,此处以 6.2.6 为例)
# 下载 Redis 源码包
wget https://download.redis.io/releases/redis-6.2.6.tar.gz# 验证下载完整性(可选)
wget https://download.redis.io/releases/redis-6.2.6.tar.gz.asc
gpg --verify redis-6.2.6.tar.gz.asc# 解压 Redis 源码包
tar -zxvf redis-6.2.6.tar.gz# 进入解压后的目录
cd redis-6.2.6
注意事项:
- 建议使用稳定版本(stable)而非开发版本
- 下载前可访问 https://download.redis.io/releases/ 查看最新版本
- 对于生产环境,建议使用 Redis 6.x 及以上版本以支持多线程处理
步骤 2:编译与安装
# 安装编译依赖(CentOS/RHEL)
yum install -y gcc make tcl# 或 Ubuntu/Debian
apt-get install -y build-essential tcl# 编译 Redis(此过程可能需要几分钟)
make# 运行测试(可选,但推荐)
make test# 安装 Redis 到系统目录
make install
安装结果验证:
- 默认安装路径:/usr/local/bin
- 检查是否安装成功:
ls -l /usr/local/bin/redis-*
步骤 3:配置并启动 Redis
基本配置管理:
# 创建 Redis 配置目录
mkdir -p /etc/redis# 复制配置文件模板
cp redis.conf /etc/redis/redis.conf# 创建数据存储目录
mkdir -p /var/lib/redis
关键配置文件修改(/etc/redis/redis.conf):
# 使用 vim 或其他编辑器修改配置文件
vim /etc/redis/redis.conf
需要修改的重要配置项:
网络配置:
# 允许远程访问(注释掉或修改为) # bind 127.0.0.1 bind 0.0.0.0安全配置:
# 关闭保护模式 protected-mode no# 设置密码(生产环境必须) requirepass your_strong_password运行模式:
# 以守护进程方式运行 daemonize yes日志配置:
# 指定日志文件路径 logfile "/var/log/redis/redis-server.log"数据持久化(根据需求配置):
# RDB 持久化配置 save 900 1 save 300 10 save 60 10000# AOF 持久化配置(可选) appendonly yes appendfsync everysec
启动 Redis 服务:
# 创建日志目录
mkdir -p /var/log/redis
chown -R redis:redis /var/log/redis# 启动 Redis 服务
redis-server /etc/redis/redis.conf# 验证服务状态
ps aux | grep redis
netstat -tulnp | grep 6379
连接测试:
# 本地连接测试
redis-cli -h 127.0.0.1 -p 6379 -a your_password ping# 远程连接测试(从其他服务器)
redis-cli -h your_server_ip -p 6379 -a your_password ping
设置开机自启(可选):
# 创建 systemd 服务文件
vim /etc/systemd/system/redis.service
添加以下内容:
[Unit]
Description=Redis In-Memory Data Store
After=network.target[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always[Install]
WantedBy=multi-user.target
然后执行:
# 重新加载 systemd
systemctl daemon-reload# 启用开机自启
systemctl enable redis# 启动服务
systemctl start redis# 检查状态
systemctl status redis
2.2 Java 开发环境配置
客户端选择说明
Java 操作 Redis 主要有两种主流客户端:
Jedis:
- 官方推荐的轻量级客户端
- 直接操作 Redis 命令
- 支持连接池
- 适合简单场景和性能要求高的场景
Redisson:
- 基于 Netty 实现
- 提供分布式对象和服务
- 内置分布式锁、原子操作等高级功能
- 适合复杂分布式场景
2.2.1 Maven 依赖配置
Jedis 依赖配置(推荐使用连接池):
<!-- Jedis 核心依赖 -->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.1</version>
</dependency><!-- 连接池依赖(必须) -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version>
</dependency><!-- 可选:JSON 序列化支持 -->
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.9.0</version>
</dependency>
Redisson 依赖配置:
<!-- Redisson 核心依赖 -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.6</version>
</dependency><!-- 可选:配置支持(如 JSON/YAML 配置文件) -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.3</version>
</dependency>
版本选择建议:
- 生产环境建议使用稳定版本
- 可访问 Maven 中央仓库(https://mvnrepository.com/)查询最新版本
- 注意版本兼容性:
- Jedis 4.x 支持 Redis 6.x
- Redisson 3.x 支持 Redis 5.x+
开发环境验证
创建简单的测试类验证环境是否配置成功:
import redis.clients.jedis.Jedis;public class RedisTest {public static void main(String[] args) {// 创建连接Jedis jedis = new Jedis("localhost", 6379);jedis.auth("your_password"); // 如果有密码// 测试连接System.out.println("连接状态:" + jedis.ping());// 基本操作jedis.set("test_key", "Hello Redis");System.out.println("获取值:" + jedis.get("test_key"));// 关闭连接jedis.close();}
}
IDE 配置建议
IntelliJ IDEA:
- 确保 Maven 项目正确导入依赖
- 安装 Redis 插件(如 "Redis" 插件)方便调试
Eclipse:
- 使用 M2Eclipse 插件管理依赖
- 可安装 Redis 客户端插件(如 "RedisClipse")
生产环境注意事项
连接池配置:
- 必须使用连接池管理连接
- 推荐配置参数:
JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(128); // 最大连接数 poolConfig.setMaxIdle(32); // 最大空闲连接 poolConfig.setMinIdle(8); // 最小空闲连接 poolConfig.setTestOnBorrow(true); // 获取连接时测试
安全建议:
- 不要将密码硬编码在代码中
- 使用配置中心或环境变量管理敏感信息
- 启用 SSL/TLS 加密传输(Redis 6+支持)
性能优化:
- 批量操作使用 pipeline
- 大 value 考虑分片存储
- 合理选择序列化方式
三、Jedis 客户端:Java 操作 Redis 的基础实现
3.1 基本使用:单实例连接
步骤 1:创建 Jedis 实例并连接 Redis
import redis.clients.jedis.Jedis;public class JedisBasicDemo {public static void main(String[] args) {// 1. 创建Jedis实例(参数说明:Redis地址、端口、连接超时时间(毫秒)、密码)// 注意:如果Redis服务器在同一台机器,地址可以是"localhost"Jedis jedis = new Jedis("192.168.1.100", 6379, 5000); try {// 2. 验证密码(若Redis未设置密码,可跳过此步骤)// 如果密码错误,会抛出redis.clients.jedis.exceptions.JedisDataExceptionjedis.auth("your_password");// 3. 测试连接// ping()返回"PONG"表示连接正常System.out.println("Redis连接状态:" + jedis.ping()); // 期望输出:PONG// 4. 操作String类型数据jedis.set("username", "zhangsan"); // 存入数据,默认永不过期// 带过期时间的设置(单位秒)jedis.setex("temp_token", 3600, "abcd1234"); String username = jedis.get("username"); // 获取数据System.out.println("获取到的用户名:" + username); // 输出:zhangsan// 5. 操作Hash类型数据// 存储用户信息jedis.hset("user:1001", "name", "lisi");jedis.hset("user:1001", "age", "25");jedis.hset("user:1001", "gender", "male");// 获取Hash中所有字段和值(返回Map<String,String>)System.out.println("用户1001信息:" + jedis.hgetAll("user:1001"));// 输出:{name=lisi, age=25, gender=male}// 获取单个字段System.out.println("用户年龄:" + jedis.hget("user:1001", "age")); // 输出:25} catch (Exception e) {e.printStackTrace();} finally {// 6. 关闭连接(重要!避免连接泄漏)if (jedis != null) {jedis.close();}}}
}
注意事项
线程安全性:
- Jedis实例是非线程安全的,每个线程应该使用自己的Jedis实例
- 在高并发场景中直接使用单例连接会导致线程安全问题
性能考量:
- 每次操作都需要创建和关闭TCP连接,频繁操作时会产生较大性能开销
- 实测表明,在100次连续操作中,单实例连接方式耗时是连接池方式的10倍以上
实际应用建议:
- 开发环境或简单脚本可以使用单实例连接
- 生产环境必须使用连接池(JedisPool)管理连接
- 对于长时间运行的任务,建议使用try-with-resources确保连接关闭
异常处理:
- 网络中断时可能抛出JedisConnectionException
- 认证失败抛出JedisDataException
- 建议对关键操作添加重试机制
3.2 进阶使用:Jedis 连接池
步骤 1:配置 Jedis 连接池
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;public class JedisPoolDemo {// 初始化Jedis连接池(使用静态代码块确保只初始化一次)private static JedisPool jedisPool;static {// 1. 配置连接池参数GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();// 关键参数配置(根据实际业务需求调整)poolConfig.setMaxTotal(100); // 最大连接数(根据Redis服务器配置调整)poolConfig.setMaxIdle(20); // 最大空闲连接数(建议设为MaxTotal的20%-50%)poolConfig.setMinIdle(5); // 最小空闲连接数(保持一定数量的热连接)poolConfig.setBlockWhenExhausted(true); // 连接耗尽时是否阻塞等待(建议true)poolConfig.setMaxWaitMillis(3000); // 获取连接最大等待时间(毫秒)poolConfig.setTestOnBorrow(true); // 获取连接时是否测试有效性(建议true,但会有性能损耗)poolConfig.setTestOnReturn(false); // 归还连接时是否测试(建议false)poolConfig.setTestWhileIdle(true); // 空闲时是否测试(建议true)// 2. 初始化连接池String redisHost = "192.168.1.100";int redisPort = 6379;int timeout = 5000; // 连接超时时间(毫秒)String password = "your_password";jedisPool = new JedisPool(poolConfig, redisHost, redisPort, timeout, password);}// 从连接池获取Jedis实例public static Jedis getJedis() {return jedisPool.getResource();}// 测试连接池使用public static void main(String[] args) {// 使用try-with-resources语法自动管理连接try (Jedis jedis = getJedis()) {// 1. 操作String类型jedis.set("product:1001", "iPhone 13");System.out.println("商品1001名称:" + jedis.get("product:1001"));// 2. 操作List类型(模拟消息队列)jedis.lpush("message:queue", "msg1", "msg2", "msg3"); // 左推(入队)// 右拉(出队),0表示无限期阻塞等待String msg = jedis.brpop(0, "message:queue").get(1); System.out.println("消费的消息:" + msg);// 3. 批量操作(使用pipeline提升性能)long start = System.currentTimeMillis();for (int i = 0; i < 1000; i++) {jedis.set("key_" + i, "value_" + i);}long end = System.currentTimeMillis();System.out.println("普通操作耗时:" + (end - start) + "ms");} catch (Exception e) {e.printStackTrace();}}
}
核心优势
性能优化:
- 连接复用减少TCP三次握手开销
- 实测在1000次操作中,连接池方式比单实例快8-10倍
- 支持Pipeline批量操作,进一步提升吞吐量
资源管理:
- 自动维护连接池中的活跃连接数
- 可配置连接最大存活时间(maxAge)避免长时间使用的连接出现问题
- 通过evictor线程定期检测和清理无效连接
生产环境配置建议:
// 典型生产环境配置 poolConfig.setMaxTotal(200); // 根据QPS估算,一般(平均QPS*平均耗时(ms))/1000 poolConfig.setMaxIdle(50); // 根据业务波动情况调整 poolConfig.setMinIdle(10); // 保持一定热连接 poolConfig.setMaxWaitMillis(1000); // 根据系统容忍度设置 poolConfig.setTestOnBorrow(true); // 确保获取的连接可用 poolConfig.setTimeBetweenEvictionRunsMillis(30000); // 30秒检测一次监控指标:
- 可通过JMX监控连接池状态
- 关键指标:活动连接数、空闲连接数、等待获取连接的线程数等
最佳实践:
- 每个应用使用独立的连接池
- 根据业务类型(读写比例、命令复杂度)配置不同连接池
- 定期监控连接池状态,及时调整参数
3.3 Jedis 操作核心数据结构
(1)Set 类型(无序、唯一)
try (Jedis jedis = JedisPoolDemo.getJedis()) {// 1. 添加元素(自动去重)jedis.sadd("tags:java", "spring", "mybatis", "redis");jedis.sadd("tags:java", "spring"); // 重复元素不会被添加// 2. 获取所有元素(无序)System.out.println("Java相关标签:" + jedis.smembers("tags:java")); // 输出可能是:[redis, spring, mybatis]// 3. 判断元素是否存在System.out.println("是否包含spring:" + jedis.sismember("tags:java", "spring")); // 输出:true// 4. 移除元素jedis.srem("tags:java", "mybatis");// 5. 计算集合大小System.out.println("标签数量:" + jedis.scard("tags:java")); // 输出:2// 6. 集合运算jedis.sadd("tags:backend", "spring", "django", "redis");// 交集System.out.println("交集:" + jedis.sinter("tags:java", "tags:backend"));// 输出:[spring, redis]// 并集System.out.println("并集:" + jedis.sunion("tags:java", "tags:backend"));// 输出:[spring, redis, django]// 差集(tags:java有而tags:backend没有的)System.out.println("差集:" + jedis.sdiff("tags:java", "tags:backend"));// 输出:[]
}
(2)Sorted Set 类型(有序、唯一,基于分数排序)
try (Jedis jedis = JedisPoolDemo.getJedis()) {// 1. 添加元素(如果成员已存在,则更新分数)jedis.zadd("rank:java", 95, "zhangsan");jedis.zadd("rank:java", 88, "lisi");jedis.zadd("rank:java", 92, "wangwu");jedis.zadd("rank:java", 90, "lisi"); // 更新lisi的分数// 2. 按分数升序获取(0到-1表示全部)System.out.println("升序排名:" + jedis.zrange("rank:java", 0, -1));// 输出:[lisi, wangwu, zhangsan]// 3. 按分数降序获取(带分数)Set<Tuple> results = jedis.zrevrangeWithScores("rank:java", 0, -1);results.forEach(tuple -> System.out.println(tuple.getElement() + ":" + tuple.getScore()));// 输出:// zhangsan:95.0// wangwu:92.0// lisi:90.0// 4. 获取分数System.out.println("lisi的分数:" + jedis.zscore("rank:java", "lisi"));// 输出:90.0// 5. 获取排名(从0开始)System.out.println("zhangsan的降序排名:" + jedis.zrevrank("rank:java", "zhangsan"));// 输出:0// 6. 范围查询(80<=score<=90)System.out.println("80-90分的学生:" + jedis.zrangeByScoreWithScores("rank:java", 80, 90));// 输出:[lisi:90.0, wangwu:92.0](注意包含边界)// 7. 原子操作:增加分数jedis.zincrby("rank:java", 5, "lisi"); // lisi分数+5System.out.println("新分数:" + jedis.zscore("rank:java", "lisi"));// 输出:95.0
}
(3)其他数据结构操作
// Geo地理位置操作
try (Jedis jedis = JedisPoolDemo.getJedis()) {// 添加地理位置(经度、纬度、名称)jedis.geoadd("cities", 116.404, 39.915, "beijing");jedis.geoadd("cities", 121.474, 31.230, "shanghai");// 计算两地距离(单位:km)Double dist = jedis.geodist("cities", "beijing", "shanghai", GeoUnit.KM);System.out.println("北京到上海距离:" + dist + "km");// 查找附近位置List<GeoRadiusResponse> nearby = jedis.georadiusByMember("cities", "beijing", 1000, GeoUnit.KM);nearby.forEach(r -> System.out.println(r.getMemberByString()));
}// HyperLogLog基数统计
try (Jedis jedis = JedisPoolDemo.getJedis()) {// 添加元素(用于统计不重复元素数量)for (int i = 0; i < 10000; i++) {jedis.pfadd("visitors", "user_" + i);jedis.pfadd("visitors", "user_" + (i % 5000)); // 50%重复}// 获取基数估计值System.out.println("独立访客数:" + jedis.pfcount("visitors"));// 输出约7500(误差率约0.81%)
}
四、Redisson 客户端:Java 操作 Redis 的高级实现
Redisson是基于Jedis的封装,不仅提供了更简洁易用的API,还实现了大量Redis官方推荐的分布式特性。相比原生Jedis,Redisson具有以下核心优势:
- 线程安全:内置连接池管理,彻底解决Jedis线程安全问题
- 丰富特性:支持分布式锁(可重入锁、公平锁、联锁等)、分布式集合(Set、Map、List)、延迟队列、布隆过滤器等20+种分布式对象
- 协议支持:完整支持Redis 5.0+的所有命令和数据类型
- 容错机制:自动重连、故障转移、读写分离等企业级特性
4.1 Redisson客户端初始化
Redisson支持多种Redis部署模式,每种模式的配置方式如下:
单点模式配置示例
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;/*** Redisson客户端工厂类(推荐单例模式)* 典型应用场景:Spring Bean管理、静态工具类等*/
public class RedissonClientFactory {// 使用volatile保证多线程可见性private static volatile RedissonClient redissonClient;// 双重检查锁实现线程安全初始化public static RedissonClient getRedissonClient() {if (redissonClient == null) {synchronized (RedissonClientFactory.class) {if (redissonClient == null) {// 1. 创建配置构建器Config config = new Config();// 2. 配置单点模式(生产环境建议使用YAML/JSON配置文件)config.useSingleServer().setAddress("redis://192.168.1.100:6379") // 支持rediss://协议(SSL加密).setPassword("your_password") // 密码特殊字符需URL编码.setDatabase(0) // 默认DB索引.setConnectionPoolSize(100) // 最大连接数(默认64).setConnectionMinimumIdleSize(10) // 最小空闲连接(默认10).setConnectTimeout(5000) // 连接超时(毫秒).setIdleConnectionTimeout(30000) // 空闲连接超时.setRetryAttempts(3) // 命令重试次数.setRetryInterval(1000); // 重试间隔// 3. 创建客户端实例(实际会启动连接池和定时任务)redissonClient = Redisson.create(config);}}}return redissonClient;}// 优雅关闭方法public static void shutdown() {if (redissonClient != null) {redissonClient.shutdown();}}
}
其他部署模式配置示例
哨兵模式
config.useSentinelServers().addSentinelAddress("redis://sentinel1:26379").addSentinelAddress("redis://sentinel2:26379").setMasterName("mymaster");
集群模式
config.useClusterServers().addNodeAddress("redis://node1:6379").addNodeAddress("redis://node2:6379").setScanInterval(2000); // 集群状态扫描间隔
最佳实践建议
- 连接池配置:根据QPS调整连接数(建议公式:最大连接数 = QPS × 平均响应时间(秒))
- 超时设置:网络不稳定的环境适当增大超时时间
- 资源释放:应用关闭时调用
shutdown()释放资源 - 配置分离:生产环境建议使用外部配置文件(JSON/YAML格式)
4.2 Redisson 操作核心数据结构
Redisson 是一个基于 Redis 的 Java 客户端,它通过 RedissonClient 提供了对应 Redis 数据结构的封装类,使得操作 Redis 更加面向对象和简洁。Redisson 支持 Redis 的所有核心数据结构,包括 String、Hash、List、Set 和 Sorted Set 等,每种数据结构都有对应的 Java 接口实现。
(1)String 类型(RBucket)
RBucket 是 Redisson 对 Redis String 类型的封装,提供了一系列操作字符串的方法。RBucket 支持设置过期时间,适用于缓存场景。
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;public class RedissonStringDemo {public static void main(String[] args) {// 获取 RedissonClient 实例RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();try {// 获取 RBucket 对象(对应 Redis 的 String 类型)RBucket<String> usernameBucket = redissonClient.getBucket("username");// 存入数据,并设置过期时间为 3600 秒(1小时)usernameBucket.set("zhangsan", 3600);// 获取数据String username = usernameBucket.get();System.out.println("用户名:" + username); // 输出:zhangsan// 判断 key 是否存在System.out.println("是否存在:" + usernameBucket.isExists()); // 输出:true// 删除数据usernameBucket.delete();System.out.println("删除后是否存在:" + usernameBucket.isExists()); // 输出:false} finally {// 关闭客户端(若为长期运行的服务,无需频繁关闭)redissonClient.shutdown();}}
}
(2)Hash 类型(RMap)
RMap 是 Redisson 对 Redis Hash 类型的封装,提供类似 Java Map 的操作方法,适用于存储对象或结构化数据。
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;public class RedissonHashDemo {public static void main(String[] args) {RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();try {// 获取 RMap 对象(对应 Redis 的 Hash 类型)RMap<String, String> userMap = redissonClient.getMap("user:1001");// 存入数据userMap.put("name", "lisi");userMap.put("age", "25");userMap.put("gender", "male");// 获取数据System.out.println("用户姓名:" + userMap.get("name")); // 输出:lisiSystem.out.println("用户所有信息:" + userMap); // 输出:{name=lisi, age=25, gender=male}// 删除字段userMap.remove("gender");System.out.println("删除 gender 后的信息:" + userMap); // 输出:{name=lisi, age=25}// 判断字段是否存在System.out.println("是否包含 age:" + userMap.containsKey("age")); // 输出:true} finally {// 关闭客户端redissonClient.shutdown();}}
}
(3)List 类型(RList)
RList 是 Redisson 对 Redis List 类型的封装,支持双向添加、删除和获取元素,适用于队列或栈的场景。
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;public class RedissonListDemo {public static void main(String[] args) {RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();try {// 获取 RList 对象(对应 Redis 的 List 类型)RList<String> messageList = redissonClient.getList("message:queue");// 添加元素(右添加,等同于 RPUSH)messageList.add("msg1");// 左添加(等同于 LPUSH)messageList.add(0, "msg0");// 获取指定索引元素System.out.println("索引 0 的元素:" + messageList.get(0)); // 输出:msg0// 获取所有元素System.out.println("所有消息:" + messageList); // 输出:[msg0, msg1]// 移除并返回最后一个元素(等同于 RPOP)String lastMsg = messageList.remove(messageList.size() - 1);System.out.println("移除的最后一条消息:" + lastMsg); // 输出:msg1// 获取列表长度System.out.println("列表长度:" + messageList.size()); // 输出:1} finally {redissonClient.shutdown();}}
}
(4)Set 类型(RSet)
RSet 是 Redisson 对 Redis Set 类型的封装,支持元素的添加、删除和集合运算,适用于标签或唯一值存储场景。
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;public class RedissonSetDemo {public static void main(String[] args) {RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();try {// 获取 RSet 对象(对应 Redis 的 Set 类型)RSet<String> tagSet = redissonClient.getSet("tags:java");// 添加元素tagSet.add("spring");tagSet.add("mybatis");tagSet.add("redis");// 判断元素是否存在System.out.println("是否包含 spring:" + tagSet.contains("spring")); // 输出:true// 获取所有元素System.out.println("Java 标签集合:" + tagSet); // 输出:[spring, mybatis, redis]// 创建另一个 SetRSet<String> backendTagSet = redissonClient.getSet("tags:backend");backendTagSet.add("spring");backendTagSet.add("django");backendTagSet.add("redis");// 计算并返回交集System.out.println("Java 与 Backend 标签交集:" + tagSet.retainAll(backendTagSet));// 输出:[spring, redis](retainAll 方法会保留交集元素并返回)// 获取集合大小System.out.println("交集大小:" + tagSet.size()); // 输出:2} finally {redissonClient.shutdown();}}
}
(5)Sorted Set 类型(RSortedSet)
RSortedSet 是 Redisson 对 Redis Sorted Set 类型的封装,支持按分数排序和范围查询,适用于排行榜或优先级队列场景。
import org.redisson.api.RSortedSet;
import org.redisson.api.RedissonClient;
import org.redisson.api.ScoredEntry;
import java.util.Collection;public class RedissonSortedSetDemo {public static void main(String[] args) {RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();try {// 获取 RSortedSet 对象(对应 Redis 的 Sorted Set 类型)RSortedSet<String> rankSet = redissonClient.getSortedSet("rank:java");// 添加元素(指定分数)rankSet.add(95, "zhangsan");rankSet.add(88, "lisi");rankSet.add(92, "wangwu");// 按分数升序获取元素System.out.println("成绩升序排名:" + rankSet); // 输出:[lisi, wangwu, zhangsan]// 按分数降序获取元素(含分数)Collection<ScoredEntry<String>> scoredEntries = rankSet.entryRangeReversed(0, -1);for (ScoredEntry<String> entry : scoredEntries) {System.out.println(entry.getValue() + ":" + entry.getScore());// 输出:// zhangsan:95.0// wangwu:92.0// lisi:88.0}// 获取指定元素的排名(降序)System.out.println("zhangsan 的排名:" + rankSet.revRank("zhangsan")); // 输出:0// 获取分数范围内的元素Collection<String> range = rankSet.valueRange(90, true, 95, true);System.out.println("分数在 90~95 之间的学生:" + range); // 输出:[wangwu, zhangsan]} finally {redissonClient.shutdown();}}
}
4.3 Redisson 高级特性:分布式锁与延迟队列
4.3.1 分布式锁(RLock)
在分布式系统中,多个服务实例可能同时操作同一资源(如订单创建、库存扣减等场景),分布式锁可避免并发问题,保证数据一致性。Redisson 的RLock实现了与Java的ReentrantLock类似的可重入锁特性,且支持自动过期释放机制,无需手动处理死锁问题。
核心特性
- 可重入性:同一线程可以多次获取同一把锁
- 自动续期:通过watchdog机制自动延长锁持有时间
- 公平锁支持:可配置为公平锁模式
- 锁等待超时:避免线程无限期等待
- 锁释放保障:通过finally块确保锁释放
典型应用场景
- 秒杀系统中的库存扣减
- 支付系统中的订单状态变更
- 分布式定时任务调度
- 重要数据的并发修改
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;public class RedissonDistributedLockDemo {// 锁的key命名规范:业务类型:业务ID(如订单创建锁)private static final String LOCK_KEY = "lock:order:create"; public static void createOrder(RedissonClient redissonClient) {// 1. 获取分布式锁实例(非阻塞)RLock lock = redissonClient.getLock(LOCK_KEY);try {// 2. 尝试获取锁(参数说明)// - waitTime: 获取锁的最大等待时间(建议设置短于业务超时时间)// - leaseTime: 锁自动释放时间(业务完成前应足够长)// - TimeUnit: 时间单位boolean isLocked = lock.tryLock(3, 30, TimeUnit.SECONDS);if (isLocked) {try {// 3. 加锁成功,执行业务逻辑System.out.println(Thread.currentThread().getName() + "获取分布式锁成功,开始创建订单...");// 模拟业务处理耗时(如数据库操作、远程调用等)Thread.sleep(5000);System.out.println("订单创建完成!");} finally {// 4. 确保业务完成后再释放锁if (lock.isHeldByCurrentThread()) {lock.unlock();System.out.println("分布式锁已释放!");}}} else {// 5. 获取锁失败处理(可记录日志、返回友好提示或重试)System.out.println("获取分布式锁失败,请稍后再试!");// 可抛出特定异常或返回错误码}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("线程被中断:" + e.getMessage());} catch (Exception e) {System.err.println("业务处理异常:" + e.getMessage());}}public static void main(String[] args) {RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();// 模拟3个并发线程尝试获取锁for (int i = 0; i < 3; i++) {new Thread(() -> createOrder(redissonClient), "Thread-"+i).start();}// 注意:实际应用中RedissonClient应该是单例长期存在// redissonClient.shutdown();}
}
最佳实践
- 锁命名规范:使用业务相关的有意义的名称,如
lock:order:{orderId} - 锁超时设置:leaseTime应大于预估的业务处理时间
- 异常处理:确保在finally块中释放锁
- 避免长时间持有锁:锁持有时间应尽量短
- 重试机制:获取锁失败时应有合理的重试策略
4.3.2 延迟队列(RDelayedQueue)
延迟队列可实现"消息延迟一段时间后再被消费"的场景,如订单超时未支付自动取消、定时任务触发、预约提醒等。Redisson的RDelayedQueue基于Redis的Sorted Set实现,提供了简单易用的API,避免了自行实现延迟队列的复杂性。
核心特性
- 精确延时:支持毫秒级延迟精度
- 持久化存储:消息不会因服务重启而丢失
- 高可用:基于Redis集群保证可靠性
- 多消费者支持:多个服务实例可以同时消费
- 消息顺序保障:按延迟时间顺序消费消息
典型应用场景
- 电商订单超时自动取消(30分钟未支付)
- 会议开始前15分钟提醒
- 延时任务调度
- 红包24小时未领取自动退回
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;public class RedissonDelayedQueueDemo {// 延迟队列命名规范:业务类型:业务目的private static final String DELAY_QUEUE_KEY = "delay:queue:order"; private static final String BLOCKING_QUEUE_KEY = "blocking:queue:order";/*** 生产者:添加延迟消息* @param redissonClient Redisson客户端* @param message 消息内容(建议包含业务ID)* @param delayTime 延迟时间(单位:秒)*/public static void produceDelayMessage(RedissonClient redissonClient, String message, long delayTime) {// 1. 获取阻塞队列(底层实际存储结构)RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(BLOCKING_QUEUE_KEY);// 2. 创建延迟队列(与阻塞队列关联)try (RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue)) {// 3. 添加延迟消息delayedQueue.offer(message, delayTime, TimeUnit.SECONDS);System.out.println("[" + System.currentTimeMillis() + "] 添加延迟消息:" + message + ",延迟时间:" + delayTime + "秒");} // 自动关闭延迟队列(Java 7+ try-with-resources)}/*** 消费者:持续消费延迟消息* @param redissonClient Redisson客户端*/public static void consumeDelayMessage(RedissonClient redissonClient) {// 1. 获取阻塞队列(与生产者使用的相同)RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(BLOCKING_QUEUE_KEY);System.out.println("消费者启动,等待消费延迟消息...");while (!Thread.currentThread().isInterrupted()) {try {// 2. 阻塞获取消息(当有消息到期时自动唤醒)String message = blockingQueue.take();// 3. 处理消息long currentTime = System.currentTimeMillis();System.out.println("[" + currentTime + "] 消费延迟消息:" + message);// 根据消息内容处理业务if (message != null && message.startsWith("order:")) {String[] parts = message.split(":");if (parts.length >= 2) {String orderId = parts[1];System.out.println("处理订单超时:订单" + orderId + "超时未支付,已自动取消!");// 实际业务中此处会调用订单服务取消订单}}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("消费者线程被中断");break;} catch (Exception e) {System.err.println("消费消息异常:" + e.getMessage());// 可添加重试或错误处理逻辑}}}public static void main(String[] args) {RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();// 启动消费者线程(实际应用中通常是独立服务)Thread consumerThread = new Thread(() -> consumeDelayMessage(redissonClient));consumerThread.setDaemon(true);consumerThread.start();// 模拟生产3个订单延迟消息produceDelayMessage(redissonClient, "order:10001", 5); // 5秒后超时produceDelayMessage(redissonClient, "order:10002", 10); // 10秒后超时produceDelayMessage(redissonClient, "order:10003", 15); // 15秒后超时// 保持主线程运行(演示用)try {Thread.sleep(20000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 实际应用中不需要关闭(长期运行的服务)// redissonClient.shutdown();}
}
最佳实践
- 消息设计:消息内容应包含足够业务信息(如订单ID)
- 异常处理:消费者需处理各种异常情况
- 幂等性:消息处理应保证幂等,防止重复消费
- 监控报警:对消费延迟和堆积情况监控
- 资源释放:正确关闭延迟队列实例
性能优化建议
- 批量消费:可适当批量获取消息减少网络开销
- 分区设计:大流量场景可按业务ID分多个队列
- 消费并行度:根据业务需求调整消费者数量
- 消息压缩:大消息可考虑压缩后再存储
五、Spring Boot 整合 Redis:企业级项目实战
在 Spring Boot 项目中,可通过 spring-boot-starter-data-redis 快速整合 Redis,简化配置和开发。该 starter 默认使用 Spring Data Redis 抽象层,支持 Jedis、Lettuce(Spring Boot 2.x 默认客户端)等底层客户端。Spring Data Redis 提供了统一的 API 来操作 Redis,支持多种数据结构的操作,包括字符串、哈希、列表、集合等。
5.1 环境配置
5.1.1 添加 Maven 依赖
在 pom.xml 中添加以下依赖:
<!-- Spring Boot Redis Starter -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><!-- 排除默认的Lettuce客户端(若需使用Jedis,可添加此配置) --><!-- <exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions> -->
</dependency><!-- 若排除Lettuce后,需添加Jedis依赖 -->
<!-- <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency> --><!-- Spring Boot Web Starter(用于演示接口) -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
说明:
spring-boot-starter-data-redis是 Spring Boot 提供的 Redis 启动器,自动配置了 Redis 相关的 Bean。- 默认使用 Lettuce 客户端,如需切换为 Jedis,需排除 Lettuce 并添加 Jedis 依赖。
spring-boot-starter-web用于演示 RESTful 接口,实际项目中根据需求添加。
5.1.2 配置 application.yml
在 src/main/resources/application.yml 中添加 Redis 相关配置:
spring:redis:host: 192.168.1.100 # Redis服务地址,生产环境建议使用域名或内网IPport: 6379 # Redis端口,默认6379password: your_password # Redis密码(若未设,可省略)database: 0 # Redis数据库索引(0-15),默认0timeout: 5000ms # 连接超时时间lettuce: # Lettuce客户端配置(若使用Jedis,需改为jedis配置)pool: # 连接池配置max-active: 100 # 最大连接数,默认8max-idle: 20 # 最大空闲连接数,默认8min-idle: 5 # 最小空闲连接数,默认0max-wait: 3000ms # 连接池耗尽时的最大等待时间,默认-1(无限等待)
配置项详解:
host和port:Redis 服务器地址和端口,必填项。password:如果 Redis 设置了密码,需配置此项。database:Redis 默认有 16 个数据库(0-15),可通过此配置选择使用的数据库。timeout:连接 Redis 的超时时间,建议设置为 5000ms 以上。lettuce.pool:Lettuce 连接池配置,适用于高并发场景:max-active:最大连接数,根据业务量调整。max-idle和min-idle:空闲连接数,避免频繁创建和销毁连接。max-wait:获取连接的最大等待时间,避免长时间阻塞。
Jedis 配置示例(如需切换):
spring:redis:host: 192.168.1.100port: 6379jedis:pool:max-active: 100max-idle: 20min-idle: 5max-wait: 3000ms
生产环境建议:
- 使用连接池以提高性能。
- 密码和敏感信息建议通过环境变量或配置中心管理。
- 对于集群或哨兵模式,需额外配置。
5.2 核心 API:RedisTemplate 与 StringRedisTemplate
Spring Data Redis 提供了两种主要的模板类用于操作 Redis,它们各自适用于不同的使用场景:
StringRedisTemplate
StringRedisTemplate 是专门用于操作 String 类型数据的模板类,具有以下特点:
- 序列化方式:默认使用 StringRedisSerializer,所有键和值都以 UTF-8 编码的字符串形式存储
- 优势:
- 完全避免中文乱码问题
- 存储的数据可直接在 Redis CLI 中查看
- 性能较高,适用于简单字符串操作
- 限制:
- 只能操作字符串类型的数据
- 无法直接存储 Java 对象
RedisTemplate
RedisTemplate 是通用模板类,支持所有 Redis 数据结构,但需要注意:
- 默认序列化问题:
- 默认使用 JdkSerializationRedisSerializer
- 会导致存储的数据带有"xac\xed\x00\x05t\x00"等序列化前缀
- 存储的数据在 Redis CLI 中不可读
- 适用场景:
- 需要操作复杂数据结构(List, Set, ZSet等)
- 需要直接存储 Java 对象
- 需要自定义序列化方式
5.2.1 配置 RedisTemplate(自定义序列化)
为优化 RedisTemplate 的默认行为,推荐配置自定义序列化:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 设置连接工厂(必需)redisTemplate.setConnectionFactory(redisConnectionFactory);// Key序列化配置(String类型)StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringRedisSerializer);redisTemplate.setHashKeySerializer(stringRedisSerializer);// Value序列化配置(JSON格式)GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// 初始化模板(必需)redisTemplate.afterPropertiesSet();return redisTemplate;}
}
配置说明:
- 键序列化使用 StringRedisSerializer,保证键的可读性
- 值序列化使用 Jackson,支持复杂对象存储
- 必须调用 afterPropertiesSet() 完成初始化
5.2.2 StringRedisTemplate 使用示例
StringRedisTemplate 的常见操作示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;import java.util.Map;
import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/redis/string")
public class StringRedisController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 存储String数据(带过期时间)@PostMapping("/set")public String setString(@RequestParam String key,@RequestParam String value,@RequestParam(required = false, defaultValue = "3600") long timeout) {stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);return "存储成功!Key:" + key + ",Value:" + value;}// 获取String数据@GetMapping("/get/{key}")public String getString(@PathVariable String key) {String value = stringRedisTemplate.opsForValue().get(key);return "获取到的数据:Key:" + key + ",Value:" + (value == null ? "无" : value);}// 存储Hash数据@PostMapping("/hash/set")public String setHash(@RequestParam String key,@RequestParam Map<String, String> hashMap) {stringRedisTemplate.opsForHash().putAll(key, hashMap);return "Hash数据存储成功!Key:" + key;}// 获取Hash数据@GetMapping("/hash/get/{key}")public Map<Object, Object> getHash(@PathVariable String key) {return stringRedisTemplate.opsForHash().entries(key);}// 设置过期时间@PostMapping("/expire")public Boolean expireKey(@RequestParam String key, @RequestParam long timeout) {return stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);}
}
5.2.3 RedisTemplate 使用示例(操作对象)
定义实体类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Long id;private String name;private Integer age;private String gender;
}
对象操作示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/redis/object")
public class ObjectRedisController {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 存储User对象@PostMapping("/user/set")public String setUser(@RequestBody User user,@RequestParam(required = false, defaultValue = "86400") long timeout) {String key = "user:" + user.getId();redisTemplate.opsForValue().set(key, user, timeout, TimeUnit.SECONDS);return "User对象存储成功!Key:" + key + ",User:" + user;}// 获取User对象@GetMapping("/user/get")public User getUser(@RequestParam Long userId) {String key = "user:" + userId;return (User) redisTemplate.opsForValue().get(key);}// 删除User对象@PostMapping("/user/delete")public String deleteUser(@RequestParam Long userId) {String key = "user:" + userId;Boolean isDeleted = redisTemplate.delete(key);return isDeleted ? "User对象删除成功!Key:" + key : "删除失败,Key不存在";}
}
操作说明:
- 对象存储会自动使用配置的 Jackson 序列化器
- 获取时会自动反序列化为 Java 对象
- 建议使用"类型:id"的键命名规范(如"user:123")
5.3 Spring Boot Redis缓存注解:简化缓存开发
Spring Boot提供了@Cacheable、@CachePut、@CacheEvict等缓存注解,通过声明式的方式快速实现缓存功能,无需手动调用RedisTemplate。这些注解底层基于Spring Cache抽象,可以与多种缓存实现(如Redis、EhCache等)无缝集成,大大简化了缓存开发流程。
5.3.1 开启缓存注解支持
在Spring Boot启动类上添加@EnableCaching注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching // 开启缓存注解支持,自动配置CacheManager
public class RedisSpringBootApplication {public static void main(String[] args) {SpringApplication.run(RedisSpringBootApplication.class, args);}
}
注意事项:
- 确保项目中已引入
spring-boot-starter-cache和spring-boot-starter-data-redis依赖 - 在application.properties/yml中配置Redis连接信息
- 默认情况下会自动配置RedisCacheManager
5.3.2 常用缓存注解详解
(1)@Cacheable:查询缓存
当方法被调用时,先从缓存中查询数据。若缓存存在,则直接返回缓存数据;若缓存不存在,则执行方法逻辑,并将结果存入缓存。
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {/*** 根据用户ID查询用户信息* @param userId 用户ID* @return 用户对象* * 缓存配置说明:* - value="userCache": 指定缓存名称(相当于Redis中的key前缀)* - key="#userId": 动态生成缓存key(使用SpEL表达式获取方法参数)* - unless="#result == null": 当方法返回null时不缓存结果*/@Cacheable(value = "userCache", key = "#userId", unless = "#result == null")public User getUserById(Long userId) {// 模拟从数据库查询数据(实际开发中替换为DAO层调用)System.out.println("从数据库查询用户,userId:" + userId);return new User(userId, "张三", 25, "男");}
}
典型应用场景:
- 高频访问但数据变化不频繁的查询,如商品详情、用户信息等
- 复杂计算结果的缓存,如报表数据、统计结果等
(2)@CachePut:更新缓存
执行方法逻辑后,将结果存入缓存(无论缓存是否存在,都会覆盖原有缓存),常用于数据新增或更新场景。
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;@Service
public class UserService {/*** 保存或更新用户信息* @param user 用户对象* @return 更新后的用户对象* * 缓存配置说明:* - value="userCache": 缓存名称* - key="#user.id": 使用用户ID作为缓存key* - unless="#result == null": 更新失败不缓存*/@CachePut(value = "userCache", key = "#user.id", unless = "#result == null")public User saveOrUpdateUser(User user) {// 模拟数据库新增/更新操作System.out.println("数据库新增/更新用户,user:" + user);return user; // 将更新后的用户对象存入缓存}
}
使用建议:
- 方法返回值必须是需要缓存的对象
- 确保key与查询方法@Cacheable的key一致
- 适用于insert和update操作
(3)@CacheEvict:删除缓存
执行方法逻辑后,删除指定缓存,常用于数据删除场景。
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;@Service
public class UserService {/*** 删除用户* @param userId 用户ID* * 缓存配置说明:* - value="userCache": 缓存名称* - key="#userId": 指定要删除的缓存key* - allEntries=false: 默认只删除指定key的缓存*/@CacheEvict(value = "userCache", key = "#userId", allEntries = false)public void deleteUser(Long userId) {// 模拟数据库删除操作System.out.println("数据库删除用户,userId:" + userId);}/*** 清空用户缓存* * 缓存配置说明:* - value="userCache": 缓存名称* - allEntries=true: 删除该缓存名称下的所有缓存*/@CacheEvict(value = "userCache", allEntries = true)public void clearUserCache() {System.out.println("清空用户缓存");}
}
删除策略选择:
- 单条删除:使用key指定要删除的缓存项
- 批量删除:设置allEntries=true清空整个缓存区域
- 可在方法执行前删除(beforeInvocation=true)
(4)@Caching:组合缓存注解
当一个方法需要同时使用多个缓存注解(如同时更新和删除缓存)时,可使用@Caching组合注解。
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;@Service
public class UserService {/*** 更新用户信息并清空用户列表缓存* @param user 用户对象* @return 更新后的用户对象*/@Caching(put = {// 更新用户缓存@CachePut(value = "userCache", key = "#user.id")},evict = {// 删除用户列表缓存(假设存在用户列表缓存)@CacheEvict(value = "userListCache", allEntries = true)})public User updateUserAndClearListCache(User user) {// 模拟数据库更新操作System.out.println("更新用户并清空用户列表缓存,user:" + user);return user;}
}
适用场景:
- 需要同时操作多个缓存区域
- 需要同时执行put和evict操作
- 复杂的缓存策略组合
六、Redis 进阶:持久化、高可用与性能优化
在实际生产环境中,仅掌握基础使用还不够,还需深入理解 Redis 的持久化机制、高可用架构配置及性能优化技巧,确保 Redis 服务稳定高效运行。本章将详细介绍这些关键内容。
6.1 Redis 持久化机制
Redis 提供两种持久化方式,可根据业务需求选择单独使用或组合使用,确保数据安全性和系统可靠性。
6.1.1 RDB(Redis Database):快照持久化
原理:在指定时间间隔内,将 Redis 内存中的数据生成二进制快照文件(.rdb 文件)并保存到磁盘。这种机制类似于数据库的全量备份。
触发方式:
手动触发:
save命令:阻塞 Redis 主线程直到快照完成,期间无法处理其他请求(生产环境不推荐)bgsave命令:Redis 会 fork 一个子进程进行快照操作,主进程继续提供服务(推荐方式)
自动触发:
- 通过 redis.conf 配置文件中的 save 指令设置触发条件
- 当 Redis 正常关闭时(shutdown命令)也会自动执行 RDB 持久化
典型配置示例:
# 900秒(15分钟)内至少1个key变化,触发RDB
save 900 1
# 300秒(5分钟)内至少10个key变化,触发RDB
save 300 10
# 60秒内至少10000个key变化,触发RDB
save 60 10000# RDB文件名称
dbfilename dump.rdb
# 工作目录(保存RDB文件和AOF文件的目录)
dir /var/lib/redis
优缺点分析:
- 优点:
- 快照文件体积小,占用空间少
- 恢复速度快,特别适合大规模数据恢复
- 对 Redis 性能影响小,适合定期备份
- 缺点:
- 可能丢失最后一次快照后的所有数据(如 Redis 异常崩溃时)
- 当数据量很大时,fork 子进程的过程可能耗时较长,导致短暂的服务停顿
6.1.2 AOF(Append Only File):日志持久化
原理:将 Redis 的每一条写命令(如 SET、HSET)以协议文本格式追加到 AOF 日志文件中,Redis 重启时通过重新执行日志中的命令来重建数据。
AOF 工作流程:
- 命令执行后写入 AOF 缓冲区
- 根据配置的刷盘策略将缓冲区内容同步到磁盘
- 定期进行 AOF 重写(rewrite)压缩文件体积
配置详解:
# 开启AOF(默认关闭,需手动设置为yes)
appendonly yes# AOF文件名称
appendfilename "appendonly.aof"# AOF刷盘策略(关键配置)
# everysec:每秒刷盘一次(推荐,平衡性能和数据安全性)
# always:每执行一条命令刷盘一次(数据零丢失,性能较差)
# no:由操作系统决定刷盘时机(性能最好,数据丢失风险最高)
appendfsync everysec# AOF重写触发条件
# 当前AOF文件大小比起最后一次重写时的大小增长率达到100%时触发
auto-aof-rewrite-percentage 100
# AOF文件最小重写大小,避免文件很小就重写
auto-aof-rewrite-min-size 64mb# 加载AOF时发现文件末尾不完整时的处理方式
aof-load-truncated yes
AOF 重写机制:
- 原理:创建一个新的 AOF 文件,包含重建当前数据集所需的最少命令集
- 触发方式:
- 自动触发:根据 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 配置
- 手动触发:执行
BGREWRITEAOF命令
优缺点分析:
- 优点:
- 数据安全性高,最多丢失1秒数据(everysec配置下)
- AOF 文件可读性强,便于问题排查
- 通过重写机制可以压缩文件大小
- 缺点:
- AOF 文件通常比 RDB 文件大
- 恢复速度比 RDB 慢
- 在高写入负载下,AOF 可能影响 Redis 性能
6.1.3 RDB 与 AOF 组合使用
生产环境中推荐同时开启 RDB 和 AOF,充分发挥两者的优势:
数据恢复策略:
- 日常恢复:优先使用 AOF 恢复(数据更完整)
- 大规模数据恢复:若 AOF 文件过大,先使用 RDB 快速恢复基础数据,再通过 AOF 补充后续数据
典型配置组合:
- RDB 配置为每天全量备份一次
- AOF 配置为每秒刷盘
- 定期将 RDB 文件和 AOF 文件备份到异地
注意事项:
- Redis 4.0+ 支持混合持久化(aof-use-rdb-preamble),AOF 文件前半部分是 RDB 格式,后半部分是 AOF 格式,可以结合两者优点
- 需要监控磁盘空间使用情况,避免持久化文件占满磁盘
6.2 Redis 高可用配置
为避免 Redis 单点故障,需搭建高可用架构,确保服务持续可用。Redis 提供了多种高可用方案,适用于不同场景。
6.2.1 主从复制(Master-Slave)
原理:将一台 Redis 服务器(Master)的数据同步到多台 Redis 服务器(Slave),实现数据冗余和读写分离。
核心特性:
- 异步复制:Slave 异步从 Master 同步数据
- 读写分离:Master 处理写请求,Slave 处理读请求
- 级联复制:Slave 可以作为其他 Slave 的 Master
配置步骤(以 1 主 2 从为例):
- 准备 3 台 Redis 服务器(假设IP为192.168.1.100-102)
- 配置 Master(192.168.1.100): 确保 redis.conf 中有以下配置:
# 如果需要密码认证 requirepass your_password - 配置 Slave(192.168.1.101 和 192.168.1.102):
# 指定Master的地址和端口 replicaof 192.168.1.100 6379# 若Master设置了密码,需配置认证密码 masterauth your_password# 可选:设置Slave只读 replica-read-only yes - 验证主从状态:
- 在 Master 上执行
info replication查看 Slave 连接信息 - 在 Slave 上执行
info replication查看复制状态
- 在 Master 上执行
主从复制过程:
- Slave 启动后向 Master 发送 SYNC 命令
- Master 执行 BGSAVE 生成 RDB 文件并发送给 Slave
- Master 将生成 RDB 期间的新命令缓存起来,之后发送给 Slave
- 之后 Master 将所有写命令异步发送给 Slave
注意事项:
- 复制延迟问题:网络延迟或 Slave 负载过高可能导致数据不一致
- 数据过期问题:Redis 的过期策略在主从模式下有特殊处理
- 复制中断处理:可通过
replica-serve-stale-data yes配置Slave在断开连接时是否继续提供服务
6.2.2 哨兵模式(Sentinel)
原理:在主从复制的基础上,增加哨兵进程监控节点状态,实现自动故障转移。
哨兵核心功能:
- 监控:持续检查 Master 和 Slave 是否正常运行
- 通知:当监控的 Redis 实例出现问题时,可以通过API通知管理员
- 自动故障转移:当 Master 不可用时,自动选举新的 Master
- 配置提供者:为客户端提供最新的 Master 地址
配置步骤(3个哨兵节点):
- 准备哨兵配置文件 sentinel.conf:
port 26379 sentinel monitor mymaster 192.168.1.100 6379 2 sentinel auth-pass mymaster your_password sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 - 启动哨兵:
redis-sentinel /path/to/sentinel.conf - 验证哨兵状态:
redis-cli -p 26379 info sentinel
故障转移流程:
- 哨兵检测到 Master 不可达(超过 down-after-milliseconds 时间)
- 哨兵集群选举领导者哨兵(需要至少 quorum 数量的哨兵同意)
- 领导者哨兵从 Slave 中选择新的 Master(基于优先级、复制偏移量等)
- 将其他 Slave 重新配置为复制新的 Master
- 通知客户端配置变更
客户端访问哨兵模式的最佳实践:
- 客户端应连接哨兵获取当前 Master 地址
- 客户端应订阅哨兵的
+switch-master事件处理主从切换 - 建议使用支持哨兵的 Redis 客户端库(如Jedis、Lettuce)
6.2.3 Redis Cluster(集群)
原理:将数据分片存储到多个节点,每个节点负责一部分哈希槽(共16384个槽),同时每个分片可配置主从复制。
集群特性:
- 数据分片:自动将数据分布到多个节点
- 高可用:每个分片可以配置主从复制
- 线性扩展:可通过增加节点扩展集群容量和性能
- 客户端路由:客户端可直接连接正确的节点
配置步骤(3主3从集群):
- 准备6台服务器(192.168.1.100-105),确保端口6379和16379开放
- 每个节点的 redis.conf 配置:
cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 15000 cluster-announce-ip 192.168.1.100 # 每台填写自己的IP cluster-announce-port 6379 cluster-announce-bus-port 16379 - 启动所有节点后,创建集群:
redis-cli --cluster create \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 \--cluster-replicas 1 - 验证集群状态:
redis-cli -c -h 192.168.1.100 -p 6379 cluster info redis-cli -c -h 192.168.1.100 -p 6379 cluster nodes
数据分片原理:
- Redis 使用 CRC16(key) mod 16384 计算 key 所属的槽位
- 每个节点负责一部分连续的槽位
- 客户端可以缓存槽位到节点的映射关系
集群管理命令:
cluster meet:将节点加入集群cluster addslots:分配槽位给节点cluster replicate:设置节点为从节点cluster failover:手动触发故障转移
集群扩展操作:
- 添加新节点:
redis-cli --cluster add-node new_host:new_port existing_host:existing_port - 迁移槽位:
redis-cli --cluster reshard host:port - 删除节点:
redis-cli --cluster del-node host:port node_id
6.3 Redis 性能优化建议
6.3.1 硬件优化
内存优化:
- 为 Redis 分配足够的内存,避免频繁交换
- 使用
maxmemory限制 Redis 最大内存使用量 - 考虑使用大页内存(transparent huge pages)
CPU优化:
- Redis 6.0+ 支持多线程IO(I/O threading),可配置:
io-threads 4 io-threads-do-reads yes - 在高并发场景下,可考虑使用多个 Redis 实例分担负载
磁盘优化:
- 使用 SSD 存储持久化文件
- 确保磁盘有足够的写入带宽
- 对于 AOF,可以考虑使用
no-appendfsync-on-rewrite yes减少重写时的磁盘压力
6.3.2 配置优化
内存管理配置:
# 内存淘汰策略(当内存达到maxmemory时)
maxmemory-policy allkeys-lru# 设置最大内存(例如4GB)
maxmemory 4gb# 避免内存碎片
activedefrag yes
网络配置优化:
# 提高TCP连接队列大小
tcp-backlog 511# 客户端空闲超时时间
timeout 0 # 0表示不超时# 最大客户端连接数
maxclients 10000
持久化优化:
# 禁用持久化(仅用于纯缓存场景)
save ""
appendonly no# 如果使用RDB,适当调整save参数
save 3600 1 # 1小时内至少有1个变化则保存
6.3.3 开发优化
键设计原则:
- 使用简洁但有意义的键名,如
user:1000:profile - 避免使用大键(如包含百万元素的hash)
- 为键设置合理的过期时间
命令优化:
- 使用批量操作命令:
MSET替代多个SETHMGET替代多个HGET
- 使用管道(pipeline)减少网络往返:
pipeline = redis.pipeline() pipeline.set('key1', 'value1') pipeline.set('key2', 'value2') pipeline.execute() - 避免阻塞命令:
- 避免在生产环境使用
KEYS,改用SCAN - 谨慎使用
FLUSHALL/FLUSHDB
- 避免在生产环境使用
客户端优化:
- 使用连接池管理连接
- 合理设置连接池大小
- 实现客户端缓存(Redis 6.0+的客户端缓存功能)
监控与调优:
- 定期检查
info命令输出 - 监控慢查询(
slowlog get) - 使用
redis-benchmark进行性能测试 - 考虑使用Redis的Lua脚本减少网络开销
