Redisincr命令引发的反序列化异常和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();//键为 key1Long 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() {@Overridepublic 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() {@Overridepublic 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的源码,

/** (non-Javadoc)* @see org.springframework.data.redis.core.ValueOperations#increment(java.lang.Object)*/
@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设计与实现》黄建宏

思维导图

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

  1. Redis INCR命令

    路人甲:嘿,兄弟,知不知道redis的incr命令怎么用? 路人丙:啥?你这都不知道,不就是将key值增1嘛? 路人甲:可以一直一直一直加吗? 路人丙:-- 下图是Redis命令参考网站给出的incr ...

  2. Redis基础常用命令入门

    目录 Redis基础命令 一.字符串类型 ▪️ 赋值 SET : ▪️ 获取 GET: ▪️ 递增(减) 1 INC.DECR: ▪️ 递增(减)指定整数 INCRBY .DECRBY: ▪️ 递增( ...

  3. 一次redis连接配置修改引发的redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.异常

    一次redis连接配置修改引发的redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.异常 ...

  4. Redis爬坑记(一):incr命令和expire命令的误区

    关注公众号 要实现的功能:限制用户的每分钟的访问次数一个有严重bug的代码:每次访问来了,就执行代码块二,当第一次访问,就走else语句,设置当前用户的次数为1,且设置该key的有效期是一分钟. 在一 ...

  5. php redis获取incr的值,Redis Incr命令

    Redis Incr命令 Redis Incr 命令将 key 中储存的数字值增一. 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作. 如果值包含错误的类型, ...

  6. Redis学习之incr命令

    目录 incr命令 语法 返回值 例子 incr命令 Redis incr,命令将 key 中储存的数字值自增1 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 incr 操 ...

  7. redis的incr和incrby命令

    Redis Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作. Redis Incrby 命令将 key 中储存的 ...

  8. redis中的incr命令和incrby命令

    Redis Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作. Redis Incrby 命令将 key 中储存的 ...

  9. 【并发】详解redis的incr、decr命令

    一.前言 redis是一个单线程的服务,那么所有的命令肯定会排队被redis执行,redis提供的命令都是原子性的,百度搜索incr\decr就是说将对应的key+1,key-1的值重新set到red ...

  10. Redis的KEYS命令引起宕机事件

    摘要: 使用 Redis 的开发者必看,吸取教训啊! 原文:Redis 的 KEYS 命令引起 RDS 数据库雪崩,RDS 发生两次宕机,造成几百万的资金损失 作者:陈浩翔 Fundebug经授权转载 ...

最新文章

  1. [Design] Flyweight Pattern
  2. freeRTOSConfig.h文件对FreeRTOS进行系统配置
  3. php 判断是否是单词,php – 检查字符串是否包含任何单词
  4. 【NLP】发现一篇专门吐槽 NLP 内卷现状的 ACL 论文 ...
  5. Linux远程访问及控制(SSH、TCP Wrappers 访问控制)
  6. 为什么开源的代码没有注释_代码注释那些事儿
  7. Unity3D在C#编程中的一些命名空间的引用及说明
  8. linux-basic(13)学习shell script
  9. zynq+linux固化程序,如何在 Zynq UltraScale+ MPSoC 上实现 Linux UIO 设计
  10. 视频领域的Instagram:Viddy用户突破2600万
  11. python-带参数的装饰器
  12. Linux系统运维人员常用速查表
  13. 服务器消息机制实现--记录
  14. 用qt调用第三方库resolve
  15. 亲测源码多多进鱼带VUE源码任务悬赏源码活动营销三级分销返佣积分商城版
  16. 项目启动时,出现了Consider defining a bean of type ‘xxx’ in your configuration 问题。
  17. 小程序报错类—— thirdScriptError sdk uncaught third Error Cannot read property '$mount' of unde
  18. 请教求助,打开U盘显示,你当前无权访问该文件夹。
  19. 压测学习总结(1)——高并发性能指标:QPS、TPS、RT、吞吐量详解
  20. 家庭生活指南杂志家庭生活指南杂志社家庭生活指南编辑部2022年第6期目录

热门文章

  1. C++:构造函数中调用虚函数
  2. 计算机专业就业方向 【转】
  3. Shopee面试问题整理
  4. Scheme语言基础之数据类型
  5. prometheus+grafana搭建监控平台监控压测服务器mysql性能
  6. LSTM的优点和缺点
  7. vue input 只允许输入整数、整数和小数(保留小数点后两位)
  8. 【最佳实践】瀚高数据库备份恢复操作
  9. 小白也能看懂的手机配置攻略
  10. CleanMyMac2022免费版MAC内存清理空间软件