redis序列化配置
使用spring操作redis,一般会使用到Spring提供的RedisTemplate模板类,在使用时需要配置存入的key和value的序列化方式。
模板类RedisTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private boolean enableTransactionSupport = false; private boolean exposeConnection = false; private boolean initialized = false; private boolean enableDefaultSerializer = true; private @Nullable RedisSerializer<?> defaultSerializer; private @Nullable ClassLoader classLoader; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null; private RedisSerializer<String> stringSerializer = RedisSerializer.string();
private @Nullable ScriptExecutor<K> scriptExecutor;
private final ValueOperations<K, V> valueOps = new DefaultValueOperations<>(this); private final ListOperations<K, V> listOps = new DefaultListOperations<>(this); private final SetOperations<K, V> setOps = new DefaultSetOperations<>(this); private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations<>(this, ObjectHashMapper.getSharedInstance()); private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations<>(this); private final GeoOperations<K, V> geoOps = new DefaultGeoOperations<>(this); private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations<>(this); private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations<>(this); }
|
这里有5个序列化器:
keySerializer:对存入redis中的key的序列化器
valueSerializer:对存入redis中的value的序列化器
hashKeySerializer:对以hash结构数据存入的hashkey序列化器
hashValueSerializer:对以hash结构数据存入的hashvalue序列化器
stringSerializer:字符串方式序列化器
序列化器具体的实现:
FastJsonRedisSerializer由阿里巴巴的fastjson提供的转为JSON格式进行序列化。
Jackson2JsonRedisSerializer在spring中已经集成,采用Jackson的方式转换为JSON格式进行序列化。
JdkSerializationRedisSerializerjdk默认提供的序列化方式,如果是POJO需要实现Serializable接口。
StringRedisSerializer字符方式序列化,如果是POJO,相当于调用了toString进行序列化。
默认情况下使用的是jdk方式进行键和值的序列化,如果需要修改默认方式,在接入spring的情况下需要设置这些序列化器之后添加到spring容器中。你可以仅仅修改你想要修改的序列化器,其他的保持默认,模板类提供了一个后置接口。
它会逐个进行判断处理,如果某个序列化器为空,则设置对应的默认序列化器。所以一般在设置完成之后会调用一次这个后置接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Override public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer( classLoader != null ? classLoader : this.getClass().getClassLoader()); }
if (enableDefaultSerializer) {
if (keySerializer == null) { keySerializer = defaultSerializer; defaultUsed = true; } if (valueSerializer == null) { valueSerializer = defaultSerializer; defaultUsed = true; } if (hashKeySerializer == null) { hashKeySerializer = defaultSerializer; defaultUsed = true; } if (hashValueSerializer == null) { hashValueSerializer = defaultSerializer; defaultUsed = true; } } initialized = true; }
|
JdkSerializationRedisSerializer
对RedisTemplate进行配置,设置value的序列化器为jdk提供的序列化器,POJO需要实现Serializable接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Configuration public class RedisConfig {
@Primary @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory);
template.setValueSerializer(new JdkSerializationRedisSerializer());
template.setKeySerializer(new StringRedisSerializer());
return template; } }
|
但是这种方式进行序列化之后,在redis-cli上查看时,默认获取的值会被转换为十六进制,导致在排查问题时可读性较差。
\xAC\xED\x00\x05sr\x00\x1Ecn.com.xiaocainiaoya.vo.Person\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x04L\x00\x03aget\x00\x13Ljava/lang/Integer;L\x00\x06housest\x00\x10Ljava/util/List;L\x00\x08passwordt\x00\x12Ljava/lang/String;L\x00\x08userNameq\x00\x00\x03xpsr\x00\x11java.lang.Integer\x12\xE2\xA0\xA4\xF7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xAC\x95\x1D\x0B\x94\xE0\x8B\x02\x00\x00xp\x00\x00\x00\x0Csr\x00\x13java.util.ArrayListx\x81\xD2\x1D\x99\xC7a\x9D\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x01w\x04\x00\x00\x00\x01sr\x00\x1Dcn.com.xiaocainiaoya.vo.House\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x02L\x00\x07addressq\x00\x00\x03L\x00\x04areat\x00\x16Ljava/math/BigDecimal;xpt\x00\x06\xE5\x8C\x97\xE4\xBA\xACsr\x00\x14java.math.BigDecimalT\xC7\x15W\xF9\x81(O\x03\x00\x02I\x00\x05scaleL\x00\x06intValt\x00\x16Ljava/math/BigInteger;xq\x00\x00\x06\x00\x00\x00\x01sr\x00\x14java.math.BigInteger\x8C\xFC\x9F\x1F\xA9;\xFB\x1D\x03\x00\x06I\x00\x08bitCountI\x00\x09bitLengthI\x00\x13firstNonzeroByteNumI\x00\x0ClowestSetBitI\x00\x06signum[\x00\x09magnitudet\x00\x02[Bxq\x00\x00\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\xFF\xFF\xFF\xFE\x00\x00\x00\x01ur\x00\x02[B\xAC\xF3\x17\xF8\x06\x08T\xE0\x02\x00\x00xp\x00\x00\x00\x02\x03\xE9xxxt\x00\x03123t\x00\x03tom
根据比对调用反序列化时的二进制流,发现这一串乱码中“\xAC”表示的十六进制,“\x05sr”中的“\x05”表示一个十六进制,“sr”需要按位解析为二进制,按这个逻辑进行解析之后,得到的结果就是反序列化字节流,所以根据这个逻辑就可以简单通过一段代码将这串乱码反序列化为原始对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public static void main(String[] args) { String value = "\\xAC\\xED\\x00\\x05t\\x00\\x18Pj/1x/ZLXkvm0q6bNZiOYw==";
String[] hexValues = value.split("\\\\x"); byte[] resultValue = new byte[1000]; int resultIndex = 0;
for (int i = 1; i < hexValues.length; i++) { if(StrUtil.isEmpty(hexValues[i])){ continue; } if(hexValues[i].length() <= 2){ resultValue[resultIndex++] = (byte) Integer.parseInt(hexValues[i], 16); }else{ String str = hexValues[i]; resultValue[resultIndex++] = (byte) Integer.parseInt(str.substring(0, 2), 16); for(int j = 2; j < str.length(); j++){ resultValue[resultIndex++] = (byte) str.charAt(j); } } } Object obj = new JdkSerializationRedisSerializer().deserialize(resultValue); System.out.println(obj); }
|
在排查问题时,可以尝试使用这种方式对乱码串进行解析,得到原始对象。(前提是你的工程存在这个类!!)
StringRedisSerializer
字符序列化器,一般对于存入的key和非POJO都会采用这种方式进行序列化,在redis-cli上也能直观的看到对应的键和值是什么。
在Spring中已经提供了对键和值都采用字符序列化器的方式进行操作的模板类StringRedisTemplate。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() { setKeySerializer(RedisSerializer.string()); setValueSerializer(RedisSerializer.string()); setHashKeySerializer(RedisSerializer.string()); setHashValueSerializer(RedisSerializer.string()); }
static RedisSerializer<String> string() { return StringRedisSerializer.UTF_8; } public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8); }
|
Jackson2JsonRedisSerializer
jackson机制进行序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Configuration public class RedisConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
return template; } }
|
通过jackson方式进行序列化之后的值,如下所示,对于复杂对象会表示它的类型,这种存储方式会导致占用的序列化串长度过长,占用一定的存储空间。
[“cn.com.xiaocainiaoya.vo.Person”,{“userName”:”tom”,”password”:”123”,”age”:12,”houses”:[“java.util.ArrayList”,[[“cn.com.xiaocainiaoya.vo.House”,{“address”:”北京”,”area”:[“java.math.BigDecimal”,100.1]}]]]}]
FastJsonRedisSerializer
阿里巴巴提供的fastjson序列化器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Configuration public class RedisConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); template.setValueSerializer(fastJsonRedisSerializer);
template.setKeySerializer(new StringRedisSerializer());
return template; } }
|
通过这种方式生成的JSON串结果如下所示,对比jackson序列化器生成的结果串,发现复杂对象的所在类不会生成,减少一定的空间占用。
{“age”:12,”houses”:[{“address”:”北京”,”area”:100.1}],”password”:”123”,”userName”:”tom”}