这里写自定义目录标题
RedisTemplate
本人很菜,排查的这个问题比较初级。轻喷。
环境: Spring, Spring boot, RedistTemplate
问题描述
与同事商定了一个功能实现方法的时候,他那边采用离线计算,将计算结果放入Redis的Hash结构中,hash的key为用户id,value为计算的结果。我这边需要用的时候取出来。很简单的一套实现。
取的时候发现明明有值的用户,使用RedisTemplate取出来却是null。反复对比了配置文件确定redis的地址没有问题。
排查过程
使用redis-cli 命令行客户端去取数据一切正常:
然而使用RedisTemplate却发现相同的id取不出任何数据。
redisTemplate.opsForHash().get(“rscsys.cf.recall", "100086");
非常诡异。
首相想到的是抓一下包看看执行的redis查询语句是否一样
在linux下使用tcpdump -xxXXvvnn port 6372抓包:
redis-cli客户端:
hget recsys.cf.recall 100086
0x0000: 001f 7a00 0001 342e b68f faa9 0800 4500 ..z...4.......E.
0x0010: 0065 1009 4000 4006 0708 ac10 b5a8 c0a8 .e..@.@.........
0x0020: 0121 b186 18eb 9adf 45b9 3ff5 3701 8018 .!......E.?.7...
0x0030: 12e6 76e8 0000 0101 080a 1ebb 7016 ff72 ..v.........p..r
0x0040: 7543 2a33 0d0a 2434 0d0a 6867 6574 0d0a uC*3..$4..hget..
0x0050: 2431 360d 0a72 6563 7379 732e 6366 2e72 $16..recsys.cf.r
0x0060: 6563 616c 6c0d 0a24 360d 0a31 3030 3038 ecall..$6..10008
0x0070: 360d 0a 6..
RedisTemplate:
redisTemplate.opsForHash().get(“rscsys.cf.recall", "100086");
0x0000: 001f 7a00 0001 342e b68f faa9 0800 4500 ..z...4.......E.
0x0010: 006d f37d 4000 4006 238b ac10 b5a8 c0a8 .m.}@.@.#.......
0x0020: 0121 b550 18eb 4a58 9535 b29e 884a 8018 .!.P..JX.5...J..
0x0030: 01f6 cbe9 0000 0101 080a 1eba f7ab ff71 ...............q
0x0040: fe04 2a33 0d0a 2434 0d0a 4847 4554 0d0a ..*3..$4..HGET..
0x0050: 2431 360d 0a72 6563 7376 732e 6366 2e72 $16..recsvs.cf.r
0x0060: 6563 616c 6c0d 0a24 3133 0d0a aced 0005 ecall..$13......
0x0070: 7400 0631 3030 3038 360d 0a t..100086..
发现都是使用hget命令,redis的key也都是recsys.cf.recall
但hashKey部分就不一样了。一处有$6,一处有$13
既然知道是hashKey导致的问题。进一步猜想可能性。
会不是RedisTemplate多做了一次字符串序列化呢?比如将 “100086"序列化成了”“100086"”?
使用redis-cli客户端访问
hget recsys.cf.recall \"100086\"
0x0000: 001f 7a00 0001 342e b68f faa9 0800 4500 ..z...4.......E.
0x0010: 0067 100f 4000 4006 0700 ac10 b5a8 c0a8 .g..@.@.........
0x0020: 0121 b186 18eb 9adf 45ea 3ff5 3991 8018 .!......E.?.9...
0x0030: 12f0 2912 0000 0101 080a 1ebc 7901 ff73 ..).........y..s
0x0040: 9040 2a33 0d0a 2434 0d0a 6867 6574 0d0a .@*3..$4..hget..
0x0050: 2431 360d 0a72 6563 7379 732e 6366 2e72 $16..recsys.cf.r
0x0060: 6563 616c 6c0d 0a24 380d 0a22 3130 3030 ecall..$8.."1000
0x0070: 3836 220d 0a 86"..
发现了$ 6变成了$ 8!
原来$ 符后面的数字代表key的长度。
那么知道了redisTemplate的hashKey长度从6个字符变成了13个字符。
验证一下猜想
使用RedisTemplate向随便一个key里塞一个hash数据。
redisTemplate.opsForHash().put(“test", "100086","test" );
使用redis-cli客户端读一下看看:
HKEYS test
返回结果为
"\xac\xed\x00\x05t\x00\x06 100086"
原来RedisTemplate往hashKey里加了不少东西。
后续研究
看了一下SpringBoot自动注入RedisTemplate部分的源码:
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
@Configuration
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisTemplate.class,
Flux.class })
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisReactiveAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "reactiveRedisTemplate")
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
public ReactiveRedisTemplate<Object, Object> reactiveRedisTemplate(
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory,
ResourceLoader resourceLoader) {
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(
resourceLoader.getClassLoader());
RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext
.newSerializationContext().key(jdkSerializer).value(jdkSerializer)
.hashKey(jdkSerializer).hashValue(jdkSerializer).build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory,
serializationContext);
}
}
发现使用的默认的序列化组件是JdkSerializationRedisSerializer。是jdk提供的序列化的封装。
对比一下Spring框架中的RedisSerializer.string()序列化组件:
public static void main(String[] args) {
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
System.out.println(jdkSerializer.getClass());
byte[] c = jdkSerializer.serialize("100086");
System.out.println(c.length);
System.out.println(new String(c));
RedisSerializer serializer = RedisSerializer.string();
System.out.println(serializer.getClass());
byte[] a = serializer.serialize("100086");
System.out.println(a.length);
System.out.println(new String(a));
}
运行结果为:
class org.springframework.data.redis.serializer.JdkSerializationRedisSerializer
13
�� t 100086
class org.springframework.data.redis.serializer.StringRedisSerializer
6
100086
手动为其设置序列化组件后一切执行正常!
public RedisTemplate<String, String> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}