redis序列化
测试环境的接口没有返回值,经过查询发现是直接从redis中读取的数据,由于不清楚是通过什么方式写入的redis(没仔细想办法找到写入的代码),所以查询了开发环境的redis key,发现有数据,就想直接将开发环境的key 和value手动写入到测试环境。手动复制的过程中发现value的中文都是些编码过的16进制的符号,然后在复制到测试环境后,测试环境接口报错,解析对象异常。估计是序列化引起的问题,虽然value只能看到值,但是我记得好像有些会存@class之类的,暂时不清楚,只有回头查找程序是在哪里写入redis的,直接对该key idea中全局查找,就找到了写入的接口,接口逻辑,查询数据库,然后写入redis,手动在数据库添加测试数据,然后调用该接口,成功写入redis。
总结:
-
1.遇到问题,不能总想着偷懒的方式解决,多思考一步,或许很容易就能找到更合适的方法。
-
2.先将直观的方式和可行的方案都罗列出来,评估选定一个合适的方案。多思考,思考清楚了在动手,而不是动手后发现不行,再思考一下,发现有更简单的方式。
为什么使用redis-cli查询value,中文是16进制数据
原因解析
1.Redis存储的本质
Redis存储的是二进制安全的字节序列(byte[]),不关心内容编码(UTF-8,GBC等)
-
程序写入时,序列化器(如StringRedisSerializer)会将字符串转为UTF-8编码的字节数组存入redis.
-
中文字符在UTF-8中通常占3个字节(如“中”->\xE4\xB8\xAD)
2.redis-cli的默认行为
-
默认以ASCLL安全模式显示数据:非ASCII字符(如中文)会被转义为\xXX形式(每个字节的十六进制值)
-
这是为了保护终端显示安全,避免二进制数据(如图片)直接输出到终端导致乱码或控制符误触发。
3.应用程序的行为
-
程序读取数据后,会用相同的序列化器(如UTF-8解码)将字节数组还原为字符串,因此中文正常显示。
若已进入redis-cli交互界面,则在查询命令前加 RAW
#在get命令前加 RAW 127.0.0.1:6379> SET your_key "你好" # 写入测试数据 127.0.0.1:6379> GET your_key # 默认显示转义字符:"\xe4\xbd\xa0\xe5\xa5\xbd" 127.0.0.1:6379> RAW GET your_key # 使用 RAW 模式输出 你好
为什么手动复制redis的value到测试环境,会无法解析
根本原因:手动复制破坏了序列化结构
1.序列化器的隐式元信息丢失
当程序使用带类型签名的序列化器(如SpringBoot默认的JdkSerializationRedisSerializer
或 GenericJackson2JsonRedisSerializer
)时,数据中会保护额外的类型标识:
// GenericJackson2JsonRedisSerializer 写入的数据实际结构 {"@class": "com.example.User", // 类型签名(Java特有)"name": "张三","age": 30 }
-
手动复制行为
通过redis-cli复制的是人类可读的字符串表示,而非原始二进制数据->类型签名@class 等元信息被破坏或丢失
序列化的必要性:
-
数据格式统一性 Redis底层存储基于字节流,复杂对象(如Java/Python对象)无法直接存储。序列化将对象转为二进制或字符串(如JSON),确保数据可被Redis处理。
-
跨语言兼容性 微服务架构中,不同语言服务(如Java写入、Python读取)需通用格式(如JSON/MessagePack)实现数据交换
-
存储效率与安全 序列化可压缩数据体积(如MessagePack比JSON小30%),同时避免明文存储敏感信息,并通过类型校验防止注入攻击
二、常见序列化方式及对比
序列化器 | 适用场景 | 优点 | 缺点 | 示例数据类型 |
---|---|---|---|---|
StringRedisSerializer | 简单字符串、数值 | 性能最优,内存占用低,直接存储字符串 | 仅支持基础类型,不支持对象 | 用户Token、计数器 |
GenericJackson2JsonRedisSerializer | 复杂对象(嵌套结构、集合) | 可读性好(JSON格式),跨语言,支持复杂类型 | 性能较低,内存占用较高,需完整get/set方法 | 用户信息、订单数据 |
JdkSerializationRedisSerializer | Java对象 | 支持任意Java对象(需实现Serializable) | 二进制不可读,体积大(JSON的5倍),仅限Java | 特定Java对象 |
自定义序列化 | 特殊需求(如Protobuf) | 灵活性高,可优化性能/体积 |
关键对比:
-
性能
StringRedisSerializer
>GenericJackson2Json
>JDK序列化
-
存储体积:JDK二进制 > JSON > MessagePack/Protobuf
-
调试友好性:JSON > 字符串 > 二进制
选型建议与最佳实践
1.高性能场景(如缓存热点数据)
-
优先使用
StringRedisSerializer
(Key/Value均适用) -
复杂对象手动转为JSON存储,避免全局使用JSON序列化器
// Java示例:手动序列化对象 ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(user); redisTemplate.opsForValue().set("user:1001", json); // 使用String序列化器
2.跨语言或复杂对象场景
-
使用
GenericJackson2JsonRedisSerializer
,但需确保对象有无参构造器和完整get/set方法 -
避免
Jackson2JsonRedisSerializer
(需指定Class类型,灵活性差)
3.规避JDK默认序列化
-
默认的
JdkSerializationRedisSerializer
因性能差、体积大、语言绑定强,不推荐生产使用
4.进阶优化
-
压缩大对象:对超过1KB的数据启用GZIP压缩
-
版本控制:Key中嵌入版本号(如
user:v2:1001
),应对字段变更 -
空值缓存:防止缓存穿透