0%

redis序列化配置

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:字符串方式序列化器

序列化器具体的实现:

  1. FastJsonRedisSerializer由阿里巴巴的fastjson提供的转为JSON格式进行序列化。
  2. Jackson2JsonRedisSerializerspring中已经集成,采用Jackson的方式转换为JSON格式进行序列化。
  3. JdkSerializationRedisSerializerjdk默认提供的序列化方式,如果是POJO需要实现Serializable接口。
  4. 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 {

/**
* 以下两种redisTemplate自由根据场景选择
*
* @param connectionFactory
* @return
*/
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

// 使用jdk序列化方式进行序列化
template.setValueSerializer(new JdkSerializationRedisSerializer());

// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// template.afterPropertiesSet();
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;
}
// AC/ED/00等表示为单个十六进制
if(hexValues[i].length() <= 2){
resultValue[resultIndex++] = (byte) Integer.parseInt(hexValues[i], 16);
}else{
// 05t等认为前两位为十六进制,后面按字符逐个转换为字节
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;
}
// 设置为编码为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 {

/**
* 以下两种redisTemplate自由根据场景选择
*
* @param connectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
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);

// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// template.afterPropertiesSet();
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 {

/**
* 以下两种redisTemplate自由根据场景选择
*
* @param connectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
// 全局开启AutoType,不建议使用
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 建议使用这种方式,小范围指定白名单
//ParserConfig.getGlobalInstance().addAccept("cn.com.xiaocainiao");
template.setValueSerializer(fastJsonRedisSerializer);

// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// template.afterPropertiesSet();
return template;
}
}

通过这种方式生成的JSON串结果如下所示,对比jackson序列化器生成的结果串,发现复杂对象的所在类不会生成,减少一定的空间占用。

{“age”:12,”houses”:[{“address”:”北京”,”area”:100.1}],”password”:”123”,”userName”:”tom”}

-------------本文结束感谢您的阅读-------------