详解 GaussDB(DWS)的 CPU 资源隔离管控能力
本文分享自华为云社区《GaussDB(DWS)的CPU资源隔离管控能力【这次高斯不是数学家】》,作者:门前一棵葡萄树。
一、cgroup 概述
cgroup 全称 control group,是 linux 内核提供的用于对进程/线程使用的资源进行隔离、管控以及记录的组件。
相关概念:
任务(task):对应系统中的一个进程/线程;
控制组(control group):进行资源限制隔离的基本单位,一个任务加入到控制组任务列表(tasks)后即受控制组资源控制,支持在线将一个任务从一个控制组迁移到另外一个控制组。
层级(hierarchy):控制组是一种树形结构,一个父控制组可以有多个子控制组,一个子控制组只能属于一个父控制组。同属一个父控制组的子控制组间按照资源配置进行资源争抢、隔离,子控制组继承父控制组的资源配置。
子系统(subsytem):一个子系统对应一种资源的资源控制器,比如 CPU 子系统是控制 CPU 时间分配的控制器,CPUSET 子系统是控制 CPU 核分配的控制器。
cgroup 主要子系统介绍:
cpu 子系统:限制任务的 cpu 使用率;
cpuacct 子系统:统计 cgroup 中所有任务使用 cpu 的累积信息,单位 ns;
cpuset 子系统:限制任务能够使用的 cpu 核;
memory 子系统:限制任务的 memory 使用;
blkio 子系统:限制任务磁盘 IO;
这里我们着重介绍 GaussDB 应用到的 cgroup 子系统,涉及的子系统包括:cpu 子系统、cpuacct 子系统以及 cpuset 子系统。
1.1 cgroup 文件系统
VFS (Virtual File System) 虚拟文件系统是系统内核非常强大的一个功能,它把文件系统的具体实现细节隐藏起来,给用户态进程提供一个统一的文件系统 API 接口。cgroup 的接口操作也是基于 VFS 实现的,可以通过 mount 命令查看 cgroup 挂载信息,每个目录对应 cgroup 的一个子系统。
1.1.1 cpu & cpuacct 子系统
cpu 子系统和 cpuacct 子系统相辅相成,cpu 子系统限制 cgroup 使用的 cpu 时间,cpuacct 统计 cgroup 使用的 cpu 时间,同时 cpu 子系统与 cpuacct 子系统挂载路径一致,因此可以把这两个子系统放在一起讨论:
任务
tasks 记录着关联到该 cgroup 的任务(进程/线程)pid,只有加入 tasks 的任务才受 cgroup 控制。
cpu 子系统用于控制 cgroup 中所有任务可以使用的 cpu 时间,主要包含以下几个接口:
cpu.cfs_period_us 与 cpu.cfs_quota_us
cfs_period_us 与 cfs_quota_us 需要组合使用,cfs_period_us 用来配置进行 cpu 限制的单位时间周期,cfs_quota_us 用来配置在设置的时间周期内所能使用的 CPU 时间。二者单位都是微秒(us),cfs_period_us 的取值范围为 1 毫秒(ms)到 1 秒(s),cfs_quota_us 的取值大于 1ms 即可,如果 cfs_quota_us 的值为-1(默认值),表示不受 cpu 周期的限制。举例说明:
cpu.shares
shares 用来设置 cgroup 中任务 CPU 可用时间的相对比例,针对所有可用 cpu,默认值是 1024,在 cpu 出现满负载争抢时,各控制组按照 shares 设置的相对比例争抢 cpu。假如系统中有两个 cgroup,分别是 A 和 B,A 的 shares 值是 10000,B 的 shares 值是 20000,那么 A 和 B 出现 cpu 争抢时 A 将获得 10000/(10000+20000)=33.3%的 CPU 资源,而 B 将获得 66.7%的 CPU 资源。shares 作为一种共享配额的 cpu 管控方式,具备以下几个特点:
cpu 空闲情况下,shares 不起作用,只有在 cpu 出现满负载争抢时,各控制组任务才按照 shares 配置比例争抢 cpu
空闲 cpu 其他控制组可以使用,如果 A 没有使用到 33.3%的 cpu 时间,那么剩余的 cpu 时间将会被分配给 B,即 B 的 CPU 使用率可以超过 66.7%
如果添加了一个新的控制组 C,且它的 shares 值是 10000,那么 A 的配额比例变成 10000/(10000+20000+10000)=25%,B 的 cpu 配额比例变成 50%
由于 shares 是一个权重值,需要和其它控制组的权重值进行对比才能得到自己的配额比例,在一个负载多变的环境上,cgroup 数量和权重可能是多变的,这样给 cgroup 配置的配额比例,可能因为增加 cgroup 或修改其他 cgroup 权重导致该 cgroup 配额比例发生变化,无法精确控制 cpu 使用率。
cpu.stat
stat 包含以下三项统计结果
nr_periods: 表示经历了多少个 cpu.cfs_period_us 里面配置的时间周期
nr_throttled: 在上面的这些周期中,有多少次 cpu 受到了限制(即 cgroup 中的进程在指定的时间周期中用光了它的配额)
throttled_time: cgroup 中的进程被限制使用 CPU 持续了多长时间(纳秒)
cpu.rt_runtime_us & cpu.rt_period_us
rt_runtime_us 与 rt_period_us 组合使用可以对 cgroup 中的实时调度任务进行 cpu 时间限制,只可用于实时调度任务。rt_period_us 用来配置进行 cpu 限制的单位时间周期,设置每隔多久 cgroup 对 cpu 资源的存取进行重新分配,rt_runtime_us 用来配置在设置的时间周期内任务对 cpu 资源的最长连续访问时间,二者单位都是微秒(us)。
cpuacct(cpu accounting)子系统用于统计 cgroup 中任务所使用的 cpu 时间,主要包含以下接口:
cpuacct.usage
统计 cgroup 中所有任务使用的 cpu 时间,单位 ns,可以通过写入 0 值重置统计信息
cpuacct.usage_percpu
统计 cgroup 中所有任务在每个 cpu 核上使用的 cpu 时间,单位 ns
cpuacct.stat
统计 cgroup 中所有任务使用的用户态和系统态 cpu 时间,单位 USER_HZ,格式如下:
user:cgroup 中所有任务使用的用户态 cpu 时间
system:cgroup 中所有任务使用的内核态 cpu 时间
1.1.2 cpuset 子系统
cpuset 子系统可以为 cgroup 分配专属的 cpu 核和内存节点,cgroup 中所有任务只能运行在分配的 cpu 和内存节点上。GaussDB 仅应用了 cpuset 子系统的 cpu 控制能力,因此我们这里仅介绍 cpu 相关控制接口。
cpuset.cpus
设置 cgroup 中任务可以使用的 cpu,使用小横线‘-’设置连续 cpu,不连续 cpu 之间使用逗号‘,’分隔。例如:0-1,11-12,17 表示 cgroup 可以使用 cpu 0、1、11、12、17。
cpuset.cpu_exclusive
包含标签 0 和 1,可以控制其他 cpuset 及其父子 cpuset 是否可以共享该 cpuset 的 cpu,默认值为 0,cpu 不会专门分配给某个 cpuset。
cpuset.sched_load_balance
包含标签 0 和 1,设定内核是否可以在该 cpuset 的 cpu 上进行负载均衡,默认值 1,表示内核可以将超载 cpu 上的任务移动至低负载 cpu 上以平衡负载。
注意:如果父 cgroup 启用了负载均衡,则其所有子 cgroup 默认开启负载均衡,因此如果要禁用 cgroup 的负载均衡,则其所有上层 cgroup 都需要关闭负载均衡,同时需要考虑其他同层 cgroup 是否能够关闭负载均衡。
cpuset.sched_relax_domain_level
表示内核进行负载均衡的策略,如果禁用负载均衡则该值无意义。不同系统框架下,该值意义可能不同,以下为常用值的含义:
二、GaussDB 的 cpu 管控
GaussDB 支持两种 cpu 管控能力,基于 cpu.shares 的共享配额管控和基于 cpuset 的专属限额管控。在介绍 GaussDB 的 cpu 管控之前,我们首先介绍 GaussDB 中的 cgroup 结构。
2.1 GaussDB 的 cgroup 层级模型
GaussDB 的 cpu 管控需要考虑其他进程与 GaussDB 之间的 cpu 管控,GaussDB 内核后台线程与用户线程之间的 cpu 管控,用户之间的 cpu 管控。为了应对不同层级的 cpu 隔离管控需求,GaussDB 设计了基于 cgroup 层级特点的 cpu 分层隔离管控,GaussDB 的 cgroup 层级模型如下图所示:
以上 cgroup 均适配了 cpu 子系统和 cpuset 子系统,每一个 cgroup 都包含两个值:cpu.shares 和 cpuset.cpus。GaussDB 借助 cgroup 提供了三个维度的 cpu 隔离管控能力:
GaussDB 与其他进程之间的隔离管控
数据库常驻后台线程与作业线程的隔离管控
数据库用户之间的隔离管控
GaussDB 与其他进程之间的隔离管控
数据库集群的每个节点在 cgroup 的 cpu 子系统和 cpuset 子系统内均包含一个专属目录:“GaussDB:gaussdba”,作为 GaussDB 的主 cgroup 节点(目录),用于限制和记录 GaussDB 内所有线程使用的 cpu,GaussDB 进程内所有线程均直接或间接的受到该 cgroup 的限制。通过限制 GaussDB 内核使用的 cpu,可防止数据库系统对其他应用程序造成影响。
数据库常驻后台线程与作业线程的隔离管控
GaussDB 主 cgroup 节点之下包含两个 cgroup:Backend control 组和 Class control 组。Backend 控制组用于 GaussDB 常驻后台线程的 cpu 隔离管控,Class 控制组用于作业线程的 cpu 隔离管控。大部分后台常驻线程 cpu 资源占用较少,但 AutoVacuum 线程 cpu 资源占用可能较多,因此在 Backend 控制组单独提供 Vacuum 控制组用于 AutoVaccum 线程的 cpu 资源限制,而除 AutoVacuum 之外的其他后台常驻线程均受到 DefaultBackend 控制组的 cpu 资源限制。
数据库用户之间的隔离管控
通常情况下数据库系统会同时运行多种类型的作业,不同类型作业之间可能出现 cpu 资源争抢。GaussDB 资源管理为用户提供了双层的 cgroup 层级结构用于 cpu 隔离管控,用户可按需创建和修改 cgroup 配置实现不同类型作业之间的 cpu 资源限制。用户双层 cgroup 包含以下控制组:
UserClass 控制组:用户创建的父控制组,主要实现 cpu 资源的初步划分,如:不同的父控制组可以属于不同的部门/分公司
RemainWD 控制组:包含 UserClass 控制组创建 UserWD 控制组后剩余的 cpu 资源
Timeshare 控制组:创建 UserClass 控制组时默认创建的优先级控制组,包含四个优先级的控制组:Rush:High:Medium:Low,资源配比为:8:4:2:1
UserWD 控制组:用户创建的子控制组,在父控制组基础上实现 cpu 资源的细粒度划分,如:不同的子控制组可以属于同一部门下的不同类型作业
2.2 GaussDB 作业的 cpu 管控
数据库系统中运行多种类型作业出现 cpu 争取时,不同用户可能有不同的诉求,主要诉求如下:
实现 cpu 资源的充分利用,不在意单一类型作业的性能,主要关注 cpu 整体吞吐量
允许一定程度的 cpu 资源争抢和性能损耗,在 cpu 空闲情况下实现 cpu 资源充分利用,在 cpu 满负载情况下希望各类型按比例使用 cpu
部分作业对性能敏感,不在意 cpu 资源的浪费
对于第一种诉求,不建议进行用户之间的 cpu 隔离管控,不管控哪一种的 cpu 管控都或多或少的对 cpu 整体使用率产生影响;对于第二种诉求可以采用基于 cpu.shares 的共享配额管控方式,实现满负载 cpu 隔离管控前提下尽量提高 cpu 整体使用率;对于第三种诉求可以采用基于 cpuset.cpus 的专属限额管控方式,实现不同类型作业之间的 cpu 绝对隔离。下面对这两种作业 cpu 管控方式进行简要介绍,具体细节和使用中的疑问,我们后面文章再详细介绍,用兴趣的朋友也可以自己动手实验。
2.2.1 CPU 的共享配额管控
共享配额有两层含义:
共享:cpu 是所有控制组共享的,空闲的 cpu 资源其他控制组能够使用
配额:业务繁忙 cpu 满负载情况下,控制组之间按照配额比例进行 cpu 抢占
共享配额基于 cpu.shares 实现,通过上面 cgroup 的介绍我们可知这种管控方式只有在 cpu 满负载情况下生效,因此在 cpu 空闲情况下并不能保证控制组能够抢占到配额比例的 cpu 资源。cpu 空闲是不是可以理解为没有 cpu 资源争抢,控制组内任务可以任意使用 cpu,因此不会有性能影响呢?答案是错误的,虽然 cpu 平均使用率可能不高,但是某个特定时刻还是可能存在 cpu 资源争抢的。示例:
10 个 cpu 上运行 10 个作业,每个 cpu 上运行一个作业,这种情况下各作业在任意时刻请求 cpu 都可以瞬间得到响应,作业之间没有任何 cpu 资源的争抢;但是假如 10 个 cpu 上运行 20 个作业,因为作业不会一直占用 cpu,在某些时间可能等待 IO、网络等,因此 cpu 使用率可能并不高,此时 cpu 资源看似空闲,但是在某个时刻可能出现 2~N 作业同时请求一个 cpu 的情况出现,此时即会导致 cpu 资源争抢,影响作业性能。
通过测试验证,在 cpu 满负载情况下,控制组之间基本可以按照配额比例占用 cpu,实现 cpu 资源的配额管控。
2.2.2 CPU 的专属限额管控
专属限额有两层含义:
专属:cpu 是某个控制组专属的,空闲的 cpu 资源其他控制组不能使用
限额:只能使用限额配置的 cpu 资源,其他控制组空闲的 cpu 资源,也不能抢占
专属限额基于 cpuset.cpus 实现,通过合理的限额设置可以实现控制组之间 cpu 资源的绝对隔离,各控制组间任务互不影响。但是因为 cpu 的绝对隔离,因此在控制组空闲时就会导致 cpu 资源的极大浪费,因此限额设置不能太大。那从作业性能来看是不是限额越大越好呢?答案是不完全正确,示例:
假设 10 个作业运行在 10 个 cpu 上,cpu 平均使用率 5%左右;10 个作业运行在 5 个 cpu 上,cpu 平均使用率 10%左右。通过上面共享配额的性能分析我们可知:虽然 10 个作业运行在 5 个 cpu 上 cpu 使用率很低,看似空闲,但是相对 10 个作业运行在 10 个 cpu 上还是存在某种程度的 cpu 资源争抢的,因此 10 个作业运行在 10 个 cpu 上性能要好于运行在 5 个 cpu 上。那是不是越多越好呢?10 个作业运行在 20 个 cpu 上,在任意一个时刻,总会至少 10 个 cpu 是空闲的,因此理论上 10 个作业运行在 20 个 cpu 上并不会比运行在 10 个 cpu 上性能更好。
因此我们可以得知,对于并发为 N 的控制组,分配 cpus 小于 N 的情况下,cpu 越多作业性能越好;但是当分配 cpus 大于 N 的情况下,性能就不会有任何提升了。
2.2.3 共享配额与专属限额对比
cpu 共享配额和专属限额的管控方式各有优劣,共享配额能够实现 CPU 资源的充分利用,但是各控制组之间资源隔离不彻底,可能影响查询性能;专属限额的管控方式可以实现 cpu 资源的绝对隔离,但是在 cpu 资源空闲时会造成 cpu 资源的浪费。相对专属限额来说,共享配额拥有更高的 cpu 使用率和更高的整体作业吞吐量;相对共享配额来说,专属限额 cpu 隔离彻底,更满足性能敏感用户的使用诉求。
从上面 GaussDB 的 cgroup 层级结构我们得知,用户 cgroup 是包含父子两层控制的,那可不可以父控制组一层使用专属限额,而子控制组一层使用共享配额呢?答案是肯定的,另外同一层控制组也可以同时有使用专属限额和共享配额的控制存在。具体使用本文不做介绍,有兴趣的可以自己试验。
【这次高斯不是数学家】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/345260
华为伙伴暨开发者大会 2022 火热来袭,重磅内容不容错过!
【精彩活动】
勇往直前·做全能开发者→12 场技术直播前瞻,8 大技术宝典高能输出,还有代码密室、知识竞赛等多轮神秘任务等你来挑战。即刻闯关,开启终极大奖!点击踏上全能开发者晋级之路吧!
【技术专题】
未来已来,2022 技术探秘→华为各领域的前沿技术、重磅开源项目、创新的应用实践,站在智能世界的入口,探索未来如何照进现实,干货满满点击了解
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/3025e9db2fd28b2d47f3e7003】。文章转载请联系作者。
评论