JBOSS內容錯亂的分析過程

來源:互聯網
上載者:User

現象:
    線上服務的應用在發布一個應用後突然出現使用者帳號串號現象,這個問題的嚴重性完全是重大事故級。
對於商業網站簡直是致命的,線上在出現問題的時馬上進行了復原。對出現問題的使用者進行手工修正資料等善後
工作。

分析:
    因為之前基本沒有這個情況(說基本沒有,是據說一年前出現過這樣的一次情況,因為只出現過一次
而且是一年前的,當時懷疑有可能是網路接入層的資料緩衝)。就因為一個應用發布突然出現大量(對於商業
網站,如果出現十幾個,幾十個使用者帳號錯誤,應該算是大量了),所以首先從新發布的應用查起。

    有一個線索就是當出現串號時,有些頁面的內容和狀態代碼都發生錯誤,本來應該是302的卻變成了200
還有的頁面內容明顯不對,本來只有true和false輸出,但卻輸出一段HTML。
    雖然不相信公司的程式員會將Session變數定義在請求處理之外,但還是小心地掃描一遍,排除。
    我們的應用中因為分布式應用的Session Store是自己實現的,所以對Request,Response,Session,Context
都進行了攔截封裝,如果某些羅輯導致沒有提交Response,可能會造成髒資料。但是對所有代碼進行逐行分析,沒
有發現這樣的問題。
    一個Servlet中只有一行代碼:Response.sendReDirect("/path");這是為了將原來多個不同的URL轉向同
一請求的,理論上這個URL肯定返回302, 但卻在發生串號時返回200.所以懷疑是否是在filter中某種條件下chain
沒有調用,直接返回了,請求沒有到達servlet.結果對所有filter分析後沒有這種情況。

    有些束手無策了,搜尋引擎能搜尋到的內容的參考率為零。
    從對業務層的懷疑轉移到了容器環境。
    一開始沒有懷疑容器環境是因為之前沒有出現這個問題,就是因為一個應用的發布而出現的,現在應用本身
的業務代碼基本排除問題,所以想象是否是容器環境以前存在問題,但因為條件沒有達到而沒有觸發,這次應用發布
後因為一個活動使訪問量增加好幾倍,是否是因為壓力上升而導致原來隱藏的問題觸發出來了?
    公司其它的應用的容器環境已經成熟穩定多年,所以主要看這個應用的容器環境和其它應用環境的區別。
    發現大多數應用都是apache2.0.3+jboss4.0.5+mod_jk1.2.28而這個應用是apache2.0.3+jboss4.2.3+mod_jk1.2.26.
jk版本本來就是從1.2.26升級上去的,升級之前也沒有發生過這種情況,所以先查jboss4.2.3。
    搜尋jboss4.2.3的BUG列表,終於發現一個靠譜的問題。

    jboss4.2.3使用的是jbossweb2.0.1,對於GA版本,2.0.x都存在一個BUG,會造成資料錯亂:
    org.apache.catalina.connector.Request.java中的parseParameters方法中,最後幾行:

        byte[] formData = null;
            if (len < CACHED_POST_LEN) {
                if (postData == null)
                    postData = new byte[CACHED_POST_LEN];
                formData = postData;
            } else {
                formData = new byte[len];
            }
        try {
                if (readPostBody(formData, len) != len) {
                    return;
                }
            } catch (IOException e) {
                // Client disconnect
                if (context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.parseParameters"), e);
                }
            }
            parameters.processParameters(formData, 0, len);

    在Servlet引擎處理實際的用戶端請求時,並不是每次請求產生一個Request對象,而是一個線程綁定一個
Request對象(ThreadLocal),在處理一個請求後進行清理然後重用。但是有時postData並沒被有清理,而是每次讀取的資料覆蓋一個buffer,

然後從buffer中讀取指定的長度。

    如果用戶端是一個POST請求,在從apache通過jk向jboss傳送時,jk先告訴jboss本次傳輸的body長度是x, 而jboss在
