Redis-常见 Java 客户端
一、核心 Java 客户端对比
| 客户端 | 核心特点 | 适用场景 |
|---|---|---|
| Jedis⭐ | 以 Redis 命令为方法名,学习成本低;但线程不安全,需依赖连接池使用 | 简单场景、非高并发需求 |
| Lettuce⭐ | 基于 Netty 实现,支持同步 / 异步 / 响应式编程,线程安全;支持哨兵 / 集群 / 管道 | 高并发、分布式场景(Spring Boot 默认) |
| Redisson | 基于 Redis 实现分布式数据结构(Lock/Queue/Map 等),功能强大,封装程度高 | 分布式锁、分布式集合等场景 |
| SpringDataRedis⭐⭐⭐ | 基于 Jedis/Lettuce 封装的 Spring 生态组件,简化操作,适配 Spring 全家桶 | Spring/Spring Boot 项目(主流选择) |
| 其他客户端 | java-redis-client(轻量无依赖)、vertx-redis-client(Vert.x 异步) | 极小项目、Vert.x 生态项目 |
二、SpringDataRedis 核心解析
1. 核心定位
SpringDataRedis 是 Spring Data 生态下的 Redis 操作组件,并非独立客户端,而是对 Jedis、Lettuce 等底层客户端的封装,提供统一、简洁的 API,适配 Spring 的依赖注入、事务管理等特性,是 Spring Boot 项目操作 Redis 的首选方案。
2. RedisTemplate 的操作 API 分类(按数据类型)
SpringDataRedis 的RedisTemplate将不同数据类型的操作封装为独立的 API 类,便于精准操作:
| API 方法 | 返回值类型 | 说明 |
|---|---|---|
redisTemplate.opsForValue() |
ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() |
HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() |
ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() |
SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() |
ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate本身 |
- | 执行通用 Redis 命令(如过期时间、删除键) |
3. 核心组件
| 组件 | 作用 |
|---|---|
RedisTemplate |
通用模板类,支持任意类型 Key/Value(需指定序列化器) |
StringRedisTemplate |
专用模板类,Key/Value 均为 String 类型(默认 String 序列化,无需额外配置) |
RedisConnectionFactory |
连接工厂,适配 Jedis/Lettuce,管理 Redis 连接(Spring Boot 自动配置) |
RedisSerializer |
序列化器,解决对象与字节数组的转换(如 String、Jackson2Json、JDK 序列化) |
4. 快速集成(Spring Boot)
(1)依赖引入(Maven)
xml
<!-- SpringDataRedis核心依赖(自动引入Lettuce) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖(Lettuce默认用内置池,可选commons-pool2优化) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Jackson核心依赖(支持JSON序列化,处理JDK8时间类型) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- Lombok(可选,简化实体类编写) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
(2)配置(application.yml)
yaml
spring:
data:
redis:
host: 127.0.0.1 # Redis地址
port: 6379 # 端口
password: "" # 密码(无则空)
database: 0 # 数据库索引(默认0)
timeout: 10000ms # 连接超时时间
lettuce: # 底层客户端(默认Lettuce,可替换为jedis)
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: -1ms # 连接等待时间(-1表示无限制)
(3)各数据类型操作示例
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisTemplateDemo {
@Autowired
private RedisTemplate<String, Object> redisTemplate; // 自定义序列化的模板
@Autowired
private StringRedisTemplate stringRedisTemplate; // String专用模板
// 1. String类型操作(ValueOperations)
public void operateString() {
ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
// 设置值(过期时间1小时)
valueOps.set("user:name:1000", "张三", 3600, TimeUnit.SECONDS);
// 获取值
Object name = valueOps.get("user:name:1000");
// 自增(计数器)
valueOps.increment("user:login:count:1000");
}
// 2. Hash类型操作(HashOperations)
public void operateHash() {
HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
// 存储用户信息
hashOps.put("user:info:1000", "age", "25");
hashOps.put("user:info:1000", "phone", "13800000000");
// 获取单个字段
Object age = hashOps.get("user:info:1000", "age");
// 获取所有字段
Map<Object, Object> userInfo = hashOps.entries("user:info:1000");
}
// 3. List类型操作(ListOperations)
public void operateList() {
ListOperations<String, Object> listOps = redisTemplate.opsForList();
// 左推元素(消息队列生产)
listOps.leftPush("msg:queue", "消息1");
listOps.leftPush("msg:queue", "消息2");
// 右取元素(消息队列消费)
Object msg = listOps.rightPop("msg:queue");
// 获取列表范围
listOps.range("msg:queue", 0, -1);
}
// 4. Set类型操作(SetOperations)
public void operateSet() {
SetOperations<String, Object> setOps = redisTemplate.opsForSet();
// 添加元素
setOps.add("user:tags:1000", "学生", "程序员");
// 判断元素是否存在
boolean hasTag = setOps.isMember("user:tags:1000", "学生");
// 获取所有元素
Set<Object> tags = setOps.members("user:tags:1000");
}
// 5. SortedSet类型操作(ZSetOperations)
public void operateZSet() {
ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
// 添加商品销量排行(分数=销量)
zSetOps.add("goods:rank:hot", "商品A", 100);
zSetOps.add("goods:rank:hot", "商品B", 200);
// 获取前10名(降序)
Set<Object> top10 = zSetOps.reverseRange("goods:rank:hot", 0, 9);
}
// 6. 通用命令(直接用RedisTemplate)
public void operateCommon() {
// 设置键过期时间
redisTemplate.expire("user:name:1000", 30, TimeUnit.MINUTES);
// 删除键
redisTemplate.delete("user:name:1000");
// 判断键是否存在
boolean exists = redisTemplate.hasKey("user:name:1000");
}
}
5. 高级配置(自定义序列化器)
(1)JDK 序列化的问题(深度解析)
RedisTemplate默认使用JdkSerializationRedisSerializer作为序列化器,该序列化方式基于 JDK 自带的ObjectOutputStream实现对象与字节数组的转换,存在以下核心问题:
-
乱码问题 :序列化后的字节数组在 Redis 客户端(如 redis-cli)中查看时,会显示为
\xAC\xED\x00\x05t\x00\x03张三这类不可读的十六进制乱码,无法直接通过 Redis 命令行排查数据内容,调试和运维成本极高; -
体积过大 :JDK 序列化会额外存储类的全限定名、字段描述符、继承关系等元数据,即使是简单对象(如仅包含 id 和 name 的 User 对象),序列化后的体积也比 JSON 格式大 3-5 倍;例如一个包含
id=1000、name="张三"的 User 对象,JDK 序列化后约占 200 字节,而 Jackson2Json 序列化仅占 40 字节左右; -
兼容性差 :JDK 序列化强依赖类的结构,若类的序列化 ID(serialVersionUID)未指定、字段增减或修饰符变更(如 public 改 private),反序列化时会直接抛出
InvalidClassException,跨服务、跨版本部署时极易出现序列化异常; -
性能损耗:JDK 序列化的序列化 / 反序列化速度远低于 JSON 类序列化器,在高并发场景下,大量对象的序列化操作会显著增加 CPU 开销,降低 Redis 操作的整体吞吐量;
-
无法跨语言解析:JDK 序列化是 Java 专属的格式,若其他语言(如 Python、Go)的服务需要读取 Redis 中的数据,无法解析 JDK 序列化后的字节数组,导致 Redis 数据无法跨语言复用。
(2)自定义 Jackson 序列化器(解决 JDK 序列化问题)
推荐使用Jackson2JsonRedisSerializer替代 JDK 序列化,该序列化器基于 JSON 格式,兼具可读性、轻量化、跨语言兼容的优势:
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 1. Key/HashKey序列化:String(避免Key出现乱码)
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// 2. 对比:JDK序列化(默认,不推荐)
// JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
// template.setValueSerializer(jdkSerializer);
// template.setHashValueSerializer(jdkSerializer);
// 3. Value/HashValue序列化:Jackson2Json(解决JDK序列化的所有问题)
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule()); // 注册JDK8时间模块(支持LocalDateTime/LocalDate)
om.findAndRegisterModules(); // 自动注册所有Jackson模块,兼容更多数据类型
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 保留类型信息,解决反序列化时的类型丢失问题
jacksonSerializer.setObjectMapper(om);
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
(3)Jackson2JsonRedisSerializer 使用示例(存储 / 读取复杂对象)
步骤 1:定义待序列化的实体类
java
import lombok.Data;
import java.time.LocalDateTime;
@Data // 需引入lombok依赖,也可手动写get/set
public class User {
private Long id;
private String name;
private Integer age;
private String phone;
private LocalDateTime createTime; // JDK8时间类型
}
步骤 2:使用自定义 RedisTemplate 操作对象
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@Component
public class JacksonRedisSerializerDemo {
@Autowired
private RedisTemplate<String, Object> redisTemplate; // 注入自定义序列化的RedisTemplate
// 存储User对象到Redis
public void saveUserToRedis() {
// 1. 构建User对象
User user = new User();
user.setId(1000L);
user.setName("张三");
user.setAge(25);
user.setPhone("13800000000");
user.setCreateTime(LocalDateTime.now());
// 2. 获取ValueOperations操作器
ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
// 3. 存储对象(Jackson2Json序列化),设置过期时间2小时
valueOps.set("user:detail:1000", user, 2, TimeUnit.HOURS);
System.out.println("User对象已通过Jackson2Json序列化存储到Redis");
}
// 从Redis读取User对象
public User getUserFromRedis() {
// 1. 获取ValueOperations操作器
ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
// 2. 读取对象(自动反序列化为User类型)
Object result = valueOps.get("user:detail:1000");
// 3. 类型转换(因配置了enableDefaultTyping,可直接强转)
User user = (User) result;
System.out.println("从Redis读取的User对象:" + user);
return user;
}
}
步骤 3:测试效果
java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class RedisTestRunner implements CommandLineRunner {
@Autowired
private JacksonRedisSerializerDemo jacksonDemo;
@Override
public void run(String... args) throws Exception {
// 存储对象
jacksonDemo.saveUserToRedis();
// 读取对象
User user = jacksonDemo.getUserFromRedis();
System.out.println("用户ID:" + user.getId());
System.out.println("用户姓名:" + user.getName());
System.out.println("创建时间:" + user.getCreateTime());
}
}
效果验证(redis-cli 中查看)
bash
# 执行命令查看存储的内容
127.0.0.1:6379> GET user:detail:1000
# 输出(清晰的JSON格式,可直接阅读)
"{
"@class":"com.example.demo.entity.User",
"id":1000,"name":"张三",
"age":25,
"phone":"13800000000",
"createTime":[2025,12,15,22,30,50,123456000]
}"
补充:JSON 序列化优化(StringRedisTemplate 手动序列化)
1. JSON 序列化的内存开销问题
Jackson2JsonRedisSerializer 会自动在 JSON 中写入 @class 字段(存储类全限定名),用于反序列化识别类型,但会产生额外内存开销:
json
{
"@class": "com.example.demo.entity.User", // 冗余的类信息
"id": 1000,
"name": "张三",
"age": 25,
"phone": "13800000000",
"createTime": [2025,12,15,22,30,50,123456000]
}
大量存储时,@class 字段会浪费 Redis 内存,推荐使用 StringRedisTemplate 手动序列化,剔除冗余字段。
2. 通用手动序列化工具类
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.util.concurrent.TimeUnit;
/**
* StringRedisTemplate 手动序列化工具类(适配User实体,无冗余@class字段)
*/
@Component
public class RedisJsonUtil {
// 注入StringRedisTemplate(默认String序列化,无乱码)
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 自定义ObjectMapper(适配LocalDateTime,与RedisConfig中一致)
private static final ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.registerModule(new JavaTimeModule()); // 支持JDK8时间类型
OBJECT_MAPPER.findAndRegisterModules();
}
// ========== 通用序列化方法(存储对象) ==========
public <T> void set(String key, T obj) {
try {
// 手动将对象转为JSON字符串(无@class字段)
String json = OBJECT_MAPPER.writeValueAsString(obj);
stringRedisTemplate.opsForValue().set(key, json);
} catch (JsonProcessingException e) {
throw new RuntimeException("Redis序列化对象失败", e);
}
}
// 重载:带过期时间的存储
public <T> void set(String key, T obj, long timeout, TimeUnit unit) {
try {
String json = OBJECT_MAPPER.writeValueAsString(obj);
stringRedisTemplate.opsForValue().set(key, json, timeout, unit);
} catch (JsonProcessingException e) {
throw new RuntimeException("Redis序列化对象失败(带过期时间)", e);
}
}
// ========== 通用反序列化方法(读取对象) ==========
public <T> T get(String key, Class<T> clazz) {
String json = stringRedisTemplate.opsForValue().get(key);
if (json == null || json.isEmpty()) {
return null;
}
try {
// 手动将JSON转为指定类型对象
return OBJECT_MAPPER.readValue(json, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException("Redis反序列化对象失败", e);
}
}
// ========== 复用原有通用命令 ==========
public Boolean expire(String key, long timeout, TimeUnit unit) {
return stringRedisTemplate.expire(key, timeout, unit);
}
public Boolean delete(String key) {
return stringRedisTemplate.delete(key);
}
public Boolean hasKey(String key) {
return stringRedisTemplate.hasKey(key);
}
}
3. 工具类使用示例
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@Component
public class StringRedisTemplateOptDemo {
@Autowired
private RedisJsonUtil redisJsonUtil;
// 存储User对象(手动序列化,无@class冗余)
public void saveUserWithOpt() {
// 构建User对象
User user = new User();
user.setId(1000L);
user.setName("张三");
user.setAge(25);
user.setPhone("13800000000");
user.setCreateTime(LocalDateTime.now());
// 调用工具类存储(2小时过期)
redisJsonUtil.set("user:detail:1000", user, 2, TimeUnit.HOURS);
System.out.println("User对象已通过手动JSON序列化存储(无@class字段)");
}
// 读取User对象(手动反序列化)
public User getUserWithOpt() {
// 调用工具类读取,指定User类型
User user = redisJsonUtil.get("user:detail:1000", User.class);
System.out.println("手动反序列化读取的User:" + user);
return user;
}
}
4. 效果验证(redis-cli 查看)
bash
# 执行命令查看
127.0.0.1:6379> GET user:detail:1000
# 输出(无@class字段,节省内存)
"{
"id":1000,"name":"张三",
"age":25,
"phone":"13800000000",
"createTime":"2025-12-15T22:45:30.123456"
}"
5. 对比总结
| 对比维度 | JDK 序列化 | Jackson2Json(自动) | StringRedisTemplate(手动) |
|---|---|---|---|
| 可读性 | 乱码不可读 | JSON 可读(含 @class) | JSON 可读(无冗余) |
| 数据体积 | 大(元数据多) | 中(含 @class) | 小(纯数据) |
| 序列化速度 | 慢 | 较快 | 最快 |
| 跨语言兼容 | 仅 Java | 支持多语言 | 支持多语言 |
| 版本兼容性 | 差 | 好 | 好 |
| 调试成本 | 高 | 中 | 低 |
| 内存开销 | 极大 | 中等 | 极小 |
-
序列化选型原则:
-
简单字符串场景:直接用
StringRedisTemplate(原生高效); -
复杂对象场景:优先用
StringRedisTemplate + 手动JSON序列化(节省内存); -
多类型混合场景(如 Set/List 存不同对象):用自定义
RedisTemplate + Jackson2Json(保留 @class,牺牲少量内存换便捷性)。
-
6. SpringDataRedis 最佳实践
-
优先用 StringRedisTemplate:大部分场景下 Key/Value 为字符串,避免序列化问题;仅需存储对象时用自定义 RedisTemplate。
-
避免频繁创建模板实例:RedisTemplate 是线程安全的,注入后复用即可,无需每次操作新建。
-
批量操作优化 :使用
executePipelined()实现管道操作,减少网络往返:java// 批量插入数据(管道) stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> { for (int i = 0; i < 1000; i++) { connection.stringCommands().set(("key:" + i).getBytes(), ("value:" + i).getBytes()); } return null; }); -
事务支持 :通过
multi()/exec()实现 Redis 事务,但需注意 Redis 事务仅保证原子性,不支持回滚:javastringRedisTemplate.multi(); // 开启事务 stringRedisTemplate.opsForValue().set("key1", "value1"); stringRedisTemplate.opsForValue().set("key2", "value2"); stringRedisTemplate.exec(); // 执行事务 -
缓存注解结合 :搭配
@Cacheable/@CachePut/@CacheEvict简化缓存操作(Spring Cache):java@Service public class UserService { // 查询时缓存,key为user:info:id @Cacheable(value = "user", key = "'info:' + #id") public User getUserById(Long id) { // 从数据库查询用户逻辑 return new User(id, "张三", 25); } }
三、其他客户端核心实践(精简版)
1. Jedis
java
// 连接池配置 + 资源自动归还
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(50);
try (Jedis jedis = new JedisPool(poolConfig, "127.0.0.1", 6379).getResource()) {
jedis.set("key", "value");
}
2. Redisson(分布式锁核心)
java
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("distributed-lock");
try {
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.unlock();
}
四、通用最佳实践
-
连接池配置 :根据并发量设置
max-active(50-200)、max-idle(20-50),避免连接耗尽。 -
异常处理 :捕获
RedisConnectionFailureException,实现 3 次以内重试(避免网络波动导致失败)。 -
大 Key 拆分:单个 Key 值超过 10KB 时拆分,避免序列化 / 传输耗时。
-
环境隔离 :测试 / 生产环境通过
database索引(如测试用 1,生产用 0)或 Key 前缀区分,避免数据污染。