RedisAtomicInteger是springdata中在redis的基础上实现的原子计数器,在以下maven依赖包中:
<groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId>
当使用RedisAtomicInteger(String redisCounter, RedisOperations template,...)函数构建实例的情况下,在使用INCR或者DECR时,会遇到ERR value is not an integer or out of range错误,显示操作的数据不是一个整数或者超出范围。参考redis命令说明我们知道incr对操作值的要求:
这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
从redis实例中查看该key的value,会发现结果类似这样:"\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x01",原因在于value使用的序列化方式是JdkSerializationRedisSerializer,这和INCR命令对结果的要求是违背的。
该使用哪种序列化方式把value放进去呢?按照INCR命令对结果的要求,最容易想到StringRedisSerializer,但经过尝试这也行不通,会报java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。
如果看过RedisAtomicInteger的源码,在private RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, Integer initialValue)中会发现方法内部创建了RedisTemplate实例,对value设置的序列化方式是GenericToStringSerializer。该序列化内部使用spring core包下的org.springframework.core.convert.support.DefaultConversionService作为默认的对象和字符串的转换方式,主要为了满足大多数环境的要求。
至此,我们终于知道了错误的根本原因,构造RedisAtomicInteger时传入的redisTemplate是有问题的,value的默认序列化方式不满足RedisAtomicInteger的需要。那么问题也迎刃而解,将GenericToStringSerializer作为redisTemplate的value序列化方式。
这样虽然解决了问题,但很麻烦,很可能为了RedisAtomicInteger的要求需要再创建一个redisTemplate,简直不能忍受。再看RedisAtomicInteger的源码,发现构造函数除了可以用redisTemplate,还可以用RedisConnectionFactory,尝试之后,完美解决。