为什么需要序列化?
Redis 是一个内存数据库,它本身只能存储字符串、列表、哈希、集合、有序集合等几种基本数据类型,当我们想在 Redis 中存储一个 Java 对象时,由于 Java 对象是存在于内存中的复杂结构,无法直接存入 Redis。

序列化 就是将 Java 对象转换成字节流的过程,这样我们就可以将这个字节流存入 Redis,当我们从 Redis 中取出这个字节流时,再通过 反序列化 将其转换回原来的 Java 对象。
序列化 = 对象 -> 字节流,反序列化 = 字节流 -> 对象。
序列化的选择:JSON vs. 二进制
在 Java 与 Redis 交互的生态中,主要有两种序列化方式:
- JSON 序列化:将对象转换成 JSON 字符串(UTF-8 编码)。
- 二进制序列化:将对象转换成二进制字节流,这通常使用 Java 原生的
Serializable接口或更高效的第三方库(如 Kryo, Protobuf)。
| 特性 | JSON 序列化 | 二进制序列化 (如 Kryo) |
|---|---|---|
| 可读性 | 高,人类可读,便于调试。 | 低,二进制数据,无法直接阅读。 |
| 体积 | 较大,因为文本格式有冗余(如键名、引号)。 | 小,二进制格式非常紧凑,节省内存和网络带宽。 |
| 性能 | 较慢,文本解析和转换需要更多 CPU 资源。 | 快,二进制读写速度快,CPU 消耗低。 |
| 兼容性 | 极高,任何语言都能解析 JSON,适合跨语言系统。 | 较低,通常是语言相关的,但跨语言的二进制协议(如 Protobuf)兼容性也很好。 |
| 版本控制 | 好,JSON 对象增减字段时,反序列化通常不会出错。 | 差,Java 原生 Serializable 对增减字段非常敏感,容易出错,Kryo 等库有更好的版本支持机制。 |
如何选择?

- 开发调试、配置信息、跨语言交互:优先选择 JSON,它的可读性和兼容性是巨大优势。
- 高性能要求的缓存、数据量大的场景:优先选择 二进制序列化(如 Kryo),它在性能和空间效率上完胜。
实践方案
下面我们通过代码来演示几种常见的序列化方案。
准备工作:添加依赖
在你的 pom.xml 中添加 Redis 客户端依赖,我们使用流行的 Lettuce 作为客户端。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 如果需要 Jackson 来处理 JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 如果需要 Kryo -->
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.4.0</version>
</dependency>
方案一:使用 JSON 序列化 (Jackson)
这是 Spring Boot Data Redis 的默认配置之一,非常流行。
步骤:
- 配置 RedisTemplate:告诉
RedisTemplate使用哪个序列化器。 - 创建一个简单的 Java 对象。
示例代码:
1. 定义实体类
import com.fasterxml.jackson.annotation.JsonInclude;
import java.io.Serializable;
// 使用 @JsonInclude 忽略 null 值,使 JSON 更紧凑
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
// 构造方法、Getter 和 Setter 是必须的
public User() {}
public User(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
// 省略 getter 和 setter...
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
2. 配置 RedisTemplate
在 Spring Boot 配置类中,你可以自定义 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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jackson2JsonRedisSerializer);
// 设置 hash key 和 value 序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
3. 使用 RedisTemplate
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void saveUser() {
User user = new User(1L, "Alice", 30);
// 存入 Redis
redisTemplate.opsForValue().set("user:1", user);
System.out.println("User object has been saved to Redis.");
}
public User getUser(Long id) {
// 从 Redis 中取出
Object obj = redisTemplate.opsForValue().get("user:" + id);
if (obj instanceof User) {
return (User) obj;
}
return null;
}
}
Redis 中的存储结果:
user:1 这个 key 对应的 value 是一个 JSON 字符串:
{"@class":"com.example.demo.User","id":1,"name":"Alice","age":30}
注意 @class 字段,这是 Jackson 在序列化时添加的,用于在反序列化时知道要创建哪个类的实例。
方案二:使用 Kryo 二进制序列化
Kryo 是一个高性能的 Java 序列化库,非常适合对性能要求高的场景。
步骤:
- 创建一个自定义的
RedisSerializer,使用 Kryo。 - 配置
RedisTemplate使用我们自定义的序列化器。
示例代码:
1. 自定义 Kryo 序列化器
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class KryoRedisSerializer<T> implements RedisSerializer<T> {
// Kryo 不是线程安全的,所以每个线程都要有自己的 Kryo 实例
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
// 注册需要序列化的类,可以提高性能和避免问题
kryo.register(User.class);
// 关闭循环引用,避免栈溢出
kryo.setReferences(false);
return kryo;
});
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
Kryo kryo = kryoThreadLocal.get();
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream)) {
kryo.writeClassAndObject(output, t);
return output.toBytes();
}
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
Kryo kryo = kryoThreadLocal.get();
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream)) {
@SuppressWarnings("unchecked")
T t = (T) kryo.readClassAndObject(input);
return t;
}
}
}
2. 配置 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.StringRedisSerializer;
@Configuration
public class RedisKryoConfig {
@Bean
public RedisTemplate<String, Object> redisTemplateKryo(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用我们自定义的 KryoRedisSerializer
KryoRedisSerializer<Object> kryoSerializer = new KryoRedisSerializer<>(Object.class);
// Key 仍然使用 String 序列化
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(kryoSerializer);
// Hash 的 key 和 value 也使用 Kryo 序列化
template.setHashKeySerializer(kryoSerializer);
template.setHashValueSerializer(kryoSerializer);
template.afterPropertiesSet();
return template;
}
}
3. 使用
使用方式与 JSON 方案完全一样,只是注入的 RedisTemplate Bean 不同(Spring 会根据方法名自动注入 redisTemplateKryo)。
@Autowired
private RedisTemplate<String, Object> redisTemplateKryo; // 注入 Kryo 配置的模板
public void saveUserWithKryo() {
User user = new User(2L, "Bob", 25);
redisTemplateKryo.opsForValue().set("user:kryo:2", user);
System.out.println("User object has been saved to Redis with Kryo.");
}
public User getUserWithKryo(Long id) {
Object obj = redisTemplateKryo.opsForValue().get("user:kryo:" + id);
if (obj instanceof User) {
return (User) obj;
}
return null;
}
Redis 中的存储结果:
user:kryo:2 这个 key 对应的 value 是一串二进制数据,无法直接阅读。
总结与最佳实践
- 默认选择:对于大多数 Spring Boot 应用,直接使用 JSON 序列化 是最简单、最稳妥的选择,它与 Spring 生态完美集成,调试方便,兼容性好。
- 性能优化:当你的应用对 Redis 读写性能有极致要求,并且数据量很大时,可以考虑切换到 Kryo 或其他高性能二进制序列化库,你需要为此付出一定的开发成本(自定义序列化器)和调试成本(无法直接看 Redis 内容)。
- Key 的序列化:无论 Value 如何序列化,通常都建议将
RedisTemplate的keySerializer设置为StringRedisSerializer,因为 Redis 的命令(如KEYS *,HGETALL myhash)都是基于字符串的,使用字符串作为 key 可以避免很多意想不到的问题。 - 避免使用 Java 原生
Serializable:虽然RedisTemplate默认使用JdkSerializationRedisSerializer(基于 Java 的Serializable),但它有诸多缺点:- 性能差。
- 序列化后体积大。
- 序列化结果不可读。
- 版本兼容性差,修改类结构后容易反序列化失败。 除非有特殊历史原因,否则不推荐使用。
希望这个详细的讲解能帮助你理解和选择合适的 Redis Java 序列化方案!
