栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Redis的incr命令引发的反序列化异常和ERR value is not an integer or out of range异常

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redis的incr命令引发的反序列化异常和ERR value is not an integer or out of range异常

Redis的incr命令引发的反序列化异常和ERR value is not an integer or out of range异常

最近在开发中,使用Redis来实现自增ID。为什么使用Redis?这是一个高并发访问,需要考虑操作冲突导致数据不一致的问题。而Redis是内存型存储,相比关系型数据库,操作更快,避免了频繁的文件写操作。更重要的是,Redis中有个INCR和INCRBY命令,都可以实现值递增的原子性操作,方便了解决了高并发时的冲突问题。

redis命令说明

incr:

对存储在指定key的数值执行原子的加1操作。

如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0。

如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。

序列化异常 摸索

当我执行如下命令时:

public void contextLoads1() {
        RedisTemplate redisTemplate = RedisUtil.getRedis();
        ValueOperations valueOperations = redisTemplate.opsForValue();
  //键为 key1
  Long key1 = valueOperations.increment("key1");
        log.info("执行increment("key1")返回值是:{}",key1);
    }

执行结果如下:成功返回了递增后的结果

我当时要在别处查看这个key1键对应的值是多少,然后就用以下命令查看,问题来了:

居然返回的是null,我就纳闷了,明明前面执行的increment命令,为啥返回为null,我就有如下几个猜想:

猜想一:是不是increment命令执行失败了,服务器上不存在`key1

于是乎,我就打开服务器查看,发现存在啊!!!

猜想二:代码中get命令没有获取到值

经过各种debug,最后发现是前人埋下的一个坑,问题出现在如下代码里面:

@Bean
public RedisTemplate redisTemplate(RedisTemplate template) {
    template.setKeySerializer(new StringRedisSerializer());
   //ValueSerializer使用的是JdkSerializationRedisSerializer,并且默认情况下就是JdkSerializationRedisSerializer,我也不知道前人是什么个意思,再重新设置同样的序列化
    template.setValueSerializer(new JdkSerializationRedisSerializer() {
        @Override
        public Object deserialize(byte[] bytes) {
            try {
              //最夸张的是直接调用super.deserialize方法 !!!
                return super.deserialize(bytes);
            } catch (Exception e) {
              //最最最夸张的是直接返回null !!!
                return null;
            }
        }
    });
    return template;
}

问题原因:在反序列化异常的时候,直接返回null,把deserialize方法抛出的异常捕获,直接返回,也不打印错误日志,这不是误导我嘛!!!有错误就抛啊,我还在那里以为increment自增失败了呢。。。

解决

可是为什么出现了序列化异常呢???带着大大的疑问继续往下走:

我们先去服务器上通过命令查看key1对应值的编码类型:

可以看到值是一个int类型,这时候我就想到了前面"大神"配置的ValueSerializer值是JdkSerializationRedisSerializer,我们先改下代码,让异常打印出来:

@Bean
public RedisTemplate redisTemplate(RedisTemplate template) {
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new JdkSerializationRedisSerializer() {
        @Override
        public Object deserialize(byte[] bytes) {
            try {
                return super.deserialize(bytes);
            } catch (Exception e) {
                log.error("deserialize 反序列化异常:{}",e.getMessage());
                return "deserialize 反序列化异常:{" + e.getMessage() + "}";
                //return null;
            }
        }
    });
    return template;
}

再次执行获取的方法

@Test
public void contextLoads3() {
    RedisTemplate redisTemplate = RedisUtil.getRedis();
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Object value1 = valueOperations.get("key1");
    log.info("键为key1的返回值是:{}",value1);
}

出现如下错误:

2022-03-28 15:42:28.255 -ERROR 6217 [] {magenta} --- [           main] ybplan.config.RedisConfig                : deserialize 反序列化异常:Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.EOFException
--2022-03-28 15:42:28.255 - INFO 6217 [] {magenta} --- [           main] ybplan.SxapiApplicationTest              : 键为key1的返回值是:deserialize 反序列化异常:{Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.EOFException}
-

关键在于如下Is the byte array a result of corresponding serialization for DefaultDeserializer?,

byte array???,然后我就去官网查了下资料发现在JdkSerializationRedisSerializer有如下两个方法deserialize serialize

官网解释如下:

存储的时候将数据存储为binary data,获取的时候通过反序列化将binary data转为Object,可是我前面是直接通过incrment设置的值的,我们在看下`incrment的源码,

@Override
public Long increment(K key) {

   byte[] rawKey = rawKey(key);
   return execute(connection -> connection.incr(rawKey), true);
}

从源码中发现,当我们直接调用increment方法,递增量的初始值是由Redis生成的,根本没有走JdkSerializationRedisSerializer的序列化策略,又何来序列化,所以说问题原因找到了。为了证明这一点我们做如下测试:

@Test
public void contextLoads3() {
    RedisTemplate redisTemplate = RedisUtil.getRedis();
    ValueOperations valueOperations = redisTemplate.opsForValue();
    //用JdkSerializationRedisSerializer序列化去设置值和获取值
    valueOperations.set("key2",1);
    Object value2 = valueOperations.get("key2");
    log.info("键为key2的返回值是:{}",value2);
}

得到的结果如下:

结论

所以问题原因找到了,问题就是在序列化为JdkSerializationRedisSerializer的情况下先使用incrment,再去使用get去获取会出现

Cannot deserialize异常,因为incrment设置的值本来就不是一个binary data,所以在获取的时候失败了。

ERR value is not an integer or out of range 摸索

在键为key2的值上执行increment

@Test
public void contextLoads3() {
    RedisTemplate redisTemplate = RedisUtil.getRedis();
    ValueOperations valueOperations = redisTemplate.opsForValue();
    valueOperations.set("key2",1);
    log.info("在使用set设置键为key2的键上执行increment,获取到的值是:{}",valueOperations.increment("key2"));
   
}

出现如下异常:

org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR value is not an integer or out of range

根据前面Redis小节下的incr命令说可知,key2对应的值不是字符串类型或者存储的字符串类型不能表示为一个整数

我们通过服务器来验证一下:

我们先通过get获取,发现报错

查看下编码类型

通过前面可知key1的编码类型是int,同样的值是1,因为序列化的原因,导致的编码类型不同,并且服务器上并没有反序列化,就直接获取key2的值,所以报错了。

由object encoding key2返回的raw可知(具体的raw的含义会在下文给出解释),当我们对key2执行incrment的时候,因为值并不是一个字符串类型或者存储的字符串类型不能表示为一个整数,所以自增会出现ERR value is not an integer or out of range错误。

拓展

redis的字符串对象和字符串的编码类型

推荐书籍

《Redis设计与实现》黄建宏

思维导图

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/785756.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号