前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis Serializer的坑小记

redis Serializer的坑小记

作者头像
山行AI
发布2020-06-29 15:52:40
1.2K0
发布2020-06-29 15:52:40
举报
文章被收录于专栏:山行AI山行AI

最近入职了新公司,负责实时流平台、特征系统和推荐系统的开发工作。其中特征系统通过流平台将特征指标实时处理供推荐系统使用。其中有一处关于用户是否是当天新注册用户的判断,第一反应当然是bitmap,由于我们当前在用的主要缓存为redis,于是最先采取的选型自然是从redis开始的。

问题

写入的部分位于流平台中,使用的client api为Jedis,使用方式如下:

代码语言:javascript
复制
Jedis jedis = pool.getJedis();
jedis.setbit("new_user_id",9999,true);

然后直接在redis-cli中尝试获取如下图:

可以看到返回值为1,也就是可以获取到。在java程序中通过jedis来获取结果如下:

代码语言:javascript
复制
  1. boolean res = jedis.getbit("new_user_id",9999);

返回的结果也为true,貎似没有什么问题。我们继续往下看。

在特征系统中使用的是redisTemplate来操作redis的,于是尝试使用它的getBit相关api来获取代码如下:

  1. Boolean first = this.redisTemplate.opsForValue().getBit("new_user_id", 9999); Boolean second = this.redisTemplate.opsForValue().getBit("new_user_id".getBytes(), 9999);

结果:first为false,second也为false。起初以为是连接的redis实例不同,或者是使用的database不同导致的,仔细检查下来发现连的都是同一台redis的同一个库。反复运行多遍,两者仍然都返回false。

分析与解决

出现这种问题免不得想要问一句,老哥你慌不慌?其实也没什么好惊讶的,必定是哪里操作不当引起的,当程序员这么多年,什么大风大浪没见过,早就已经波澜不惊了,无非是花点时间而已,问题肯定是能够得到解决的。

话不多说,直接上分析。

redis.clients.jedis.Jedis#getbit

代码如下:

可以看到getbit方法的入参是String类型的key,它里面调用了jedis包中Client的gitbit方法。我们来分析下redis.clients.jedis.Client#getbit方法:

在这个方法内部调用了redis.clients.jedis.BinaryClient#getbit方法,该方法的代码如下:

可以看到,该方法的入参key是一个byte[],它是通过SafeEncoder的encode方法将String类型的key转化成byte[]的,我们来看下SafeEncoder的encode方法:

这里其实也没有什么额外的操作,只是通过String的getBytes方法将字符串转化成对应字符类型的字节数组。

我们接着看下对offset的操作,是通过调用redis.clients.jedis.Protocol#toByteArray(long)方法来操作long类型的offset的,代码如下:

代码语言:javascript
复制
public static final byte[] toByteArray(final long value) {
    return SafeEncoder.encode(String.valueOf(value));
}

这个方法中最后是先将value转换成String类型,然后通过SafeEncoder.encode方法生成字节数组的。

关于jedis中的操作就分析到这里了。

redisTemplate

一般在使用redisTemplate时我们常规操作如下:

  1. @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer());

这段配置没有别的目的,只是对redisTemplate设置不同方式的Serializer,以便更好地进行序列化操作。

redisTemplate中使用gitBit方法的api如下:

代码语言:javascript
复制
  1. Boolean four = this.redisTemplate.opsForValue().getBit("new_user_id".getBytes(), 9999);

在上面方法中调用的org.springframework.data.redis.core.DefaultValueOperations#getBit方法的代码如下:

  1. @Override public Boolean getBit(K key, long offset) { byte[] rawKey = rawKey(key); return execute(connection -> connection.getBit(rawKey, offset), true); }

可以看到这里将key转换成字节数组的操作是在rawKey方法中进行的,我们来看下AbstractOperations中的rawKey方法:

这个方法的入参是Object,而且在这里是不是看到了很熟悉的东西,没错就是keySerializer,可以看到,最终返回的key的字节数组是经过客户端keySerializer处理过的。而不是像上面的底层jedis api那样只是简单地str.getBytes。很明显,配置的redisTemplate的序列化器影响到了整个操作,导致了获取结果的异常。

解决方案

当然了,如果一定要在redisTemplate中既使用配置的Serializer又能正常使用getBit方法也不是没有办法,这里是我能想到的最快的解决方案,那就是在特征系统中直接使用下面的方式来使用getBit方法:

代码语言:javascript
复制
RedisCallback<Boolean> callback = (connection) -> {
    return connection.getBit("new_user_id".getBytes(),9999);
};
Object execute = redisTemplate.execute(callback);
System.out.println(execute);

返回为true,问题解决。当然,也会有其他的解决方案,欢迎有更好方法的同学联系我。

后记:最近没有太多时间分享源码性质的干货,可能在未来一段时间会持续推出flink系列的源码解析。暂时只能简单地分享一些像本文这种不涉及公司代码又值得一记的踩坑记录啦!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
  • 分析与解决
    • redis.clients.jedis.Jedis#getbit
      • redisTemplate
      • 解决方案
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档