<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 閱讀(4130) 評(píng)論(5)  編輯  收藏 所屬分類: java工作隨筆

        這篇blog遲到了很久,本來是想寫另一個(gè)跟網(wǎng)絡(luò)相關(guān)bug的查找過程,偷偷懶,寫下最近印象比較深刻的bug。這個(gè)bug是我的同事水寒最終定位到的。
        前幾個(gè)月同事報(bào)告稱有一個(gè)線上MQ集群會(huì)同一時(shí)間拋出ArrayIndexOutOfBoundsException這個(gè)異常,也就是數(shù)組越界。查看源碼,除去一些無關(guān)緊要的細(xì)節(jié)大概是這樣子:
    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);
    }

       }

        很顯然,這里的本意是實(shí)現(xiàn)一個(gè)輪詢的連接選擇器,返回一個(gè)選中的連接。使用AtomicInteger遞增并對(duì)鏈表大小取模,返回結(jié)果索引位置的連接。異常拋出的位置就是我代碼中標(biāo)紅的位置。

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

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

        這個(gè)印象持續(xù)至今,在clojure里結(jié)果也是這樣子:
    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

        這三種語言的結(jié)果完全一致,結(jié)果都為正數(shù)。這個(gè)慣性思維延續(xù)到j(luò)ava卻不成立了,可惜我根本沒做測試,讓我們?cè)囅拢?br />
       public static void main(final String[] args) {
            System.out.println(
    -1000 % 3);
            System.out.println(
    -2001 % 4);
        }

    打印結(jié)果為:
    -1
    -1

        果然,在java里負(fù)數(shù)取模的結(jié)果為負(fù)數(shù),而不是我習(xí)慣性地認(rèn)為是正數(shù)。因此最終的定位到的原因就是sets這個(gè)變量遞增超過Integer.MAX_VALUE后越界變成負(fù)數(shù)了,取模的結(jié)果為負(fù)數(shù),導(dǎo)致拋出數(shù)組越界的異常,這也解釋了為什么同一個(gè)集群都在同一時(shí)間出問題,因?yàn)檫@個(gè)集群內(nèi)的機(jī)器啟動(dòng)時(shí)間相鄰并且調(diào)用這個(gè)方法次數(shù)相對(duì)平均。修正問題很簡單,加個(gè)Math.abs就好。

        Update:加個(gè)abs是不夠的,因?yàn)镸ath.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.

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

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

    評(píng)論

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

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

    剛開始看的時(shí)候, 我還在想呢, 你們?cè)趺刺幚碡?fù)數(shù)(還以為你們的數(shù)據(jù)不會(huì)超過Integer.MAX_VALUE呢

    # re: 找bug記(2)  回復(fù)  更多評(píng)論   

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

    # re: 找bug記(2)  回復(fù)  更多評(píng)論   

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

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

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

    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.

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

    # re: 找bug記(2)  回復(fù)  更多評(píng)論   

    2011-09-16 13:34 by dennis
    @steven
    感謝分享,還有這個(gè)隱患。不過還好,至少只是失敗一次。馬上改過來。
    主站蜘蛛池模板: 免费一本色道久久一区| 亚洲国产福利精品一区二区| 成人免费午夜视频| 日韩精品久久久久久免费| fc2成年免费共享视频网站| 亚洲色大18成人网站WWW在线播放 亚洲色大成WWW亚洲女子 | 69精品免费视频| 中文字幕在线免费看| 黄人成a动漫片免费网站| 亚洲午夜无码久久久久小说| 亚洲高清在线播放| 国产亚洲情侣一区二区无码AV| 在线观看免费亚洲| 成年女人18级毛片毛片免费| 日韩国产免费一区二区三区| 久久久精品2019免费观看| 久久精品国产免费一区| 人妻在线日韩免费视频| 中文在线观看免费网站| 一区二区三区免费视频观看 | 亚洲国产精品丝袜在线观看| 国产一区二区三区免费视频| 国产精品成人无码免费| 国产美女精品视频免费观看| 国产又黄又爽又猛的免费视频播放| 最新仑乱免费视频| 日本不卡免费新一二三区| 日本a级片免费看| 在线观看永久免费视频网站| 国产成人无码免费视频97 | 亚洲heyzo专区无码综合| 亚洲国产一区二区三区在线观看| 亚洲欧洲专线一区| 亚洲国产精品无码久久九九大片 | 国产情侣激情在线视频免费看| 免费专区丝袜脚调教视频| 亚洲成人免费电影| 成人免费看片又大又黄| 国产又大又粗又硬又长免费| 亚洲女人被黑人巨大进入| 国产精品亚洲成在人线|