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

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

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

    xylz,imxylz

    關注后端架構、中間件、分布式和并發編程

       :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks
    線上服務器負載過高發生了報警,同事找我求救。
    我看到機器的負載都超過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 

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

    既然CPU在這里運行,那么就說明這個地方一定存在非常耗費CPU的操作,導致CPU非常繁忙,從而系統負載過高。
    看詳細堆棧,其實CPU在進行內存的拷貝動作。
    看下面的源碼。
    java.lang.StringBuilder.toString()
        public String toString() {
            // Create a copy, don't share the array
        return new String(value, 0, count);
        }
    接著看java.lang.String的構造函數:
        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);
        }

    看出來了么?
    問題的關鍵在于String構造函數的最后一行,value并不是直接指向的,而是重新生成了一個新的字符串,使用系統拷貝函數進行內存復制。
    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");
    }
    這里有問題的地方在于每次循環一行的時候都生成一個新的字符串。也就是說如果HTTP返回的結果輸入流中有1000行的話,將額外生成1000個字符串(不算StringBuilder擴容生成的個數)。每一個字符串還比前一個字符串大。


    我們來做一個簡單的測試,我們在原來的代碼上增加幾行計數代碼。
        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);
    我們記錄下行數lines以及額外發生的字符串拷貝大小malloc。
    這是一次輸出的結果。
    1169 -> 66958 -> 39356387
    也就是1169行的網頁,一共是66958字節(65KB),結果額外生成的內存大小(不算StringBuilder擴容占用的內存大小)為39356387字節(37.5MB)!!!
    試想一下,CPU一直頻繁于進行內存分配,機器的負載能不高么?我們線上服務器是2個CPU 16核,內存24G的Redhat Enterprise Linux 5.5,負載居然達到幾十。這還是只有訪問量很低的時候。這就難怪服務頻繁宕機了。

    事實上我們有非常完善和豐富的基于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);

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


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


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

    評論

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

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

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

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

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

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

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

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

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

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

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

    大俠能否講一下這個是怎樣發現的?  回復  更多評論
      

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

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

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

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

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


    ©2009-2014 IMXYLZ
    主站蜘蛛池模板: 人人鲁免费播放视频人人香蕉 | 国产亚洲精品无码专区| 亚洲成AV人影片在线观看| 黄页免费的网站勿入免费直接进入| 亚洲一本综合久久| 久久青草国产免费观看| 久久噜噜噜久久亚洲va久| 国产精品免费福利久久| 亚洲综合免费视频| 国产大片线上免费观看 | 亚洲精品欧洲精品| 免费不卡视频一卡二卡| 亚洲中文无码永久免| 国产精品久久免费视频| 一个人看的在线免费视频| 亚洲AV永久无码精品成人| 在线免费观看你懂的| 亚洲无码一区二区三区| 亚洲AV蜜桃永久无码精品| a级毛片免费播放| 亚洲女人影院想要爱| 国产裸模视频免费区无码| 一区二区三区免费视频观看 | 亚洲中文无码永久免费| www.91亚洲| 6080午夜一级毛片免费看| 国产日产亚洲系列| 亚洲一区在线观看视频| 天天摸夜夜摸成人免费视频| 亚洲成在人线av| 和日本免费不卡在线v| MM1313亚洲国产精品| 久久亚洲精品无码观看不卡| 99re热精品视频国产免费| 中文字幕乱码亚洲无线三区| 国产91精品一区二区麻豆亚洲| 最近中文字幕大全免费视频| 精品无码专区亚洲| 亚洲精品无码不卡| 国产在线观看免费视频播放器| 免费成人在线视频观看|