进程线程(五) groups和CPU资源分群分配

  • 基于cpu cgroups进行CPU资源分配

  • Linux的sched_autogroup(全新内容)

  • 基于cpuset cgroups进行进程CPU绑定(全新内容)

  • Docker和cgroups

  • Systemd和cgroups(全新内容)

  • Android对cgroups的利用

像服务器的场景,云计算的场景,手机的场景,对 cgroup 的使用真的很广泛,首先说下 cgroup 出现的原因。

1、cgroup 出现的原因

假设一个场景,有三个人 ABC 分别通过 SSH 连接到 一个 16 核 的编译服务器,分别编译 Android ,  我们知道 进程调度的单位是 线程,如果这三个人都开16个线程去编译,那么ABC这三个人 分别将获得 1/3 的CPU资源 (普通进程,CFS完全公平调度),48个线程都是CPU消耗型。

进程线程(五) groups和CPU资源分群分配

 但是 B 这个人 比较懂线程调度,他就在编译的时候,开32个线程,那么他就会获得更多的CPU资源。这时候想获得更多的CPU资源 就靠 “线程开得多” ,如果像B这样的人比较多的话,就会造成一个比较恶劣的结果,就是 线程之间调度的开销就会特别大,反而大家编译的就都很慢了,这样机会影响整体的效率和性能。

进程线程(五) groups和CPU资源分群分配

 基于这样的一个情况,linux 就做了一个 group 群的的概念。

进程线程(五) groups和CPU资源分群分配

 群和群之间,先按照 CFS 完全公平调度,然后 群里的线程 再按照 CFS 调度,这样就能避免之前的情况,A和 B 两个群的默认权重都是一样的,比如都是1024 ,那么 A B 两个群 就会平等的瓜分CPU资源,不管A群开了 1000个线程,还是B群开了10个线程。

演示一个程序,创建多个线程,每个线程都是一个死循环

#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

static void *thread_fun(void *param)
{
	printf("thread pid:%d, pthread_self:%lu\n", getpid(),pthread_self());
	while(1);
}

int main(int argc, char *argv[])
{
	int n = atoi(argv[1]);
	pthread_t tid[n];
	int i;

	printf("main pid:%d  tid:pthread_self:%lu\n", getpid(),pthread_self());
	
	for(i = 0; i < n; i++)
		pthread_create(&tid[i], NULL, thread_fun, NULL);

	for(i = 0; i < n; i++)
		pthread_join(tid[i], NULL);

	return 0;
}

 由于现在的 linux 已经默认开启了 自动分群的功能,测试的时候需要先关闭。

进程线程(五) groups和CPU资源分群分配

 启动 a.out 4  开启4个线程,应为是 4核的CPU ,所以CPU的使用率在 400%左右。进程线程(五) groups和CPU资源分群分配

 此时 再启动 a.out 8  再次开启8个线程,此时可以看到 开了 8个线程的CPU资源是 4个线程的2倍。

进程线程(五) groups和CPU资源分群分配

 现在我们引入 群的概念,分别将两个程序 加入两个群A  B 中 ,再来观察,两个 a.out 的 pid 分别是 3416 3411 

进程线程(五) groups和CPU资源分群分配

 之后就可看到,两个 a.out 的 CPU 利用率就差不多一样了,都是 200%左右,这就是群间的CFS调度。

进程线程(五) groups和CPU资源分群分配

 此时可以调整 群的权重 来 设置 CPU的利用率,比如 想让群A和B的CPU 资源比例为 1:2 , 那么就可以将 群A的 权重调整为 512 

sudo sh -c 'echo 512 > /sys/fs/cgroup/cpu/A/cpu.shares'

进程线程(五) groups和CPU资源分群分配

 

 还有两个比较重要的参数,可以配置 CPU资源的 限额进程线程(五) groups和CPU资源分群分配

 

 默认情况  cpu.cfs_period_us =  100000 (100ms)  

 cpu.cfs_quota_us = -1  ( -1 最大的正整数,就是不限制的意思)

进程线程(五) groups和CPU资源分群分配

现在就可以调整 cfs_quota_us 来 调整 CPU的资源配置,比如给 群 A 配置为 30% , 给群B 配置成 50% 

