一、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 项目中的典型应用场景
缓存热点数据
-
实现方式 :
java// Spring Cache示例 @Cacheable(value="products", key="#productId") public Product getProduct(String productId) { return productDao.findById(productId); } -
缓存策略 :
- 缓存穿透:布隆过滤器或空值缓存
- 缓存雪崩:随机过期时间
- 缓存击穿:互斥锁更新
分布式锁
-
原生实现:
java// 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实现:
javaRLock lock = redissonClient.getLock("order_lock_"+orderId); try { // 尝试加锁,最多等待100秒,上锁后30秒自动解锁 if(lock.tryLock(100, 30, TimeUnit.SECONDS)) { // 执行业务逻辑 } } finally { lock.unlock(); }
计数器与限流
-
计数器示例:
java// 文章阅读量计数 redisTemplate.opsForValue().increment("article:read:count:"+articleId); // 获取阅读量 Long count = redisTemplate.opsForValue().increment("article:read:count:"+articleId, 0); -
限流实现(滑动窗口算法):
javapublic 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实现简单队列:
java// 生产者 redisTemplate.opsForList().leftPush("queue:order", orderJson); // 消费者 String orderJson = redisTemplate.opsForList().rightPop("queue:order", 30, TimeUnit.SECONDS); -
Redis Stream实现(Redis 5.0+):
java// 创建消费者组 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集成:
XML<!-- pom.xml --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>properties# application.properties spring.session.store-type=redis spring.session.timeout=1800 # 30分钟java// 自动将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 为例)
bash
# 下载 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:编译与安装
bash
# 安装编译依赖(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
-
检查是否安装成功:
bashls -l /usr/local/bin/redis-*
步骤 3:配置并启动 Redis
基本配置管理:
bash
# 创建 Redis 配置目录
mkdir -p /etc/redis
# 复制配置文件模板
cp redis.conf /etc/redis/redis.conf
# 创建数据存储目录
mkdir -p /var/lib/redis
关键配置文件修改(/etc/redis/redis.conf):
bash
# 使用 vim 或其他编辑器修改配置文件
vim /etc/redis/redis.conf
需要修改的重要配置项:
-
网络配置:
conf# 允许远程访问(注释掉或修改为) # bind 127.0.0.1 bind 0.0.0.0 -
安全配置:
conf# 关闭保护模式 protected-mode no # 设置密码(生产环境必须) requirepass your_strong_password -
运行模式:
conf# 以守护进程方式运行 daemonize yes -
日志配置:
conf# 指定日志文件路径 logfile "/var/log/redis/redis-server.log" -
数据持久化(根据需求配置):
conf# RDB 持久化配置 save 900 1 save 300 10 save 60 10000 # AOF 持久化配置(可选) appendonly yes appendfsync everysec
启动 Redis 服务:
bash
# 创建日志目录
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
连接测试:
bash
# 本地连接测试
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
设置开机自启(可选):
bash
# 创建 systemd 服务文件
vim /etc/systemd/system/redis.service
添加以下内容:
ini
[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
然后执行:
bash
# 重新加载 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 依赖配置(推荐使用连接池):
XML
<!-- 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 依赖配置:
XML
<!-- 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+
开发环境验证
创建简单的测试类验证环境是否配置成功:
java
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")
生产环境注意事项
-
连接池配置:
-
必须使用连接池管理连接
-
推荐配置参数:
javaJedisPoolConfig 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
java
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.JedisDataException
jedis.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 连接池
java
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线程定期检测和清理无效连接
-
生产环境配置建议:
java// 典型生产环境配置 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 类型(无序、唯一)
java
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 类型(有序、唯一,基于分数排序)
java
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分数+5
System.out.println("新分数:" + jedis.zscore("rank:java", "lisi"));
// 输出:95.0
}
(3)其他数据结构操作
java
// 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部署模式,每种模式的配置方式如下:
单点模式配置示例
java
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();
}
}
}
其他部署模式配置示例
哨兵模式
java
config.useSentinelServers()
.addSentinelAddress("redis://sentinel1:26379")
.addSentinelAddress("redis://sentinel2:26379")
.setMasterName("mymaster");
集群模式
java
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 支持设置过期时间,适用于缓存场景。
java
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 的操作方法,适用于存储对象或结构化数据。
java
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")); // 输出:lisi
System.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 类型的封装,支持双向添加、删除和获取元素,适用于队列或栈的场景。
java
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 类型的封装,支持元素的添加、删除和集合运算,适用于标签或唯一值存储场景。
java
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]
// 创建另一个 Set
RSet<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 类型的封装,支持按分数排序和范围查询,适用于排行榜或优先级队列场景。
java
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块确保锁释放
典型应用场景
- 秒杀系统中的库存扣减
- 支付系统中的订单状态变更
- 分布式定时任务调度
- 重要数据的并发修改
java
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小时未领取自动退回
java
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 中添加以下依赖:
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 相关配置:
yaml
spring:
redis:
host: 192.168.1.100 # Redis服务地址,生产环境建议使用域名或内网IP
port: 6379 # Redis端口,默认6379
password: your_password # Redis密码(若未设,可省略)
database: 0 # Redis数据库索引(0-15),默认0
timeout: 5000ms # 连接超时时间
lettuce: # Lettuce客户端配置(若使用Jedis,需改为jedis配置)
pool: # 连接池配置
max-active: 100 # 最大连接数,默认8
max-idle: 20 # 最大空闲连接数,默认8
min-idle: 5 # 最小空闲连接数,默认0
max-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 配置示例(如需切换):
yaml
spring:
redis:
host: 192.168.1.100
port: 6379
jedis:
pool:
max-active: 100
max-idle: 20
min-idle: 5
max-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 的默认行为,推荐配置自定义序列化:
java
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 {
@Bean
public 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 的常见操作示例:
java
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 {
@Autowired
private 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 使用示例(操作对象)
定义实体类:
java
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;
}
对象操作示例:
java
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 {
@Autowired
private 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注解:
java
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:查询缓存
当方法被调用时,先从缓存中查询数据。若缓存存在,则直接返回缓存数据;若缓存不存在,则执行方法逻辑,并将结果存入缓存。
java
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:更新缓存
执行方法逻辑后,将结果存入缓存(无论缓存是否存在,都会覆盖原有缓存),常用于数据新增或更新场景。
java
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:删除缓存
执行方法逻辑后,删除指定缓存,常用于数据删除场景。
java
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组合注解。
java
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 持久化
典型配置示例:
conf
# 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)压缩文件体积
配置详解:
conf
# 开启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 中有以下配置:
conf# 如果需要密码认证 requirepass your_password -
配置 Slave(192.168.1.101 和 192.168.1.102):
conf# 指定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:
confport 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 -
启动哨兵:
bashredis-sentinel /path/to/sentinel.conf -
验证哨兵状态:
bashredis-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 配置:
confcluster-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 -
启动所有节点后,创建集群:
bashredis-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 -
验证集群状态:
bashredis-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:手动触发故障转移
集群扩展操作:
-
添加新节点:
bashredis-cli --cluster add-node new_host:new_port existing_host:existing_port -
迁移槽位:
bashredis-cli --cluster reshard host:port -
删除节点:
bashredis-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),可配置:
confio-threads 4 io-threads-do-reads yes -
在高并发场景下,可考虑使用多个 Redis 实例分担负载
磁盘优化:
- 使用 SSD 存储持久化文件
- 确保磁盘有足够的写入带宽
- 对于 AOF,可以考虑使用
no-appendfsync-on-rewrite yes减少重写时的磁盘压力
6.3.2 配置优化
内存管理配置:
conf
# 内存淘汰策略(当内存达到maxmemory时)
maxmemory-policy allkeys-lru
# 设置最大内存(例如4GB)
maxmemory 4gb
# 避免内存碎片
activedefrag yes
网络配置优化:
conf
# 提高TCP连接队列大小
tcp-backlog 511
# 客户端空闲超时时间
timeout 0 # 0表示不超时
# 最大客户端连接数
maxclients 10000
持久化优化:
conf
# 禁用持久化(仅用于纯缓存场景)
save ""
appendonly no
# 如果使用RDB,适当调整save参数
save 3600 1 # 1小时内至少有1个变化则保存
6.3.3 开发优化
键设计原则:
- 使用简洁但有意义的键名,如
user:1000:profile - 避免使用大键(如包含百万元素的hash)
- 为键设置合理的过期时间
命令优化:
-
使用批量操作命令:
MSET替代多个SETHMGET替代多个HGET
-
使用管道(pipeline)减少网络往返:
pythonpipeline = 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脚本减少网络开销