栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

精准限制CPU:Cgroups

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

精准限制CPU:Cgroups

如何看待CPU资源?

由于进程和线程在Linux的CPU调度看来没啥区别,所以本文后续都会用进程这个名词来代表内核的调度对象,一般来讲也包括线程

如果要分配资源,我们必须先搞清楚这个资源是如何存在的,或者说是如何组织的。我想CPU大家都不陌生,我们都在系统中用过各种工具查看过CPU的使用率,比如说以下这个命令和它的输出:

mpstat -P ALL 1 1

根据显示内容我们知道,这个计算机有4个cpu核心,目前的cpu利用率几乎是0,就是说系统整体比较闲。

从这个例子大概可以看出,我们对cpu资源的评估一般有两个观察角度:核心个数 和百分比。

目前的计算机基本都是多核甚至多cpu系统,一个服务器上存在几个到几十个cpu核心的情况都很常见。所以,从这个角度看,cgroup应该提供一种手段,可以给进程们指定它们可以占用的cpu核心,以此来做到cpu计算资源的隔离。
百分比这个概念我们需要多解释一下:这个百分比究竟是怎么来的呢?难道每个cpu核心的计算能力就像一个带刻度表的水杯一样?一个进程要占用就会占用到它的一定刻度么?

当然不是!这个cpu的百分比是按时间比率计算的。基本思路是:一个CPU一般就只有两种状态,要么被占用,要么不被占用。当有多个进程要占用cpu的时候,那么操作系统在一个cpu核心上是进行分时处理的。比如说,我们把一秒钟分成1000份,那么每一份就是1毫秒,假设现在有5个进程都要用cpu,那么我们就让它们5个轮着使用,比如一人一毫秒,那么1秒过后,每个进程只占用了这个CPU的200ms,使用率为20%。整体cpu使用比率为100%。
同理,如果只有一个进程占用,而且它只用了300ms,那么在这一秒的尺度看来,cpu的占用时间是30%。于是显示出来的状态就是占用30%的CPU时间。

这就是内核是如何看待和分配计算资源的。当然实际情况要比这复杂的多,但是基本思路就是这样。Linux内核是通过CPU调度器CFS--完全公平调度器对CPU的时间进行调度的,由于本文的侧重点是cgroup而不是CFS,对这个题目感兴趣的同学可以到这里进一步学习。CFS是内核可以实现真对CPU资源隔离的核心手段,因此,理解清楚CFS对理解清楚CPU资源隔离会有很大的帮助。

如何隔离CPU资源?

根据CPU资源的组织形式,我们就可以理解cgroup是如何对CPU资源进行隔离的了。

无非也是两个思路,一个是分配核心进行隔离,另一个是分配CPU使用时间进行隔离。

Cgroups介绍

Cgroups是linux的重要组件之一,可以对进程或用户进行隔离和限制

Cgroups全称Control Groups,是Linux内核提供的物理资源隔离机制,通过这种机制,可以实现对Linux进程或者进程组的资源限制、隔离和统计功能。比如可以通过cgroup限制特定进程的资源使用,比如使用特定数目的cpu核数和特定大小的内存,如果资源超限的情况下,会被暂停或者杀掉。

cgroups核心概念

任务(task)
在cgroup中,任务就是一个进程。
控制组(control group)
cgroup的资源控制是以控制组的方式实现,控制组指明了资源的配额限制。进程可以加入到某个控制组,也可以迁移到另一个控制组。
层级(hierarchy)
控制组有层级关系,类似树的结构,子节点的控制组继承父控制组的属性(资源配额、限制等)。
子系统(subsystem)
一个子系统其实就是一种资源的控制器,比如memory子系统可以控制进程内存的使用。子系统需要加入到某个层级,然后该层级的所有控制组,均受到这个子系统的控制

cgroups进行CPU限制

我们的机器自带cgproups,可以使用命令验证
mount -t cgroup

cgroup暴露给用户的API为文件系统,所有对cgroup的操作均可以通过对文件的修改完成,cgroup API对应的路径为:/sys/fs/cgroup/,作为使用方,仅需要对文件系统中的内容进行编辑,即可达到配置对应的cgroup的要求。

