在某天凌晨,歐美服出現了卡頓,但是服務器的CPU並不高,內部服務監控日志來看,是clusteragent的mq overload了,網卡IO從流量上來看,也沒達到瓶頸。后來,對方SA說是參數配錯了,8核的機器配了24線程,改成8線程后重啟,就不卡頓了。這里排查的時候用到了tracetag,雖然有點麻煩,還是值得的。
后來,又再一次出現了同樣的問題。反復調參后,發現center配4線程就有問題,配6線程就不會卡頓。子熏懷疑是線程權重設置有問題,前面的-1都改成0就解決了,改成0以后即使再配4線程,也沒有問題了。出問題的代碼如下:
static int weight[] = { -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, }; struct worker_parm wp[thread]; for (i=0;i<thread;i++) { wp[i].m = m; wp[i].id = i; if (i < sizeof(weight)/sizeof(weight[0])) { wp[i].weight= weight[i]; } else { wp[i].weight = 0; } create_thread(&pid[i+3], thread_worker, &wp[i]); }
這里線程創建的時候,會定義一個權重數組,4核的時候,相當於每個線程權重都是-1,權重的用途可以看這里:
1 int i,n=1; 2 struct skynet_message msg; 3 4 for (i=0;i<n;i++) { 5 if (skynet_mq_pop(q,&msg)) { 6 skynet_context_release(ctx); 7 return skynet_globalmq_pop(); 8 } else if (i==0 && weight >= 0) { 9 n = skynet_mq_length(q); 10 n >>= weight; 11 } 12 int overload = skynet_mq_overload(q); 13 if (overload) { 14 skynet_error(ctx, "May overload, message queue length = %d", overload); 15 } 16 17 skynet_monitor_trigger(sm, msg.source , handle); 18 19 if (ctx->cb == NULL) { 20 skynet_free(msg.data); 21 } else { 22 dispatch_message(ctx, &msg); 23 } 24 25 skynet_monitor_trigger(sm, 0,0); 26 }
這里10行的n表示mq的未處理消息條數,n右移-1位就是0,下面22行的dispatch_message就只會執行一次。右移0位,就表示有多少條消息,就處理多少條消息,右移1位,就是只處理(未處理消息條數/2)這么多。skynet_mq_pop是說從mq里嘗試彈消息,成功返回0,mq是空則返回1。如果第一次進入for循環,mq有未處理消息,這時候權重為0,就會處理當前積壓的消息。即使處理過程中來了新的消息,因為第8行的代碼不會重復跑,所以會歸並到下次消息調度來處理。
子熏提到可以將權重改成-1,0,-1,0這樣排布,目前項目是直接將最低線程數改成8來解決的,不用改代碼。遠期來看,可能引入優先級比較好,高優先級的多處理一下。子熏提的另一個問題值得思考,線程每次處理一個消息,然后切換出去,跟每次處理完當下所有消息,再切換出去,不考慮切換開銷的話,工作量是不變的。為什么現在會出現CPU不高的情況呢?而且消息處理明顯慢了很多
我覺得這從側面反映了當前的測量體系是有問題的,CPU不高不能反映游戲就正常運行了。有可能是線程總是在切換ctx,所以cache miss比較高,效率就比較低了。最好是能夠搞出來一個工作模型,里面跑的是跟線上業務類似的工作負載,每條消息處理不會太快也不會太慢,然后比較一下處理一條就切換,跟處理完再切換,兩種不同調度的吞吐量區別。
參考閱讀:
http://www.brendangregg.com/blog/2017-05-09/cpu-utilization-is-wrong.html
CPU Utilization is Wrong 作者認為CPU使用率指導意義不大,應該看的是Instruction Per Cycle,小於1的話往往是memory限制了CPU運行效率。吐槽下virtualbox,沒提供相應的計數器,在perf里看不出來。。。
后續
上次子熏提到,如果消息很多處理不過來,CPU不可能這么低。最后,子熏在卡頓的時候直接用gdb attach進去了,發現卡頓的時候在調poll。捋了一遍代碼,結果是連接平台的mq用了block住線程的poll接口,然后最近合服,跑了多條mq。。。應該一條mq注冊多個serverid,而不是每個serverid單獨起一個服務,每個服務都去連平台mq的。
調了權重后能極大改善,是因為mq服務卡了幾個線程,剩余線程每次只處理一個的話,吞吐量就不夠了,換成0會改善很多,但是如果mq服務更多(合服數量更多),還是會有同樣的問題的。
這里用gdb有點蛋疼,如果attach進去沒遇上那個窗口,就比較難看到了。可惜用systemtap對系統要求太高了,裝完一堆東西,還是這個沒有那個沒有,沒法跑出來看。最后發現strace可以看到,strace -k -fp 進程pid -T -tt -o /tmp/strace.log可以打出來系統調用,還有每個調用的耗時,-k可以打調用棧。-k依賴於libunwind,我是自己編譯后的版本才有的。。