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

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

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

    xylz,imxylz

    關(guān)注后端架構(gòu)、中間件、分布式和并發(fā)編程

       :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks
    線上服務(wù)器負(fù)載過高發(fā)生了報警,同事找我求救。
    我看到機器的負(fù)載都超過20了,查看java進程線程棧,找到了出問題的代碼。

    下面是其代碼片段,實際情況錯誤處理比這更壞。
     1 package demo;
     2 
     3 import java.io.BufferedReader;
     4 import java.io.InputStream;
     5 import java.io.InputStreamReader;
     6 import java.net.HttpURLConnection;
     7 import java.net.URL;
     8 import java.net.URLConnection;
     9 import org.apache.commons.lang.StringUtils;
    10 
    11 /**
    12  * @author adyliu (imxylz#gmail.com)
    13  * @since 2012-3-15
    14  */
    15 public class FaultDemo {
    16 
    17     /**
    18      * @param args
    19      */
    20     public static void main(String[] args) throws Exception {
    21         final String tudou = "http://v.youku.com/v_playlist/f17170661o1p9.html";
    22 
    23         URL url = new URL(tudou);
    24         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    25         conn.connect();
    26         try {
    27             InputStream in = conn.getInputStream();
    28             BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
    29             StringBuilder buf = new StringBuilder();
    30             String line = null;
    31             while ((line = br.readLine()) != null) {
    32                 if (StringUtils.isNotEmpty(buf.toString())) {
    33                     buf.append("\r\n");
    34                 }
    35                 buf.append(line);
    36             }
    37             //do something with 'buf'
    38 
    39         } finally {
    40             conn.disconnect();
    41         }
    42 
    43     }
    44 
    45 }
    46 

    思考下,這段代碼有什么致命問題么?(這里不追究業(yè)務(wù)邏輯處理的正確性以及細(xì)小的瑕疵)
    .
    ..
    ...
    現(xiàn)在回來。
    我發(fā)現(xiàn)線程棧里面的線程都RUNNABLE在32行。
    這一行看起來有什么問題呢?StringBuilder.toString()不是轉(zhuǎn)換成String么?Apache commons-lang里面的StringUtils.isNotEmpty使用也沒問題啊?
    看代碼,人家的邏輯其實是判斷是否是第一行,如果不是第一行那么就增加一個換行符。

    既然CPU在這里運行,那么就說明這個地方一定存在非常耗費CPU的操作,導(dǎo)致CPU非常繁忙,從而系統(tǒng)負(fù)載過高。
    看詳細(xì)堆棧,其實CPU在進行內(nèi)存的拷貝動作。
    看下面的源碼。
    java.lang.StringBuilder.toString()
        public String toString() {
            // Create a copy, don't share the array
        return new String(value, 0, count);
        }
    接著看java.lang.String的構(gòu)造函數(shù):
        public String(char value[], int offset, int count) {
            if (offset < 0) {
                throw new StringIndexOutOfBoundsException(offset);
            }
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            // Note: offset or count might be near -1>>>1.
            if (offset > value.length - count) {
                throw new StringIndexOutOfBoundsException(offset + count);
            }
            this.offset = 0;
            this.count = count;
            this.value = Arrays.copyOfRange(value, offset, offset+count);
        }

    看出來了么?
    問題的關(guān)鍵在于String構(gòu)造函數(shù)的最后一行,value并不是直接指向的,而是重新生成了一個新的字符串,使用系統(tǒng)拷貝函數(shù)進行內(nèi)存復(fù)制。
    java.util.Arrays.copyOfRange(char[], int, int)
        public static char[] copyOfRange(char[] original, int from, int to) {
            int newLength = to - from;
            if (newLength < 0)
                throw new IllegalArgumentException(from + " > " + to);
            char[] copy = new char[newLength];
            System.arraycopy(original, from, copy, 0,
                             Math.min(original.length - from, newLength));
            return copy;
        }

    好了,再回頭看邏輯代碼32行。
    if (StringUtils.isNotEmpty(buf.toString())) {
        buf.append("\r\n");
    }
    這里有問題的地方在于每次循環(huán)一行的時候都生成一個新的字符串。也就是說如果HTTP返回的結(jié)果輸入流中有1000行的話,將額外生成1000個字符串(不算StringBuilder擴容生成的個數(shù))。每一個字符串還比前一個字符串大。


    我們來做一個簡單的測試,我們在原來的代碼上增加幾行計數(shù)代碼。
        int lines =0;
        int count = 0;
        int malloc = 0;
        while ((line = br.readLine()) != null) {
            lines++;
            count+=line.length();
            malloc += count;
            if (StringUtils.isNotEmpty(buf.toString())) {
                buf.append("\r\n");
            }
            buf.append(line);
        }
        System.out.println(lines+" -> "+count+" -> "+malloc);
    我們記錄下行數(shù)lines以及額外發(fā)生的字符串拷貝大小malloc。
    這是一次輸出的結(jié)果。
    1169 -> 66958 -> 39356387
    也就是1169行的網(wǎng)頁,一共是66958字節(jié)(65KB),結(jié)果額外生成的內(nèi)存大小(不算StringBuilder擴容占用的內(nèi)存大小)為39356387字節(jié)(37.5MB)!!!
    試想一下,CPU一直頻繁于進行內(nèi)存分配,機器的負(fù)載能不高么?我們線上服務(wù)器是2個CPU 16核,內(nèi)存24G的Redhat Enterprise Linux 5.5,負(fù)載居然達到幾十。這還是只有訪問量很低的時候。這就難怪服務(wù)頻繁宕機了。

    事實上我們有非常完善和豐富的基于Apache commons-httpclient的封裝,操作起來也非常簡單。對于這種簡單的請求,只需要一條命令就解決了。
    String platform.utils.HttpClientUtils.getResponse(String)
    String platform.utils.HttpClientUtils.postResponse(String, Map<String, String>)

    即使非要自造輪子,處理這種簡單的輸入流可以使用下面的代碼,就可以很好的解決問題。
        InputStream in = 
        ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
        int len = -1;
        byte[] b = new byte[8192];//8k
        while ((len = in.read(b)) > 0) {
            baos.write(b, 0, len);
        }
        baos.close();//ignore is ok
        String response =  new String(baos.toByteArray(), encoding);

    當(dāng)然了,最后緊急處理線上問題最快的方式就是將有問題的代碼稍微變通下即可。
        if (buf.length() > 0) {
            buf.append("\r\n");
        }


    這個問題非常簡單,只是想表達幾個觀點:
    • 團隊更需要合作,按照規(guī)范來進行。自造輪子不是不可以,但是生產(chǎn)環(huán)境還是要限于自己熟悉的方式。
    • 即使非常簡單的代碼,也有可能有致命的陷阱在里面。善于思考才是王道。
    • 學(xué)習(xí)開源的代碼和常規(guī)思路,學(xué)習(xí)解決問題的常規(guī)做法。這個問題其實非常簡單,熟悉輸入輸出流的人非常熟練就能解決問題。


    ©2009-2014 IMXYLZ |求賢若渴
    posted on 2012-03-15 18:30 imxylz 閱讀(11527) 評論(16)  編輯  收藏 所屬分類: J2EE

    評論

    # re: 一次簡單卻致命的錯誤[未登錄] 2012-03-15 22:06 Joey
    if (buf.toString().length() > 0) {
    buf.append("\r\n");
    }
    既然是toString() 方法 每個循環(huán) 都會生成新的String對象引起的為什么還要像上面那樣解決?  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-15 22:10 xylz
    @Joey
    謝謝提醒,寫錯了。
    直接用文本復(fù)制粘貼手動修改錯了。去掉toString()就可以了。  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤[未登錄] 2012-03-16 09:40 changedi
    嗯,在技術(shù)汪洋中,我們很容易陷入而忽略了那些基本的思考~  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-16 11:03 小明
    寫出 if (StringUtils.isNotEmpty(buf.toString()))的程序員應(yīng)該裁掉  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤[未登錄] 2012-03-16 11:27 kevin
    @小明
    同意,為什么總有人喜歡寫StringUtils之類的東西,我在很多項目里都看到過。  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-16 18:35 Saga
    @changedi
    確實,深有感受  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-17 15:17 bescq
    在三分鐘內(nèi)看出這個代碼問題,并給與解決方案的人,在貴司能給個什么級別?大概值多少米?   回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-19 19:36 路過
    @bescq
    8K-1W  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-22 00:14 iamct
    親,偶爾瞅了一眼排行。第6:) 另外,為啥不把那個判斷放在循環(huán)外面呢?  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-27 17:34 new comer
    "我發(fā)現(xiàn)線程棧里面的線程都RUNNABLE在32行。",大俠,能否講一下這個是怎樣發(fā)現(xiàn)的?  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-03-27 17:35 new comer
    "我發(fā)現(xiàn)線程棧里面的線程都RUNNABLE在32行。"

    大俠能否講一下這個是怎樣發(fā)現(xiàn)的?  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤[未登錄] 2012-03-30 19:23 y
    @new comer
    應(yīng)該是線程棧 打出來 一眼掃過去 一部分線程正在執(zhí)行的代碼都是一個地方  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2012-12-20 11:53 dohkoos
    if (StringUtils.isNotEmpty(buf.toString()))
    寫出這斷代碼是對StringUtils中的方法還不熟悉,isNotEmpty中的參數(shù)是CharSequence類型,不需要轉(zhuǎn)換的。
      回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2015-01-18 12:40 風(fēng)車
    同意@小明
      回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2015-06-16 09:49 高帆
    請交大俠!查看java線程是怎么看的  回復(fù)  更多評論
      

    # re: 一次簡單卻致命的錯誤 2015-11-16 11:52 shaw
    @高帆
    jstack 打印出來 線程棧信息,能看到 線程棧目前運行在那個地方,等待什么資源  回復(fù)  更多評論
      


    ©2009-2014 IMXYLZ
    主站蜘蛛池模板: 成人在线免费观看| 亚洲色成人WWW永久在线观看| 四虎影视www四虎免费| 久久久精品午夜免费不卡| 免费播放美女一级毛片| 亚洲免费视频网址| 亚洲国产二区三区久久| 亚洲人成色77777在线观看大 | 亚洲午夜无码片在线观看影院猛| 国产乱子精品免费视观看片| 国产免费阿v精品视频网址| 免费国产黄网站在线看| 狼人大香伊蕉国产WWW亚洲| 亚洲ts人妖网站| 亚洲精品日韩专区silk| 亚洲A∨无码一区二区三区| 亚洲综合熟女久久久30p| 亚洲黄黄黄网站在线观看| 国产又长又粗又爽免费视频| 成年性生交大片免费看| 国产一卡二卡3卡四卡免费| 日韩免费无码一区二区三区| 成全动漫视频在线观看免费高清版下载| 精品特级一级毛片免费观看| 亚洲乱码中文字幕在线| 亚洲最大中文字幕无码网站| 亚洲毛片基地4455ww| 亚洲欧洲日本天天堂在线观看| 久久久久亚洲AV成人无码| 久久精品亚洲中文字幕无码网站| 亚洲色偷偷偷鲁综合| 亚洲第一极品精品无码久久| 亚洲成av人影院| 亚洲伊人tv综合网色| 亚洲黄色在线观看网站| 亚洲精品乱码久久久久久下载 | 一级成人a毛片免费播放| 91av免费观看| 亚洲三级高清免费| 嫩草视频在线免费观看| 国产精品国产午夜免费福利看 |