最近有個線上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],如果原值是最小的值取反之後不就越界了嘛。
空口無憑,我們做個試驗:
可以看到,試驗結果與現場完全吻合。
其實這個坑我很早以前就知道了,並不是什麼稀奇的問題,但是這次還是犯錯了,所以在此記錄一下,嚴防以後再犯,也和大家分享一下,希望大家以後注意。