readPostBody時沒有讀到相應的長度,可能發生逾時,雖然異常被捕獲了,但卻沒有return,繼續運行到了下面的
    parameters.processParameters(formData, 0, len);因為buffer中並沒有讀到len長度的資料,但卻按len長度去處理,
    就會發生使用上一次請求的post的內容或部分上一次內容來進行processParameters.這必然會生產資料錯亂。

    為了重現這個問題,我在修改了mod_jk1.2.26的源碼的common/jk_ajp_common.c中 ajp_send_request函數:

    postlen = op->post->len;
    if (postlen > AJP_HEADER_LEN) {
        .................
    }
    else if (s->reco_status == RECO_FILLED) {
        .................
    }
    else{
        if(rand() % 3 == 0){
            jk_log(l, JK_LOG_ERROR,"it will sleep.......................");
            sleep(120);
            jk_log(l, JK_LOG_ERROR,"end sleep...........................");
        }
        .........................
    }
    在進入post資料發送的時候sleep了120秒。這個時間要大於jk設定檔中connection_pool_timeout,而不是
socket_timeout.connection_pool_timeout這個值和jboss中的Connector中connectionTimeOut是對應的。一般會設定很長
我們的應用設定成600s,為了調試逾時我把它們改成60s,所以sleep(120)就肯定發生逾時了。
    然後用用戶端程式進行測試,寫一個HttpURLConnection,然後我在post參數中放入了一個mark=xxxxxxxxxx的
隨機串,一個jsp檔案擷取mark參數後列印出來。我從用戶端再讀這個值,應該和我傳過去的是一樣的。
    因為有1/3的逾時機會,所以我只用10次迴圈來運行這個用戶端測試情況,果然在發生逾時的時候,傳回來的mark
不是當前請求傳過去的mar,而是上一次的mark. 完全驗證了這個情況。

    那麼要發生串號的情況,就可以解釋了。
    1.是post請求。
    2.servlet要處理Request,也就是要從中get內容,為了效能原因,用戶端內容被tcp層接收(apache)後先緩衝起來。
如果servlet本身沒有涉及對Request.getXXX的處理,Jboss就不會通過jk向apache請求這部分資料,apache直接把它拋棄。
不會觸發這個BUG。在調試的時候我就上過這個當,在把jsp改成servlet測試時,沒有擷取參數,結果沒有觸發,還想了好久
才發現這個問題。
    3.雖然發生異常後本次請求使用了上次的請求的部分資料或全部資料,從邏輯上說參數本身是錯誤的,但並不一定
輸出的結果也是錯誤的,有些內容不是根據請求參數輸出的。
    4.只有前一次請求資料中本身包含其它帳號的資訊,以此作為條件擷取的輸出結果中又包含帳號資訊返回給目前使用者
時才會看到串號現象。所以即使產生很多readPostBody也不一定產生串號結果,但串號結果是這樣產生的。

 

解決:

      這個簡單了,如果你覺得升級或降級其它版本都有風險,直接在上面的catch的最後加上return.然後編譯替換原來的jar就行了。

 

    附註:這個BUG在jboss4.0.5中沒有產生,4.0.5中使用的tomcat5.5, parameters.processParameters(formData, 0, len);
方法中try塊中調用的,而且加了readLen == contentLen的測試。而jbossweb2.10開始也修複了這個bug.
   
    另外其它版本還有一個可能會產生資料錯亂,就是jk和jboss之間的串連池,預設的實現中如果發生異常,連結會被
關閉,但如果在connector中有對Connector進行攔截的handler中自己吃掉了這個異常,則異常不能被AjpConnectionHandler
擷取,則串連不能被關閉而放回串連池中,對於非阻塞IO,這個SocketChannel中的殘留資料就會被下一次讀取,這樣的結果
是不能接受的,所以如果要對輸入輸出資料流進行攔截處理不要捕獲它們的異常,或者捕獲處理後要立即拋出去。

這個問題產生的原因很大一部分是因為ie的post兩階段交易認可。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.