创建cgroup
cd /sys/fs/cgroup/cpu
mkdir test


创建文件夹后,cgroup会自动在该文件夹下初始化出配置文件:

其中,需要关注的文件有3个,分别为:

cgroup的限制逻辑如下:

1 限制所有pid在tasks中的进程,
2 在 cpu.cfs_period_us 周期内,只能使用最多 cpu.cfs_quota_us 的cpu资源。
3 默认情况下,cpu.cfs_period_us的单位为微秒,默认值为100ms。cpu.cfs_quota_us的值为-1,暨不做限制。
4 例如: 限制在100ms中,只能使用30ms的cpu资源,暨限制cpu占用率为30%
echo 30000 > cpu.cfs_quota_us
5 启动测试程序,并添加pid到tasks文件中后,再观察CPU情况,可以清晰的看到被限制在了30%
echo pid(loglistener的进程号) > /sys/fs/cgroup/cpu/rocket/test

使用cgroups的go客户端

这是一个使用Golang封装的用来操作cgroups的工具包,支持创建、管理、检查和销毁cgroups。使用go提供的客户端,可以在服务器上提供一个守护进程,由守护进程接收请求后,进行cgroup管理。相关的核心代码如下:

package main

import (
	"flag"
	"github.com/containerd/cgroups"
	"github.com/opencontainers/runtime-spec/specs-go"
	"log"
	"os/exec"
	"strings"
	"time"
)

var kb = 1024
var mb = 1024 * kb

// main
// call some process and add this process into cgroup
func main() {
	var cgPath = flag.String("cgroup_path", "", "cg-path is cgroup path name")
	var period = flag.Uint64("cpu_period", 100000, "cpu limit value, default is 100% ")
	var quota = flag.Int64("cpu_quota", -1, "cpu limit value, default is 100% ")
	var memLimit = flag.Int("mem_limit", 100, "mem limit value, default is 100mb ")

	var cmd = flag.String("cmd", "", "your application cmd")
	var args = flag.String("args", "", "cmd args")

	flag.Parse()

	cpuLimit := float32(*quota) / float32(*period) * 100
	limit := int64(*memLimit * mb)

	log.Printf("cgroup_path: %s, cpu_quota: %v, cpu_period: %v,max (%v%%), mem_limit: %vm (%d), cmd: %s, args: %v n",
		*cgPath, *quota, *period, cpuLimit, *memLimit, limit, *cmd, *args)

	control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(*cgPath), &specs.LinuxResources{
		CPU: &specs.LinuxCPU{
			Quota:  quota,
			Period: period,
		},
		Memory: &specs.LinuxMemory{
			Limit: &limit,
		},
	})
	if err != nil {
		log.Fatal(err)
		return
	}
	defer control.Delete()

	pid := run(*cmd, strings.Split(*args, " ")...)
	log.Printf("run process done, pid: %v, add to cgroup taskn", pid)
	if err = control.AddTask(cgroups.Process{Pid: pid}); err != nil {
		log.Fatal(err)
		return
	}

	tasks, err := control.Tasks(cgroups.Freezer, false)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Current tasks: %v", tasks)

	time.Sleep(10 * time.Second)
}

// run cmd in background, and return pid
func run(cmd string, args ...string) int {
	log.Printf("[run], cmd:%s, args: %v", cmd, args)

	command := exec.Command(cmd, args...)
	err := command.Start()
	if err != nil {
		log.Fatalf("Start error, %v", err)
		return 0
	}
	for {
		if command.Process != nil {
			return command.Process.Pid
		}
		time.Sleep(1000 * time.Microsecond)
	}
}

go run main.go -cgroup_path test -cmd /root/pi/main -cpu_quota 300000 -cpu_period 1000000 


2022/08/08 12:21:23 cgroup_path: test, cpu_quota: 300000, cpu_period: 1000000,max (30.000002%), mem_limit: 100m (104857600), cmd: /root/pi/main, args:  
2022/08/08 12:21:23 [run], cmd:/root/pi/main, args: []
2022/08/08 12:21:23 run process done, pid: 11855, add to cgroup task
2022/08/08 12:21:23 Current tasks: [{freezer 11855 /sys/fs/cgroup/freezer/test/}]
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1040903.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号