最近入职了新公司,负责实时流平台、特征系统和推荐系统的开发工作。其中特征系统通过流平台将特征指标实时处理供推荐系统使用。其中有一处关于用户是否是当天新注册用户的判断,第一反应当然是bitmap,由于我们当前在用的主要缓存为redis,于是最先采取的选型自然是从redis开始的。
写入的部分位于流平台中,使用的client api为Jedis,使用方式如下:
Jedis jedis = pool.getJedis();
jedis.setbit("new_user_id",9999,true);
然后直接在redis-cli中尝试获取如下图:
可以看到返回值为1,也就是可以获取到。在java程序中通过jedis来获取结果如下:
返回的结果也为true,貎似没有什么问题。我们继续往下看。
在特征系统中使用的是redisTemplate来操作redis的,于是尝试使用它的getBit相关api来获取代码如下:
结果:first为false,second也为false。起初以为是连接的redis实例不同,或者是使用的database不同导致的,仔细检查下来发现连的都是同一台redis的同一个库。反复运行多遍,两者仍然都返回false。
出现这种问题免不得想要问一句,老哥你慌不慌?其实也没什么好惊讶的,必定是哪里操作不当引起的,当程序员这么多年,什么大风大浪没见过,早就已经波澜不惊了,无非是花点时间而已,问题肯定是能够得到解决的。
话不多说,直接上分析。
代码如下:
可以看到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的,代码如下:
public static final byte[] toByteArray(final long value) {
return SafeEncoder.encode(String.valueOf(value));
}
这个方法中最后是先将value转换成String类型,然后通过SafeEncoder.encode方法生成字节数组的。
关于jedis中的操作就分析到这里了。
一般在使用redisTemplate时我们常规操作如下:
这段配置没有别的目的,只是对redisTemplate设置不同方式的Serializer,以便更好地进行序列化操作。
redisTemplate中使用gitBit方法的api如下:
在上面方法中调用的org.springframework.data.redis.core.DefaultValueOperations#getBit方法的代码如下:
可以看到这里将key转换成字节数组的操作是在rawKey方法中进行的,我们来看下AbstractOperations中的rawKey方法:
这个方法的入参是Object,而且在这里是不是看到了很熟悉的东西,没错就是keySerializer,可以看到,最终返回的key的字节数组是经过客户端keySerializer处理过的。而不是像上面的底层jedis api那样只是简单地str.getBytes。很明显,配置的redisTemplate的序列化器影响到了整个操作,导致了获取结果的异常。
当然了,如果一定要在redisTemplate中既使用配置的Serializer又能正常使用getBit方法也不是没有办法,这里是我能想到的最快的解决方案,那就是在特征系统中直接使用下面的方式来使用getBit方法:
RedisCallback<Boolean> callback = (connection) -> {
return connection.getBit("new_user_id".getBytes(),9999);
};
Object execute = redisTemplate.execute(callback);
System.out.println(execute);
返回为true,问题解决。当然,也会有其他的解决方案,欢迎有更好方法的同学联系我。
后记:最近没有太多时间分享源码性质的干货,可能在未来一段时间会持续推出flink系列的源码解析。暂时只能简单地分享一些像本文这种不涉及公司代码又值得一记的踩坑记录啦!