假設業務模型中耗費cpu的分四種類型,(1)網卡中斷(2)1個處理網絡收發包進程(3)耗費cpu的n個worker進程(4)其他不太耗費cpu的進程
基於1中的 負載均衡是針對進程數,那么(1)(2)大部分時間會出現在cpu0上,(3)的n個進程會隨着調度,平均到其他多個cpu上,(4)里的進程也是隨着調度分配到各個cpu上;
當發生網卡中斷的時候,cpu被打斷了,處理網卡中斷,那么分配到cpu0上的worker進程肯定是運行不了的
其他cpu上不是太耗費cpu的進程獲得cpu時,就算它的時間片很短,它也是要執行的,那么這個時候,你的worker進程還是被影響到了;按照調度邏輯,一種非常惡劣的情況是:(1)(2)(3)的進程全部分配到cpu0上,其他不太耗費cpu的進程數很多,全部分配到cpu1,cpu2,cpu3上。。那么網卡中斷發生的時候,你的業務進程就得不到cpu了
如果從業務的角度來說,worker進程運行越多,肯定業務處理越快,人為的將它捆綁到其他負載低的cpu上,肯定能提高worker進程使用cpu的時間
每個cpu都利用起來了,負載會比不綁定的情況下好很多
有效果的原因:
依據《linux內核設計與實現》的42節,人為控制一下cpu的綁定還是有用處地
linux的SMP負載均衡是基於進程數的,每個cpu都有一個可執行進程隊列(為什么不是線程隊列呢??),只有當其中一個cpu的可執行隊列里進程數比其他cpu隊列進程數多25%時,才會將進程移動到另外空閑cpu上,也就是說cpu0上的進程數應該是比其他cpu上多,但是會在25%以內。
示例程序
cpu.c
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/sysinfo.h>
#include<unistd.h>
#define __USE_GNU
#include<sched.h>
#include<ctype.h>
#include<string.h>
int main(int argc, char* argv[])
{
int num = sysconf(_SC_NPROCESSORS_CONF);
int created_thread = 0;
int myid;
int i;
int j = 0;
cpu_set_t mask;
cpu_set_t get;
if (argc != 2)
{
printf("usage : ./cpu num\n");
exit(1);
}
myid = atoi(argv[1]);
printf("system has %i processor(s). \n", num);
CPU_ZERO(&mask);
CPU_SET(myid, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) == -1)
{
printf("warning: could not set CPU affinity, continuing...\n");
}
while (1)
{
CPU_ZERO(&get);
if (sched_getaffinity(0, sizeof(get), &get) == -1)
{
printf("warning: cound not get cpu affinity, continuing...\n");
}
for (i = 0; i < num; i++)
{
if (CPU_ISSET(i, &get))
{
printf("this process %d is running processor : %d\n",getpid(), i);
}
}
}
return 0;
}
下面是在兩個終端分別執行了./cpu 0 ./cpu 2 后得到的結果. 效果比較明顯.
QUOTE:
Cpu0 : 5.3%us, 5.3%sy, 0.0%ni, 87.4%id, 0.0%wa, 0.0%hi, 2.0%si, 0.0%st
Cpu1 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 5.0%us, 12.2%sy, 0.0%ni, 82.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu4 : 0.0%us, 0.0%sy, 0.0%ni, 99.7%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu5 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu6 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu7 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
CPU親和力
linux下的進程可以通過sched_setaffinity系統調用設置進程親和力,限定進程只能在某些特定的CPU上運行。負載均衡必須考慮遵守這個限制(前面也多次提到)。
遷移線程
前面說到,在普通進程的load_balance過程中,如果負載不均衡,當前CPU會試圖從最繁忙的run_queue中pull幾個進程到自己的run_queue來。
但是如果進程遷移失敗呢?當失敗達到一定次數的時候,內核會試圖讓目標CPU主動push幾個進程過來,這個過程叫做active_load_balance。這里的“一定次數”也是跟調度域的層次有關的,越低層次,則“一定次數”的值越小,越容易觸發active_load_balance。
這里需要先解釋一下,為什么load_balance的過程中遷移進程會失敗呢?最繁忙run_queue中的進程,如果符合以下限制,則不能遷移:
1、進程的CPU親和力限制了它不能在當前CPU上運行;
2、進程正在目標CPU上運行(正在運行的進程顯然是不能直接遷移的);
(此外,如果進程在目標CPU上前一次運行的時間距離當前時間很小,那么該進程被cache的數據可能還有很多未被淘汰,則稱該進程的cache還是熱的。對於cache熱的進程,也盡量不要遷移它們。但是在滿足觸發active_load_balance的條件之前,還是會先試圖遷移它們。)
對於CPU親和力有限制的進程(限制1),即使active_load_balance被觸發,目標CPU也不能把它push過來。所以,實際上,觸發active_load_balance的目的是要嘗試把當時正在目標CPU上運行的那個進程弄過來(針對限制2)。
在每個CPU上都會運行一個遷移線程,active_load_balance要做的事情就是喚醒目標CPU上的遷移線程,讓它執行active_load_balance的回調函數。在這個回調函數中嘗試把原先因為正在運行而未能遷移的那個進程push過來。為什么load_balance的時候不能遷移,active_load_balance的回調函數中就可以了呢?因為這個回調函數是運行在目標CPU的遷移線程上的。一個CPU在同一時刻只能運行一個進程,既然這個遷移線程正在運行,那么期望被遷移的那個進程肯定不是正在被執行的,限制2被打破。
當然,在active_load_balance被觸發,到回調函數在目標CPU上被執行之間,目標CPU上的TASK_RUNNING狀態的進程可能發生一些變化,所以回調函數發起遷移的進程未必就只有之前因為限制2而未能被遷移的那一個,可能更多,也可能一個沒有。
