1. 程式人生 > >一次執行緒池有關的效能調優之旅

一次執行緒池有關的效能調優之旅

最近在做的專案,在除錯時打開了top,發現CPU一直是90%以上的,這讓我們感覺奇怪。一般來說寫得好的程式,CPU不應該滿負載執行。大家決定查詢原因。

1、總共有3個大模組,每個模組都是由Quartz定時器觸發。所以每次只開放一個,遮蔽掉其他兩個。結果發現,本人和大師寫的模組單獨執行時CPU都很高再見再見

2、剛好那晚是週五,其他人都滿懷激動的心情迎接週末,我們一直搞到9點半,排查問題。這個過程也是挺曲折的。首先想了一下程式中哪些地方會耗資源。db連線?db寫入?

Socket通訊?執行緒池?當晚沒有找出問題。

3、時間來到了週末,繼續在宿舍中探索此問題。我的模組共有3個主要的job,從這裡入手。

 <ref bean="loadMessageJobTrigger" />    a
 <ref bean="updataMongoJobTrigger" />   b
 <ref bean="threadPoolJobTrigger" />        c

先只開了a,這是個從MongoDB讀取資料到佇列的job,一次1w條。執行時cpu穩定在20%,不是這裡的問題;

只開b,這是個批量寫入MongoDB的job,一次最多也就寫1k,也不是這裡的問題;

開了a和c,一下子就90%了,看來是c的問題。這是一個用job來啟動執行緒池,定義了一個pool,在while(true)中,判斷有空閒thread就新增任務。這裡大師在群裡說他已經找到是死迴圈的問題。若非我的也是了。

while (true) {
      if (normalFutures.size() == poolSize) {
                tmpFutureList = new ArrayList<Future<ConsumeMessageThread>>();
                for (int i = 0; i < poolSize; i++) {   // 1
                    future = normalFutures.get(i);
                    if(! future.isDone()) {
                        tmpFutureList.add(future);
                    }
                }
                normalFutures = tmpFutureList;
            }else {  // 2
                ConsumeMessageThread msgThread = (ConsumeMessageThread) BeanHoldFactory
                        .getApplicationContext()
                        .getBean("consumeMessageThread");
                
                map = MessageContainer.waitForPushQueue.poll();
                ......
                msgThread.initMap(map, newSerialId);
                future = (Future<ConsumeMessageThread>) exService.submit(msgThread);
                normalFutures.add(future);            
 }
while(true)中大致如上。如果task總數已經等於執行緒池中thread數量,就進入1中判斷是否有沒做完的task,有就放到臨時的list中。如果task不滿,get一個bean,帶上資料,提交到pool中。我先把所有程式碼都註釋,空跑while(true),發現cpu不高。想到可能是task處理中耗資源。我的ConsumeMessagThread中包含了httpClient的程式碼。便把這幾段注掉
client = HttpClients.createDefault();
post = new HttpPost(url);
post.setHeader("Content-Type", "text/html;charset=UTF-8");
ByteArrayEntity e = new ByteArrayEntity(json.getBytes("UTF-8"));
post.setEntity(e);
response = client.execute(post);

再跑,cpu正常。就是這幾段的問題了吧。由於這是一個關於訊息推送的專案,資料量會很大,必須要快速地把資料推送給三方介面,所以在一個dead loop中不停使用httpclient。client和post要頻繁建立並且excute,這是消耗資源的。於是在外層用了一個int變數來記,當迴圈1000次,就sleep(100)。發現在sleep期間cpu下降了,但是做1000次的期間,cpu照樣100%,呈鋸齒狀波動。看來一次性執行太多httpclient還是不行。把1000改小,波動則小一些。沒意思,無論cpu再高,也要成為一條平穩的線才好。那隻能dead loop的最後加sleep,不要湊夠1000了。這樣子就是每個task都會sleep,而不再是任由瞬間的執行,傳送速度上也做了妥協。比如說加了sleep(5),理論上的最快速度也只能是200個/s,但是cpu平穩,在i7 4720HQ上平均十幾而已。不加sleep,可能是好幾百,但是cpu滿載。後期這種速度應該是達不到要求了。到時再考慮叢集什麼的來buff了。叢集的話,從資料來源做好入口控制,保證不同server傳送不同的資料即可。

(注:有個誤區就是之前以為空跑while(true)時cpu很低,以為不是死迴圈的問題。其實是錯誤的。如果一點程式碼都不寫,幾乎不佔資源的,但是如果只寫一行system.out.print,也是100%)