1. Spring Data Redis 简介
Spring Data Redis 是 Spring Data 家族的一部分,它为 Spring 应用提供了对 Redis 存储的高级抽象。它屏蔽了底层连接库(如 Jedis 或 Lettuce)的复杂实现细节,使开发者能够通过统一的 API 与 Redis 进行交互。
核心价值
- 连接管理:自动管理 Redis 连接的生命周期及连接池。
- 操作封装:将 Redis 原始命令封装为面向对象的 API。
- 异常转化:将底层的 Redis 异常转换为 Spring 统一的
DataAccessException体系。
2. RedisTemplate 介绍
RedisTemplate是 Spring Data Redis 的核心类,它提供了执行 Redis 操作的模板方法。
数据结构操作映射
RedisTemplate将 Redis 的基本数据结构划分为不同的操作接口:
| 接口方法 | 对应的 Redis 数据结构 |
|---|---|
opsForValue() | String(字符串) |
opsForHash() | Hash(哈希) |
opsForList() | List(列表) |
opsForSet() | Set(集合) |
opsForZSet() | ZSet(有序集合) |
3. Spring Boot 集成步骤
在Spring Boot 3.4.2环境下,集成步骤如下:
3.1 引入依赖 (pom.xml)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>3.2 配置连接信息 (application.yaml)
spring:data:redis:host:127.0.0.1port:6379database:0jedis:pool:max-active:8# 最大连接数max-idle:8# 最大空闲连接4. RedisTemplate 常见操作示例
4.1 String 字符串操作
@AutowiredprivateRedisTemplate<String,Object>redisTemplate;publicvoidtestString(){// 写入数据redisTemplate.opsForValue().set("user:name","czl");// 读取数据Stringvalue=(String)redisTemplate.opsForValue().get("user:name");// 带过期时间的写入redisTemplate.opsForValue().set("code","1234",Duration.ofMinutes(5));}4.2 Hash 哈希操作
publicvoidtestHash(){Stringkey="user:info:1";// 存入单个字段redisTemplate.opsForHash().put(key,"name","tiger");// 获取单个字段Objectname=redisTemplate.opsForHash().get(key,"name");// 获取全部字段Map<Object,Object>entries=redisTemplate.opsForHash().entries(key);}4.3 List 列表操作
publicvoidtestList(){Stringkey="queue:task";// 从左侧推入redisTemplate.opsForList().leftPush(key,"task1");// 从右侧弹出Objecttask=redisTemplate.opsForList().rightPop(key);}4.4 Set 集合操作
publicvoidtestSet(){Stringkey="tags";// 添加元素redisTemplate.opsForSet().add(key,"java","redis","spring");// 判断是否存在BooleanisMember=redisTemplate.opsForSet().isMember(key,"java");}5. RedisTemplate 与 Jedis 实例关系
在 Spring Boot 应用中,RedisTemplate与底层Jedis实例之间并非一比一的绑定关系,而是一种典型的“多对一”或“一对多”的解耦模型。
5.1 实例数量对比
一对多关系
- RedisTemplate。
在一个 Spring Context 中,你通常只需要配置一个RedisTemplate<String, Object>就能在全工程中通过@Autowired共享。它是无状态的,专门负责定义操作逻辑和序列化规则。不过也可以配置多个定制化RedisTemplate,关键是与Jedis实例是一对多关系。 - Jedis 实例:是一个连接池。
底层通过JedisPool维护了多个长连接实例。具体数量由你在application.yaml中配置的max-active决定。
5.2 动态映射关系
当你调用redisTemplate.opsForValue().set(...)时,发生的数量调度如下:
- 借用阶段:
RedisTemplate拦截到请求,从底层的JedisPool中申请一个当前的空闲Jedis实例。 - 独占阶段:在命令执行期间,这个特定的
Jedis实例被该线程独占,其他线程无法使用它。 - 归还阶段:操作完成后,
RedisTemplate自动释放连接,将该Jedis实例归还给池中。
5.3 结论
- 逻辑层:你只需要维护1 个
RedisTemplate即可处理所有的并发请求。 - 物理层:底层的
JedisPool会根据并发量动态分配N 个Jedis实例来支持这 1 个模板的工作。
6. RedisTemplate 序列化器深度解析
6.1 为什么要使用序列化器?
Redis 数据库本身是二进制安全的,它只能存储字节序列(byte array)。而 Java 是面向对象的语言,我们操作的是String、Integer或自定义的UserDO对象。
序列化器(Serializer)的作用:就是充当 Java 对象与 Redis 字节流之间的“翻译官”。
- 存入时:将 Java 对象转换成二进制字节。
- 读取时:将二进制字节转换回 Java 对象。
示例:不配置序列化器的后果
假设你使用 Spring 默认的RedisTemplate(未手动配置序列化器),执行以下操作:
redisTemplate.opsForValue().set("name","虎哥");虽然代码运行成功,但当你通过命令行或 Redis Insight 查看时,你会发现:
- Key变成了:
\xac\xed\x00\x05t\x00\x04name - Value变成了:
\xac\xed\x00\x05t\x00\x06\xe8\x99\x8e\xe5\x93\xa5
这是因为默认使用了JdkSerializationRedisSerializer,它在数据前添加了 Java 序列化协议的魔数(\xac\xed)和类元数据。这导致数据在 Redis 中不可读,且其他语言(如 Python、Go)无法解析。
6.2 序列化器的具体用法
在 Spring Boot 3.4.2 环境下,我们通过自定义RedisConfig类来指定序列化规则。
1. 配置代码示例
通常建议Key 使用 String 序列化,Value 使用 JSON 序列化,以达到最佳的可读性和兼容性。
@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactoryfactory){RedisTemplate<String,Object>template=newRedisTemplate<>();template.setConnectionFactory(factory);// 1. 指定 Key 的序列化器:StringRedisSerializer// 效果:存入 "name",Redis 中显示为字符串 "name"template.setKeySerializer(newStringRedisSerializer());template.setHashKeySerializer(newStringRedisSerializer());// 2. 指定 Value 的序列化器:GenericJackson2JsonRedisSerializer// 效果:存入对象,Redis 中显示为标准 JSON 字符串template.setValueSerializer(newGenericJackson2JsonRedisSerializer());template.setHashValueSerializer(newGenericJackson2JsonRedisSerializer());returntemplate;}}2. 配置后的存储效果对比
在使用上述配置后,同样的set("name", "虎哥")操作,Redis 中的表现如下:
| 存储部分 | 默认 Jdk 序列化(乱码) | 配置后(明文/JSON) |
|---|---|---|
| Key | \xac\xed\x00\x05t\x00\x04name | name |
| Value | \xac\xed\x00\x05t\x00... | "虎哥" |
| 对象 Value | 二进制乱码 | {"id":1, "nickname":"虎哥"} |
7. Jackson 序列化器的内存占用问题及优化方案
7.1 Jackson 序列化器的“多余”字节码问题
在使用GenericJackson2JsonRedisSerializer时,为了实现自动反序列化,Jackson 会在生成的 JSON 字符串中额外添加一个名为@class的字段。
示例:自动序列化的存储结果
假设存储一个简单的UserDO对象:
{"@class":"cn.iocoder.boot.springdataredis.UserDO","id":1,"nickname":"虎哥"}- 问题所在:这个
@class字段包含了类全路径名,在大型项目中,这个字符串往往比业务数据本身还要长。 - 内存影响:如果你有数百万个 Key,这些重复的类路径字符串会额外占用大量的 Redis 内存空间,增加硬件成本和网络带宽压力。
7.2 解决方案:手动序列化(String + ObjectMapper)
为了追求极致的内存利用率,业界常用的方案是:全局只使用StringRedisTemplate(或 String 序列化器),在代码逻辑中手动利用ObjectMapper(或JsonUtils)进行对象转换。
1. 核心思路
- 存储时:调用
JsonUtils.toJsonString(obj)得到纯净的 JSON,不带@class标记,存入 Redis。 - 读取时:取出 String,根据业务需要调用
JsonUtils.parseObject(json, UserDO.class)还原对象。
2. 代码实现示例
基于你已有的Spring Boot 3.4.2环境 和JsonUtils:
@ServicepublicclassUserService{@AutowiredprivateStringRedisTemplatestringRedisTemplate;// 默认已有的 String 模板publicvoidsaveUserManual(UserDOuser){// 1. 手动序列化为纯净 JSON 字符串StringjsonStr=JsonUtils.toJsonString(user);// 2. 存入 RedisstringRedisTemplate.opsForValue().set("user:"+user.getId(),jsonStr);}publicUserDOgetUserManual(Longid){// 3. 获取纯净 JSON 字符串StringjsonStr=stringRedisTemplate.opsForValue().get("user:"+id);// 4. 手动反序列化,通过 Class 参数明确目标类型returnJsonUtils.parseObject(jsonStr,UserDO.class);}}