在centos7的/sys/fs/cgroup下面可以看到與cpu相關的有cpu,cpuacct和cpuset 3個subsystem。cpu用於對cpu使用率的划分;cpuset用於設置cpu的親和性等,主要用於numa架構的os;cpuacct記錄了cpu的部分信息。對cpu資源的設置可以從2個維度考察:cpu使用百分比和cpu核數目。前者使用cpu subsystem進行配置,后者使用cpuset subsystem進程配置。首先看cpu subsystem的用法
cpu subsystem
cgroup使用如下2種方式來對cpu進行調度
- 完全公平調度程序(CFS):按照比例進行cpu分配調度,具體實現可以參考CFS
- 實時調度程序(RT):與CFS類似,用於限制實時任務對cpu的獲取,一般用不到。(注:Linux的進程分普通進程和實時進程,實時進程比普通進程的優先級高,由於其在進程死亡之前始終是活動進程,故占用cpu資源大)
cpu subsystem主要涉及5接口:cpu.cfs_period_us,cpu.cfs_quota_us,cpu.shares,cpu.rt_period_us,cpu.rt_runtime_us
cfs_quota_us為-1,表示使用的CPU不受cgroup限制。cfs_quota_us的最小值為1ms(1000),最大值為1s,參見CFS Bandwidth Control
cpu.cfs_period_us用於設置cpu帶寬(bandwidth),單位為微秒us。cpu.cfs_quota_us設置cpu.cfs_period_us周期內cgroup可使用的cpu。多核場景下,如配置cpu.cfs_period_us=10000,而cfs_quota_us=20000,表示該cgroup可以完全使用2個cpu。較大的cfs_period_us可以提高吞吐量(可以為CPU密集型任務提供更多運行時間)。cfs_period_us表示一個CPU的寬度,系統上可用的總的CPU寬度為:(cpus on the host) * (cpu.cfs_period_us)。當出現如下條件時,cpu.stat中的nr_throttled統計會+1。
- 周期(period)內使用的CPU達到quota的CPU
- 父cgroup中使用的CPU達到其quota的CPU
首先在/sys/fs/cgroup/cpu下面新建一個cgroup,將cpu周期設置為100000,cgroup在單個周期中占用時長為50000,即單個cpu的50%
# cat cpu.cfs_period_us 100000 # echo 50000 > cpu.cfs_quota_us # bash # cat tasks # echo $$ 40768 # echo $$ > cgroup.procs
# while true; do a=a+1;done
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 40768 root 20 0 116872 3644 1808 R 50.3 0.4 0:19.75 bash
下例中將cpu周期設置為100000,cgroup在單個周期中占用時長為300000,即該cgroup可以完全占用3個cpu(當前環境4 cpu)。
啟動一個bash執行while true; do a=a+1;done並將該進程加入到cgroup.procs,使用top命令可以看到1個cpu使用率已經達到100%
top - 13:20:06 up 19:24, 7 users, load average: 3.21, 2.03, 0.95 Tasks: 252 total, 3 running, 249 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.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 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75412 free, 547276 used, 373208 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138856 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41416 root 20 0 116872 3644 1808 R 99.7 0.4 5:41.19 bash
(新shell中)再啟動一個bash執行while true; do a=a+1;done,將該進程加入到cgroup.procs,使用top命令可以看到2個cpu使用率已經達到100%
top - 13:22:51 up 19:27, 7 users, load average: 1.42, 1.65, 0.98 Tasks: 252 total, 3 running, 249 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75124 free, 547544 used, 373228 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138252 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41416 root 20 0 116872 3644 1808 R 100.0 0.4 8:26.67 bash 41528 root 20 0 116872 3652 1808 R 100.0 0.4 4:22.60 bash
(新shell中)再啟動一個bash執行while true; do a=a+1;done,將該進程加入到cgroup.procs,使用top命令可以看到3個cpu使用率已經達到100%
top - 13:25:58 up 19:30, 7 users, load average: 2.28, 1.88, 1.18 Tasks: 251 total, 4 running, 247 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75412 free, 547584 used, 372900 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138228 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41528 root 20 0 116872 3652 1808 R 100.0 0.4 7:29.10 bash 41416 root 20 0 116872 3644 1808 R 99.7 0.4 11:33.12 bash 41593 root 20 0 116784 3368 1648 R 99.7 0.3 2:30.04 bash
(新shell中)再啟動一個bash執行while true; do a=a+1;done,將該進程加入到cgroup.procs,此時有4個進程同時消耗cpu,但總體消耗限制在3個cpu,如下圖中,每個bash消耗的cpu約75%
top - 13:26:49 up 19:31, 7 users, load average: 2.95, 2.12, 1.30 Tasks: 251 total, 5 running, 246 sleeping, 0 stopped, 0 zombie %Cpu0 : 74.8 us, 0.0 sy, 0.0 ni, 25.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 74.8 us, 0.0 sy, 0.0 ni, 25.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 74.2 us, 0.0 sy, 0.0 ni, 25.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 75.7 us, 0.0 sy, 0.0 ni, 24.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75536 free, 547460 used, 372900 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138352 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41593 root 20 0 116784 3368 1648 R 75.7 0.3 3:16.76 bash 41528 root 20 0 116872 3652 1808 R 74.8 0.4 8:15.67 bash 41416 root 20 0 116872 3644 1808 R 74.1 0.4 12:19.74 bash 41654 root 20 0 116784 3368 1648 R 74.1 0.3 1:41.05 bash
cpu.cfs_quota_us和cpu.cfs_period_us以絕對比例限制cgroup的cpu,而cpu.shares以相對比例限制cgroup的cpu。
在/sys/fs/cgroup/cpu/下創建2個cgroup:test1和test2,設置test1的cpu.shares=50,test2的cpu.shares=200,則意味着test1在cpu競爭下最多可以使用所有cpu的20%,而test2在cpu競爭下最多可以使用所有cpu的80%(不考慮系統基本進程占用)。為方便驗證,將系統的cpu設置為1個。創建2個bash進程分別加入2個cgroup后執行while true; do a=a+1;done
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9683 root 20 0 116872 3644 1808 R 80.0 0.4 0:16.77 bash 9629 root 20 0 116872 3644 1808 R 20.0 0.4 0:09.73 bash
使用cpu.shares需要注意的是,對cpu的相對比例是在cpu競爭的條件下,如果一個cgroup使用的相對比例是50%,但實際僅使用了10%,那么多余的cpu會被回收,給其他cgroup使用,參見CPU
當一個 cgroup 中的任務處於閑置狀態且不使用任何 CPU 時間時,剩余的時間會被收集到未使用的 CPU 循環全局池中。其它 cgroup 可以從這個池中借用 CPU 循環
下例中test1 cgroup設定50,test2 cgroup設定200,但test1中運行的進程非常消耗cpu,而test2中運行的進程僅使用很小一部分cpu,且sleep操作會導致其進程進入sleep狀態
Test1 cgroup # echo $$ 9629 [root@ test1]# cat cpu.shares 50 [root@ test1]# while true; do a=a+1;done Test2 cgroup # echo $$ 9683 [root@ test2]# cat cpu.shares 200 [root@ test2]# while true; do sleep 1 ;done
查看cpu占用,可以看到test1中的進程占用了99.3%的cpu,而其相對比例為20%
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9629 root 20 0 116872 3660 1808 R 99.3 0.4 9:32.48 bash
cpuset subsystem
cpuset主要是為了NUMA(非均勻訪問存儲模型)使用的,NUMA技術將CPU划分成不同的組(Node),每個Node由多個CPU組成,並且有獨立的本地內存、I/O等資源(硬件上保證)。可以使用numactl查看當前系統的node清空,如下面表示系統只有一個node,含cpu 0-3,內存大小約1G
# numactl -H available: 1 nodes (0) node 0 cpus: 0 1 2 3 node 0 size: 1023 MB node 0 free: 70 MB node distances: node 0 0: 10
可以使用dmesg | grep -i numa命令查看當前系統是否開啟了numa下·
numa的基本架構如下,當cpu訪問直接attach的內存時(local access)時會有較大效率,而訪問其他cpu attach的內存(remote access)會導致效率下降。

