前言
上一篇已经介绍了优雅的操作Redis:
【小家Spring】Spring Boot中使用RedisTemplate优雅的操作Redis,并且解决RedisTemplate泛型注入的问题。本篇着重介绍一下几种常用的序列化方式
最近在做一个项目,由于并发量大,大量使用到了RedisTemplate来操作Redis。但使用过程中,遇到了不少的坑,各种翻看源码来跟踪,也总结出了不少的经验。
因此今天专门做一篇专文来记录这些坑,也具体说说RedisTemplate的各种序列化方式的差异性。希望对大家也能有所帮助,帮助大家解决一些疑惑
1.序列化问题
RedisTemplate在遇到复杂类型的返序列化时,即使加了泛型,获取到的时机类型为LinedHashMap,需要得到结果后再次返序列化,不然会报类型转换异常。
如下:这样处理才是安全的:
在执行序列化的时候,操作的如果是Bean,必须有默认构造器,否则报错
2.redis集群问题(关于集群的这几个问题,后续在专门演示和解释)
如果连接的为Redis集群,则不能用管道的方法,除非改写管道的类
模糊查询的时候需要获取到所有的node信息,依次查询
Spring提供的序列化方式
从源码里看:
我们可以很清晰的看到,Spring为我们提供了6种
不同的序列化方式。
特别说明一下:如果你是在Spring Boot1.5.x
环境下使用,你可能看到是9种实现或者是7种实现,如下图所示
解释:
关于前面两个,并非Spring官方提供,而是由alibaba的FastJson自己实现的。我们看看FastJson的包结构,发现它很友好的提供了一些常用的转化器:
因此此处暂时不做过多描述,后面再说。
另外还有一个JacksonJsonRedisSerializer类,被标记为过期。而这个类在SpringBoot2.0就直接被移除掉了,因此以后的版本不用理会了。
下面主要介绍一下,Spring官方现在还存在的6大序列化器:
Generic单词意思:一般的; 通用的;类的,属性的;
1.OxmSerializer
以xml格式存储(但还是String类型~),解析起来也比较复杂,效率也比较低。因此几乎没有人再使用此方式了
2.JdkSerializationRedisSerializer
从源码里可以看出,这是RestTemplate类默认的序列化方式。若你没有自定义,那就是它了。
@Override public void afterPropertiesSet() { super.afterPropertiesSet(); boolean defaultUsed = false; if (defaultSerializer == null) { defaultSerializer = new JdkSerializationRedisSerializer( classLoader != null ? classLoader : this.getClass().getClassLoader()); } ...
使用JDK自带的序列化方式,有明显的缺点:
首先它要求存储的对象都必须实现java.io.Serializable接口,比较笨重
其次,他存储的为二进制数据,这对开发者是不友好的
再次,因为他存储的为二进制。但是有时候,我们的Redis会在一个项目的多个project中共用,这样如果同一个可以缓存的对象在不同的project中要使用两个不同的key来分别缓存,既麻烦,又浪费。
使用JDK提供的序列化功能。 优点是反序列化时不需要提供(传入)类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。
@Autowired private RedisTemplate redisTemplate; @Test public void contextLoads() { ValueOperations<String, Person> valueOperations = redisTemplate.opsForValue(); valueOperations.set("aaa", new Person("fsx", 24)); Person p = valueOperations.get("aaa"); //Person(name=fsx, age=24) System.out.println(p); }
存储的为二进制,根本开不出来是什么,对开发者调试也很不友好
3.StringRedisSerializer
也是StringRedisTemplate默认的序列化方式,key和value都会采用此方式进行序列化,是被推荐使用的,对开发者友好,轻量级,效率也比较高。
(例子略)
4.GenericToStringSerializer
他需要调用者给传一个对象到字符串互转的Converter(相当于转换为字符串的操作交给转换器去做),个人觉得使用起来其比较麻烦,还不如直接用字符串呢。所以不太推荐使用
后面两种序列化方式是重点
5.Jackson2JsonRedisSerializer
从名字可以看出来,这是把一个对象以Json的形式存储,效率高且对调用者友好
优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。
但缺点也非常致命:那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其在反序列化过程中用到了类型信息(必须根据此类型信息完成反序列化)。
6.GenericJackson2JsonRedisSerializer
基本和上面的Jackson2JsonRedisSerializer功能差不多,使用方式也差不多,**但是是推荐使用的**
需要注意:(使用区别)
@Test public void contextLoads() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); //ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); //此处泛型 因为编译器无法校验 所以如果value序列化方式是字符串 下面就会报错了 ValueOperations<String, Person> valueOperations = redisTemplate.opsForValue(); valueOperations.set("key", new Person("fsx", 24)); //java.lang.ClassCastException: com.fsx.run2.bean.Person cannot be cast to java.lang.String Person value = valueOperations.get("key"); System.out.println(value); }
如上,假如我value的序列化方式设置为String序列化器。但是set值的时候放对象了。这个时候就直接报错了,并不会自动调用toString()方法,此处一定要注意。还需要特别是初始化RestTemplate的时候,value的序列化方式禁止使用有类型偏向的StringRedisSerializer。若有需要,你直接使用StringRedisTemplate操作即可