最近有个线上spark streaming程序跑着跑着就挂了,调查了一番,发现了一个平时大家都不太注意的问题。
看了日志,抛出的异常如下:
java.lang.ArrayIndexOutOfBoundsException: -2
at com.xiaomi.poppy.hbase.HBaseUtil.getHashPrefix(HBaseUtil.java:58)
at com.xiaomi.xmpush.spark.hbase.HBaseProcessor.buildRowKey(HBaseProcessor.java:220)
at com.xiaomi.xmpush.spark.hbase.HBaseProcessor.buildRequest(HBaseProcessor.java:203)
at com.xiaomi.xmpush.spark.hbase.HBaseProcessor.writeCounter(HBaseProcessor.java:126)
at com.xiaomi.xmpush.main.PushSparkStatMain$4$1.call(PushSparkStatMain.java:102)
at com.xiaomi.xmpush.main.PushSparkStatMain$4$1.call(PushSparkStatMain.java:97)
at org.apache.spark.api.java.JavaRDDLike$$anonfun$foreach$1.apply(JavaRDDLike.scala:330)
at org.apache.spark.api.java.JavaRDDLike$$anonfun$foreach$1.apply(JavaRDDLike.scala:330)
at scala.collection.Iterator$class.foreach(Iterator.scala:727)
at scala.collection.AbstractIterator.foreach(Iterator.scala:1157)
at org.apache.spark.rdd.RDD$$anonfun$foreach$1$$anonfun$apply$28.apply(RDD.scala:894)
at org.apache.spark.rdd.RDD$$anonfun$foreach$1$$anonfun$apply$28.apply(RDD.scala:894)
at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1854)
at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1854)
at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66)
找到出错代码如下:
public static String getHashPrefix(String input, int length) {
int hash = Math.abs(getHashCodeForString(input));
final int radix = CHAR_MAP.length;
StringBuffer sb = new StringBuffer();
for (int index = 0; index < length; ++index) {
sb.append(CHAR_MAP[hash % radix]);
hash /= radix;
}
return sb.toString();
}
具体来说就是 sb.append(CHAR_MAP[hash % radix]);
这句中的 hash % radix
得到了一个负数(-2),导致数组越界。
好奇怪啊,这里的 hash
是一个绝对值,按理来说是非负数才对,而 radix
是一个数组的长度,已经固定为62了, hash % radix
怎么可能为负数。
先别下结论,我们首先知道的是: 模运算的值如果为负数,则两个运算数必须要有一个为负数,并且我们可以百分之百肯定radix不可能为负数 ,所以按照神探福尔摩斯所说的: 排除一切不可能的,剩下的即使再不可能,那也是真相 ,那我们就先把『真相』的帽子戴在这个绝对值的头上。
去看了一下JDK中关于Math.abs的文档:
/**
* Returns the absolute value of an {@code int} value.
* If the argument is not negative, the argument is returned.
* If the argument is negative, the negation of the argument is returned.
*
* <p>Note that if the argument is equal to the value of
* {@link Integer#MIN_VALUE}, the most negative representable
* {@code int} value, the result is that same value, which is
* negative.
*
* @param a the argument whose absolute value is to be determined
* @return the absolute value of the argument.
*/
public static int abs(int a) {
return (a < 0) ? -a : a;
}
果然找到了真相:如果是对Integer(Long也一样)取绝对值,如果原值是Integer.MIN_VALUE,则得到的绝对值和原值相等,是一个负数。为什么呢?因为你看看abs的实现,它很简单,如果原值是正数,直接返回,如果是负数,就简单地在前面加个负号。然而Integer的范围是[-2147483648,2147483647],如果原值是最小的值取反之后不就越界了嘛。
空口无凭,我们做个试验:
可以看到,试验结果与现场完全吻合。
其实这个坑我很早以前就知道了,并不是什么稀奇的问题,但是这次还是犯错了,所以在此记录一下,严防以后再犯,也和大家分享一下,希望大家以后注意。
声明
原创文章,转载请注明出处,本文链接: http://qifuguang.me/2015/12/30/严防Math-abs-返回负数/
如果你喜欢我的文章,请关注我的新浪微博@winwill2012或者微信公众账号:『机智的程序猿』: