<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

    找bug記(2)

    Posted on 2011-09-02 00:02 dennis 閱讀(4129) 評論(5)  編輯  收藏 所屬分類: java工作隨筆

        這篇blog遲到了很久,本來是想寫另一個跟網絡相關bug的查找過程,偷偷懶,寫下最近印象比較深刻的bug。這個bug是我的同事水寒最終定位到的。
        前幾個月同事報告稱有一個線上MQ集群會同一時間拋出ArrayIndexOutOfBoundsException這個異常,也就是數組越界。查看源碼,除去一些無關緊要的細節大概是這樣子:
    public class ConnectionSelector{
        
    private AtomicInteger sets=new AtomicInteger(0);

       
    public void selectConnection(List<Connection> connList){
              
    if(connList==null){
                    
    return null;
               }
              
    final int size = connList.size();
                
    if (size == 0) {
                    
    return null;
                }
               
    return connList.get(sets.incrementAndGet() % size);
    }

       }

        很顯然,這里的本意是實現一個輪詢的連接選擇器,返回一個選中的連接。使用AtomicInteger遞增并對鏈表大小取模,返回結果索引位置的連接。異常拋出的位置就是我代碼中標紅的位置。

        顯然,這里有兩種可能,一種情況下是說在執行那一行代碼的時候,connList的大小縮小了(也就是說連接可能被其他線程移出),那么導致取模的結果越界。另一種可能是取模的結果本身確實超過了列表范圍。

        第一種情況是完全可能的,因為服務器的連接可能隨時斷開或者重連,但是這種情況相對非常少見,因此我們這里并沒有對這個選擇過程做同步,主要是從性能的角度出發,偶爾的失敗可以接受。很遺憾的是,我被我的思維慣性誤導了,從來沒有懷疑過第二種情況,總是認為是不是真的連接恰巧斷開導致這個異常,但是卻無法解釋這個異常發生后就一直錯誤下去,無法自行恢復。
        為什么說思維慣性誤導呢?這里的問題其實是負數取模的問題,對一個負數進行取模,結果會是正數還是負數?答案是結果因語言而異。
        我很早以前在使用Ruby的時候做過測試,負數取模結果為正數,例如在irb里嘗試下:
    >> -1000%3
    => 2
    >> -2001%4
    => 3

        這個印象持續至今,在clojure里結果也是這樣子:
    Clojure 1.2.1
    user
    => (mod -1000 3)
    2
    user
    => (mod -2001 4)
    3

        可以再試試python:
    Python 2.7.1 (r271:86832, Jun 16 201116:59:05
    [GCC 
    4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
    Type 
    "help""copyright""credits" or "license" for more information.
    >>> -10000%3
    2
    >>> -2001%4
    3

        這三種語言的結果完全一致,結果都為正數。這個慣性思維延續到java卻不成立了,可惜我根本沒做測試,讓我們試下:
       public static void main(final String[] args) {
            System.out.println(
    -1000 % 3);
            System.out.println(
    -2001 % 4);
        }

    打印結果為:
    -1
    -1

        果然,在java里負數取模的結果為負數,而不是我習慣性地認為是正數。因此最終的定位到的原因就是sets這個變量遞增超過Integer.MAX_VALUE后越界變成負數了,取模的結果為負數,導致拋出數組越界的異常,這也解釋了為什么同一個集群都在同一時間出問題,因為這個集群內的機器啟動時間相鄰并且調用這個方法次數相對平均。修正問題很簡單,加個Math.abs就好。

        Update:加個abs是不夠的,因為Math.abs的javadoc提醒了:
    Note that if the argument is equal to the value of Integer.MIN_VALUE, the most negative representable int value, the result is that same value, which is negative.

        也就是說對Integer.MIN_VALUE做abs結果仍然是負數。盡管在這個場景中失敗一次可以接受,但是最好的辦法還是回復中steven提到的抵消符號位的做法:
    (sets.incrementAndGet() & 0x7FFFFFFF% size
       
        這個問題更詳細的討論后來我找到這篇博客,作者討論幾種語言和計算器的這個問題的結果,給出了一些結論。不過我覺的這個結論可能也不是那么可靠,特別是對c/c++來說,很大程度上應該還是依賴于實現,最可靠的辦法還是強制結果為正。

        這個bug的幾個教訓:
    1、首先是第一次出現的時候沒有引起足夠重視,重啟解決問題后沒有深究。有句玩笑話:99%的程序問題都可以通過重啟解決。但是事實上問題仍然存在,該發生的終究還會發生。不管你信不信,它就是發生了,這是一個奇跡。
    2、注意大腦的思維慣性,經驗主義和教條主義都不可取。最近在讀一本好書《暗時間》,大腦誤導我們的手段可是多種多樣。
    3、最后就是這個負數取模的結果因語言而異,不要依賴于特定實現。
       

    評論

    # re: 找bug記(2)[未登錄]  回復  更多評論   

    2011-09-02 15:19 by xiaoyu
    恩, 取模這個問題, 在前幾年做Excel的時候就知道java中的值不一樣(數學中的取模應該是符合 被除數 = 除數 * 商 + 余數). 不過比較簡單, 利用數學公式, 也可以自己做.

    剛開始看的時候, 我還在想呢, 你們怎么處理負數(還以為你們的數據不會超過Integer.MAX_VALUE呢

    # re: 找bug記(2)  回復  更多評論   

    2011-09-05 11:00 by kino.lucky
    謝謝博主共享,但博主說的那篇文章打不開,我google出來一篇,原理講的還不錯:http://googies.info/blog/184.html

    # re: 找bug記(2)  回復  更多評論   

    2011-09-08 18:53 by dennis
    @kino.lucky
    感謝分享。

    # re: 找bug記(2)[未登錄]  回復  更多評論   

    2011-09-16 13:33 by steven
    Math.abs()函數并不能保證一定返回正數,請看官方的說明:

    Note that if the argument is equal to the value of
    Integer.MIN_VALUE, the most negative representable
    int value, the result is that same value, which is
    negative.

    因此正確的做法是通過位運算將符號位抵消:
    (sets.incrementAndGet() & 0x7FFFFFFF) % size

    # re: 找bug記(2)  回復  更多評論   

    2011-09-16 13:34 by dennis
    @steven
    感謝分享,還有這個隱患。不過還好,至少只是失敗一次。馬上改過來。
    主站蜘蛛池模板: 黄床大片免费30分钟国产精品 | 女人张开腿等男人桶免费视频| 亚洲精品白浆高清久久久久久| 精品久久久久久亚洲综合网| 大学生一级特黄的免费大片视频 | 久久国产精品免费观看| 亚洲色无码专区在线观看| 亚洲国产免费综合| 亚洲精品国产成人片| 四虎影视无码永久免费| 亚洲AV永久无码精品一百度影院 | 天天看免费高清影视| 亚洲成熟丰满熟妇高潮XXXXX| 午夜a级成人免费毛片| 国产99久久亚洲综合精品| 国产99视频精品免费视频7| 美女免费精品高清毛片在线视| 国产成人涩涩涩视频在线观看免费 | 久久久亚洲欧洲日产国码aⅴ| 免费国产污网站在线观看15| 亚洲精品亚洲人成在线观看麻豆| 黄色网址免费大全| 亚洲午夜福利在线视频| 免费在线观看黄色毛片| 精品一区二区三区免费视频| 亚洲丁香色婷婷综合欲色啪| 成人免费激情视频| 亚洲AV无码国产精品永久一区| 亚洲乱码日产精品a级毛片久久| 99在线热播精品免费99热| 亚洲视频一区二区在线观看| 最近的免费中文字幕视频| 美女免费视频一区二区三区| 亚洲国产成人高清在线观看 | 四虎影视无码永久免费| 亚洲午夜电影在线观看| 国产青草视频在线观看免费影院| 香蕉免费看一区二区三区| 亚洲1234区乱码| 久久精品亚洲男人的天堂| 免费观看激色视频网站bd|