这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
前言
也是今天遇到的一个问题, 新搭建的项目使用SpringRedisTemplate遇到的一些问题记录一下, 也把相关的知识总结归纳一下.
问题描述
两个问题, 1. 自测的时候发现调用stringRedisTemplate.opsForValue().get(key) 耗时非常长,一直卡住不动. 2. 序列化的问题
问题原因其实是一个比较低级的问题, 也是对一些框架的原理理解的有些偏差. 业务场景我们利用布隆过滤器去过滤一些URL, 但是布隆过滤器需要定期的过期(因为数据量比较大而且一直递增). 我们定义的是每个月一个布隆过滤器, 每个月第一笔请求的时候需要判断布隆过滤器是否存在. key: 设置为bloom:filter:202108 value: bloom:filter:202108 然后把value作为布隆过滤器的name, 在去判断布隆过滤器是否存在.
@Test
public void test() {
String key = "bloom:filter:202108";
stringRedisTemplate.opsForValue().set(key, key, 100l, TimeUnit.SECONDS);
String res = stringRedisTemplate.opsForValue().get(key);
RBloomFilter<String> filter = redisson.getBloomFilter(key);
filter.tryInit(100000000, 0.001);
filter.add("iyixh");
System.out.println(filter.contains("123as"));
System.out.println(res);
}
复制代码
执行后发现, 有两个key, 一个是bloom:filter:202108,一个是bloom过滤器的配置{bloom:filter:202108}:config,
在看下每个key对应的大小,bloom:filter:202108 这个key的redis值value似乎变成了bloomFilter的大小了.
DEBUG看下源码, 可以看到第一步只是构造函数, 有两个name,一个是过滤器的名称bloom:filter:202108, 一个是配置{bloom:filter:202108}:config
那么在看下初始化的方法:tryInit, 可以看到设置初始化值的时候其实是报错的, bloom过滤器我们知道底层的数据结构是bitmap数组,初始化的时候设置大小.168.23M, 在到底层就是C的代码了.
所以我们在设置缓存key的时候不能和布隆过滤器重名, 防止底层数据结构发生变更.
说说 redisTemplate 和 stringRedisTemplate
为什么会需要两个Template呢?有什么区别呢?
2.1. 比较
stringRedisTemplate
- 主要用来存储字符串,StringRedisSerializer的泛型指定的是String。当存入对象时,会报错 :can not cast into String
- 可见性强,更易维护。如果过都是字符串存储可考虑用StringRedisTemplate。
redisTemplate
- 可以用来存储对象,但是要实现Serializable接口。
- 以二进制数组方式存储,内容没有可读性。
2.2 redisTemplate序列化
spring-data-redis提供如下几种选择:
- GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
- Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
- JacksonJsonRedisSerializer: 序列化object对象为json字符串
- JdkSerializationRedisSerializer: 序列化java对象
- StringRedisSerializer: 简单的字符串序列化
2.2.1 测试
@Test
public void testSerial(){
User user = new User();
user.setId(1001L);
user.setName("测试");
user.setAge(18);
List<Object> list = new ArrayList<>();
for(int i=0;i<200;i++){
list.add(user);
}
JdkSerializationRedisSerializer jdkSerialize = new JdkSerializationRedisSerializer();
GenericJackson2JsonRedisSerializer g = new GenericJackson2JsonRedisSerializer();
Jackson2JsonRedisSerializer jackson2 = new Jackson2JsonRedisSerializer(List.class);
Long j_s_start = System.currentTimeMillis();
byte[] bytesJ = jdkSerialize.serialize(list);
System.out.println("JdkSerializationRedisSerializer序列化时间:"+(System.currentTimeMillis()-j_s_start) + "ms,序列化后的长度:" + bytesJ.length);
Long j_d_start = System.currentTimeMillis();
jdkSerialize.deserialize(bytesJ);
System.out.println("JdkSerializationRedisSerializer反序列化时间:"+(System.currentTimeMillis()-j_d_start));
Long g_s_start = System.currentTimeMillis();
byte[] bytesG = g.serialize(list);
System.out.println("GenericJackson2JsonRedisSerializer序列化时间:"+(System.currentTimeMillis()-g_s_start) + "ms,序列化后的长度:" + bytesG.length);
Long g_d_start = System.currentTimeMillis();
g.deserialize(bytesG);
System.out.println("GenericJackson2JsonRedisSerializer反序列化时间:"+(System.currentTimeMillis()-g_d_start));
Long j2_s_start = System.currentTimeMillis();
byte[] bytesJ2 = jackson2.serialize(list);
System.out.println("Jackson2JsonRedisSerializer序列化时间:"+(System.currentTimeMillis()-j2_s_start) + "ms,序列化后的长度:" + bytesJ2.length);
Long j2_d_start = System.currentTimeMillis();
jackson2.deserialize(bytesJ2);
System.out.println("Jackson2JsonRedisSerializer反序列化时间:"+(System.currentTimeMillis()-j2_d_start));
}
复制代码
2.2.2 总结
- JdkSerializationRedisSerializer序列化后长度最小,Jackson2JsonRedisSerializer效率最高。
- 如果综合考虑效率和可读性,牺牲部分空间,推荐key使用StringRedisSerializer,保持的key简明易读;value可以使用Jackson2JsonRedisSerializer
- 如果空间比较敏感,效率要求不高,推荐key使用StringRedisSerializer,保持的key简明易读;value可以使用JdkSerializationRedisSerializer
最后
架构的过程其实就是不断比较取舍的过程, 不同的应用场景有不同的技术选型.