进程线程(五) groups和CPU资源分群分配

 group 与 group 之间  除了 可以 靠 权重 cpu.shares PK以外,还可以靠  cpu.cfs_quota_us 来限制 CPU的资源配额,需要注意的地方是 cfs_quota_us 是可以 大于 cfs_period_us 的,因为 linux里面,计算 cpu 的占用率 是以 单核为单位的,两核的cpu , 如果开了2个线程,cpu就可以达到 200% ,

进程线程(五) groups和CPU资源分群分配

 上图 在一个 4核 CPU上面,将 群B的cfs_quota_us 配置 为 500000(500ms) ,设置最大限额为 500% , 但是 群A 的限额是 30%, A 和 B 两群是CFS调度,4核CPU最大400%,所以 CPU的配额如上图所示。

 这服务器领域,谁付的钱多,我给你的CPU资源就越多,就是通过这个原理来调整的。

2 、Linus 祖师爷 都称赞的 自动分群补丁

 上面的实验,虽然说 分群是能解决问题的,但是 每次都来分群是很麻烦的,这时候不得不说,

在linux 发展历史上 一个特别小的补丁,只有200多行,但是获得了 Linus 祖师爷的赞美,

 进程线程(五) groups和CPU资源分群分配

这个补丁做了什么事情呢?

每开一个 terminal ,或者 开一个 SSH terminal ,都会是一个新的 session ,  都会有一个 session id , 这个 session id 是和 bash id 是相同的。每开一个 session 都会自动分群,自动创建 一个 group, 这个事情 不需要你人为的来控制。 之前的测试会成功,因为是 我们将 自动分群给关闭了。

进程线程(五) groups和CPU资源分群分配

开启自动分群后,有下面几个概念

  • echo $$ 可以查看 当前的 bash shell id 
  • ps -C a.out o pid,ppid,pgid,sid,comm 可以查看进程的 session id
  • session id = bash shell id 
  • 同一个shell 或者 session 里的进程,都在 同一个 群, 群内 进程 CFS调度,谁的线程多,谁的资源多。
  • 不同 shell 的进程,在不同的群,默认情况权重一样,CFS 公平调度

进程线程(五) groups和CPU资源分群分配

3、 docker 和 cgroup 

 docker 在 启动一个容器的时候,会自动进入一个群里面,

进程线程(五) groups和CPU资源分群分配

 可以看到, 启动一个 docker 容器后, 就会在 /sys/fs/cgroup/cpu/docker/ 下根据 容器id 创建一个 group , 这个group 中,可以配置权重,可以配置 cpu的资源限制进程线程(五) groups和CPU资源分群分配

 下图中,进程 5559 是在容器里跑的一个进程,在主机ps命令可以看到,cat /proc/5559/cgroup 可以看到,这个进程的 cpu group , memory group , blkio group ,这也是 linux 铁三角的内容,docker 都对这些资源都体现到了

进程线程(五) groups和CPU资源分群分配

4、systemd 是怎么使用 cgroup 的

进程线程(五) groups和CPU资源分群分配

 

 slice 只对应一种目录结构,不对应任何进程,对应进程是 scope 和 service , service的进程是靠 配置文件启动的,而 scope 的进程是外部的进程,后来又被 systemd接管了,就这样组成了一个树形关系

 systemd-cgls 

命令可以查看进程线程(五) groups和CPU资源分群分配

 进程线程(五) groups和CPU资源分群分配

 service 是 怎么和 cgroup 是怎么对应起来的呢?

进程线程(五) groups和CPU资源分群分配

 默认情况下,如果 service 文件 没有配置 cgroup 资源,那么这个service 对应的进程,都跟随

/sys/fs/cgroup/cpu  或者 /sys/fs/cgroup/memory 或者其他group 根目录下的默认配置,就不会有新的group建立。 如果在 service 文件中配置了cpu group的资源,就会 在 /sys/fs/cgroup/cpu/a.slice/b.service 建立一个新的 group ,遵循新的资源配置。

 一般来说 启动一个service 要使用 配置文件来启动,但是也是可以使用 一个命令 systemd-run 来启动。--unit 是进程的集合,就叫test,slice 就是一种目录结构

 sudo systemd-run --unit=test --slice=test ./a.out 4

进程线程(五) groups和CPU资源分群分配

使用 systemd-cgls 命令查看 ,已经按照 service 启动了

进程线程(五) groups和CPU资源分群分配

 top 命令查看,a.out 占用的cpu是 很大的,接近 400% , 这是因为 在启动 test service的时候,并没有对 cgroup 的资源进行限制,所以Linux 并不会 在 /sys/fs/cgroup/cpu/ 目录下新建一个 service的 group 。

进程线程(五) groups和CPU资源分群分配

 同样的道理,使用 systemd-cgtop 命令 可以查看以 group 为单位的资源消耗,在这里根本找不到test.service, 只能看到 / group的消耗是最大的。

进程线程(五) groups和CPU资源分群分配

 现在 停止这个 service ,重新启动 test.service ,进行 cpu 资源的限制 

sudo systemctl stop test
sudo systemd-run -p CPUQuota=25% -p CPUShares=600 -p MemoryLimit=500M --unit=test --slice=test ./a.out 4

进程线程(五) groups和CPU资源分群分配

 此时  systemd-cgtop 命令 就可以查看到 test.service 的组啦,而且 cpu限制到 25%

进程线程(五) groups和CPU资源分群分配

 也可以使用 systemctl  来修改 一个 service 的资源

systemctl set-property --runtime test.service CPUShares=500 MemoryLimit=100M

进程线程(五) groups和CPU资源分群分配

 那一般在 service 配置文件中怎么配置呢?可以按照下面的方法来。一般情况下都是按照下面的方式来设置的。按照配置文件启动的称之为 persistent service ,临时启动的service ,称之为transient  service。

进程线程(五) groups和CPU资源分群分配

 现在的这个时代, systemd 已经是一统天下了,在 Ubuntu 等很多发行版都是使用 systemd的。

 所以在用户态编程,还是要多学习 systemd 的。

6、cpuset 限制 cpu 和 mem 

在 Linux 中 可以多使用 apropos 命令根据字符串来查看 API ,下面就是一个查看 多核CPU 负载均衡API的例子:

进程线程(五) groups和CPU资源分群分配

 之前 设置 多核负载均衡的方法,一种是 线程API 设置 affinity , 另外一种是 taskset 命令来设置affinity ,这当然都是在没有 cgroup 的时代采用的方法,现在虽然也都能用。

当 cgroup 出现以后,其实有一种更好的方法来做。cpuset 也是一种 cgroup

进程线程(五) groups和CPU资源分群分配

cpuset 在 NUMA 的系统架构中,使用的比较多 。

SMP的系统架构,是多个CPU 共享一片 memory 。

NUMA的系统架构,多个CPU ,有多片 memory。 

进程线程(五) groups和CPU资源分群分配

NUMA系统架构的出现,是由于 SMP 架构当 CPU核数特别多的时候,比如1000个core ,但是只有一片 memory ,因为cpu 是很快的,比memory 要快很多,这样 1000个core去访问 同一片 memory的时候,memory的 bandwidth 就拖累了CPU的性能。所以NUMA架构出现了。

进程线程(五) groups和CPU资源分群分配

 cpuset 可以将 cpu 的 cores  和 一些 memory 节点 都可以 拉到一个 group 中,然后 也可以将一个进程,或者一组进程 放到这个 group 中,共享CPU和MEM资源。

比如在我的 SMP 4核 1片 memory 机器上 ,这个两个值是可以设置的。

进程线程(五) groups和CPU资源分群分配

 下面在 SMP 架构的 4核 上跑一个例子,创建一个 cpuset A , 将 0-1两个核加进去,因为就一个memory节点,就把memory 节点0 加进去,把a.out 4 加进去。top 命令可以看到 只在前两个核上跑,而且cpu利用率在200%

进程线程(五) groups和CPU资源分群分配

 cpuset 的好处就在于,不仅可以控制 cpu 核,还可以控制内存,这样对于NUMA的,数据访问延迟,就有很大的作用。

上一篇:SQL-labs在docker里创建失败2021-10-03


下一篇:为什么Go服务容器化之后延迟变高