Numa內存分配策略有一下四種,一般采用默認方式
- 缺省default:總是在本地節點分配(當前進程運行的節點上)。
- 綁定bind:強制分配到指定節點上。
- 交叉interleavel:在所有節點或者指定節點上交叉分配內存。
- 優先preferred:在指定節點上分配,失敗則在其他節點上分配。
numa場景下可能會出現一個性能問題,NUMA架構的CPU,The MySQL “swap insanity” problem and the effects of the NUMA architecture和A brief update on NUMA and MySQL。發生性能的主要原因是因為more策略下可能會發生swap,即總是在本地節點分配內存,當本地內存不足時會發生swap,可以嘗試使用如下方式進行
- 設置numa interleave=all,意味着整個進程的內存是均勻分布在所有的node之上,進程可以以最快的方式訪問本地內存
- 使用mlock方式申請內存,這樣這段內存不會使用swap
- 使用mmap的MAP_POPULATE,預先分配匿名頁,后續訪問此內存時不會發生缺頁
- 調節系統的vm.swappiness,對於數據庫應用服務器,設置為0,可以提高物理內存的使用率,進而提高數據庫服務的響應性能
默認方式下,進程總是使用本地節點進程內存分配,可以使用numastat查看內存分配情況
# numastat node0 numa_hit 6711656 numa_miss 0 numa_foreign 0 interleave_hit 19532 local_node 6711656 other_node 0
cpuset調用sched_setaffinity來設置進程的cpu親和性,調用mbind和set_mempolicy來設置內存的親和性。可以通過查看/proc/$pid/status查看當前進程cpu和mem的親和性。cpuset使用中應該遵循以下3點
- 子cpuset的cpu和memory node必須是父cgoup的子集
- 除非父cgroup標記了exclusive,否則子cgoup無法標記該flag
- 如果cgroup的cpu或memory標記了exclusive,那么該cgroup的cpu不能與兄弟cgroup有重合,且父子之間必須重合(參見第一條)
如下例中,在/sys/fs/cgroup/cpuset中創建2個cgroup,按照如下步驟,可以看出,當test1和test2有重合時,設置cpuset失敗
# rmdir test1 [root@ cpuset]# mkdir test1 [root@ cpuset]# mkdir test2 [root@ cpuset]# echo 1 > test1/cpuset.cpu_exclusive [root@ cpuset]# echo 1 > test2/cpuset.cpu_exclusive [root@ cpuset]# echo 0,1 > test1/cpuset.cpus [root@ cpuset]# echo 1,2 > test2/cpuset.cpus -bash: echo: write error: Invalid argument [root@ cpuset]# echo 2 > test2/cpuset.cpus
cpuset.cpu_exclusive:包含標簽(0 或者 1),它可以指定:其它 cpuset 及其父、子 cpuset 是否可共享該 cpuset 的特定 CPU。默認情況下(0),CPU 不會專門分配給某個 cpuset 。
上面介紹了設置該標志后兄弟cpuset之間的cpuset.cpus不能有重合,但父子cpuset之間是必須重合的。cpu_exclusive標記並不能實現完全的cpu隔離(不隸屬於cgroup管轄的進程默認擁有所有的cpu權限),如下例中啟動了6個消耗cpu的bash進程,僅對其中一個bash進程進行了cpuset的exclusive,可以看到exclusive並不能保證cpu的隔離,只用於保證不於其他兄弟cpuset定義的cpus重疊。核隔離可以使用內核啟動參數isolcpus,隔離的cpu不會對其進行負載均衡操作。
Tasks: 243 total, 7 running, 236 sleeping, 0 stopped, 0 zombie %Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 68208 free, 654032 used, 273656 buff/cache KiB Swap: 2097148 total, 1928188 free, 168960 used. 92388 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 74809 root 20 0 116652 3300 1656 R 86.0 0.3 1:27.86 bash 74753 root 20 0 116652 3300 1656 R 66.4 0.3 1:57.41 bash 74890 root 20 0 116652 3304 1656 R 66.4 0.3 0:11.07 bash 74081 root 20 0 116740 3572 1808 R 66.1 0.4 3:23.12 bash 70206 root 20 0 116784 3120 1376 R 62.5 0.3 1:38.89 bash 72544 root 20 0 116784 3556 1800 R 52.2 0.4 1:57.57 bash
cpuset.memory_spread_page用於設定文件系統緩沖是否應在該 cpuset 的內存節點中均勻分布,cpuset.memory_spread_slab用於設定slab緩沖(如inode和dentries)是否應在該 cpuset 的內存節點中均勻分布,默認否。該策略在將(大的)數據文件分布到多個node時可以提升性能(平均分布)。
cpuset.sched_load_balance和cpuset.sched_relax_domain_level與cpu負載均衡有關。linux使用sched domains(調度域)為單位進行負載均衡。當sched_load_balance設置為enable時,會在該cpuset中的cpu上進行負載均衡,否則不會在該cpuset中的cpu上進行負載均衡(不同cpuset中重疊的cpu上可能也會有負載均衡)。當root cpuset的sched_load_balance為enable時,會在所有的cpu上進行負載均衡,此時會忽略所有子cpuset中對該值的設置,因此只有在root cpuset disable之后,子cpuset才能生效。cpu負載均衡會影響系統性能,在以下兩種情況下可以不需要該功能:
- 大型系統中存在很多cpu,如果對單獨進程分配了獨立的cpu,此時無需使用cpu負載均衡
- 實時系統上需要減少cpu的損耗,此時可以不適用負載均衡
cpuset.sched_relax_domain_level表示 kernel 應嘗試平衡負載的 CPU 寬度范圍,僅當cpuset.sched_load_balance enable時生效。一般無需改動。
cpuset.memory_migrate包含一個標簽(0 或者 1),用來指定當 cpuset.mems 的值更改時,是否應該將內存中的頁遷移到新節點。
總結:
使用cpu subsystem可以在cpu時間上限制進程,而使用cpuset可以在cpu/mem number上限制進程。但如果cpu和cpuset不匹配時應該如何處理?如下例中,在cpuset中限制該cgroup中的進程只能運行在2號核上,但在cpu中該cgroup的進程最多可以使用2個核
# mkdir cpuset/cpusettest # mkdir cpu/cputest # cd cpuset/cpusettest # echo 0 > cpuset.mems # echo 2 > cpuset.cpus # cd cpu/cputest # echo 1000 > cpu.cfs_period_us # echo 2000 > cpu.cfs_quota_us
啟動3個bash執行while true; do a=a+1;done,並將其pid加入到cpu和cpuset的cgroup.procs中,觀察top命令可以看到3個bash進程僅占用了2號核,每個cpu占用率都約等於33%。由此可知,cpu中規定了進程可以使用的cpu的上限,但並不一定能達到上限
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.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 :100.0 us, 0.0 sy, 0.0 ni, 0.0 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 KiB Mem : 995896 total, 68408 free, 647088 used, 280400 buff/cache KiB Swap: 2097148 total, 1928444 free, 168704 used. 92768 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 72544 root 20 0 116784 3556 1800 R 33.9 0.4 4:19.03 bash 70206 root 20 0 116784 3120 1376 R 33.2 0.3 3:58.17 bash 74753 root 20 0 116652 3300 1656 R 33.2 0.3 5:05.08 bash
TIPS:
- 在設置cpuset時必須首先設置cpuset.cpus和cpuset.mems,否則可能出現"No space left on device"的錯誤
- 可以使用docker inspect DOCKERID來查看該容器掛載的cgroup路徑
參考:
