背景
项目中用到了Redis作为中间件,使用incrBy
来承担一部分的累加计算。
数据量很大,要累加的这个值也很大。
代码如下:
ShardedJedisPipeline pipeline = jedis.pipelined();
# value就是字符串类型的值
pipeline.incrByFloat(redisKey, Double.valueOf(value));
报错
运行过程中出现了以下的错:
ERR increment would produce NaN or Infinity
21/06/08 18:13:07 ERROR Main: output to redis error because: redis.clients.jedis.exceptions.JedisDataException: ERR increment would produce NaN or Infinity
21/05/28 18:13:07 ERROR Main: message is: 20210609000500000|.....|Infinity
过程
一看ERR increment would produce NaN or Infinity
,以为是累加的结果值太大了。
排查了下数据,发现数据累加起来都没超过Long.MAX_VALUE
。
于是把double转为了long,累加方法改为了incrBy
,运行了一段时间后,又报了新的错。
21/06/08 21:48:41 ERROR Main: output to redis error because: redis.clients.jedis.exceptions.JedisDataException: ERR increment or decrement would overflow
ERR increment or decrement would overflow
仔细一看,发现这个错才是真正的累加超过范围了。
于是去官网看了下,发现incrBy
仅限于64位有符号整数,也就是最大值不能超过2的64次(9223372036854775807)。
再去验证了下数据,发现这次的累加值也的确超过了9223372036854775807。
既然这个错是超出范围,那上个错肯定不是了…
又找了找,找到了这篇文章https://stackoverflow.com/questions/36861472/what-happens-when-int64-maxvalue-is-exceded-with-redis-incr
做了以下的测试,也出现了我上面的那个值超出范围的错
127.0.0.1:6379> set value 9223372036854775807 (2 power 63 -1)
OK
127.0.0.1:6379> incr value
(error) ERR increment or decrement would overflow
127.0.0.1:6379>
于是我也做了incrbyfloat
的测试:
127.0.0.1:6379> incrbyfloat mwf 1111111111111111111111111111111111111111111111111111111111111111
"1111111111111111111082384117134316001359187045209948125833199616"
发现incrbyfloat
了这么大的值,都不会出现值溢出,所以我的问题肯定不是值溢出。
于是又仔细看了报错日志:
21/06/08 18:13:07 ERROR Main: output to redis error because: redis.clients.jedis.exceptions.JedisDataException: ERR increment would produce NaN or Infinity
21/05/28 18:13:07 ERROR Main: message is: 20210609000500000|.....|Infinity
看到了Infinity
这个罪魁祸首。
做如下测试:
127.0.0.1:6379> incrbyfloat mwf Infinity
(error) ERR increment would produce NaN or Infinity
果然是它导致的!!!
再输入非法的字符串:
127.0.0.1:6379> incrbyfloat mwf asd
(error) ERR value is not a valid float
报的错是不一样的。说明Infinity
是被当做了一个有效的浮点数。
那么问题又来了。
为什么Java中Double.valueOf
解析Infinity
不报错呢???
去Java中试了下,还真不报错,返回的就是Infinity
点进去Double类看了看,真的有Infinity
、-Infinity
、NaN
三个值。看来还是太年轻了。。。
解决办法
incrByFloat
的值不是Infinity
或NaN
就好了。
在Java中判断下是否是Infinity
或者NaN
,然后对异常值处理。
protected double doubleValue(String input){
if(input == null || input.trim().equals("")){
return 0.0d;
}else{
double v = Double.parseDouble(input);
return Double.isInfinite(v) || Double.isNaN(v) ? 0.0d : v;
}
}
注意
incrByFloat
比incrBy
能累加的范围大很多,推荐使用incrByFloat
incrByFloat
在传入Infinity
和NaN
时,会出现ERR increment would produce NaN or Infinity
这个错- Java中
Double.valuesOf
传入Infinity
、-Infinity
、Nan
时,不会报错,能正常解析并返回 - 可以调用
Double.isInfinite
来判断是否是Infinity
,调用isNaN
判断是否是NaN
参考
https://redis.io/commands/incrby
https://stackoverflow.com/questions/36861472/what-happens-when-int64-maxvalue-is-exceded-with-redis-incr