三喵军团 發表於 2023-5-23 00:00:00

使用cgroups限制MongoDB的内存使用

<p>cgroups,其名称源自控制组群(control groups)的简写,是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。</p>
<p>这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词有许多不同的意义,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中去。自那以后,又添加了很多功能。</p>
<p>使​​​用​​​ cgroup,系​​​统​​​管​​​理​​​员​​​可​​​更​​​具​​​体​​​地​​​控​​​制​​​对​​​系​​​统​​​资​​​源​​​的​​​分​​​配​​​、​​​优​​​先​​​顺​​​序​​​、​​​拒​​​绝​​​、​​​管​​​理​​​和​​​监​​​控​​​。​​​可​​​更​​​好​​​地​​​根​​​据​​​任​​​务​​​和​​​用​​​户​​​分​​​配​​​硬​​​件​​​资​​​源​​​,提​​​高​​​总​​​体​​​效​​​率​​​。<br>
在实践中,系统管理员一般会利用cgroup做下面这些事:</p>
<ul>
<li>隔离一个进程组(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。</li>
<li>为这组进程 分配其足够使用的内存</li>
<li>为这组进程分配相应的网络带宽和磁盘存储限制</li>
<li>限制访问某些设备(通过设置设备的白名单)</li>
</ul>
<h2 id="cgroups相关概念">cgroups相关概念</h2>
<ol>
<li>任务(<strong>task</strong>)。在cgroups中,任务就是系统的一个进程。</li>
<li>控制组群(<strong>control group</strong>)。控制组群就是一组按照某种标准划分的进程。cgroups中的资源控制都是以控制组群为单位实现。一个进程可以加入到某个控制组群,也从一个进程组迁移到另一个控制组群。一个进程组的进程可以使用cgroups以控制组群为单位分配的资源,同时受到cgroups以控制组群为单位设定的限制。</li>
<li>层级(<strong>hierarchy</strong>)。控制组群可以组织成hierarchical的形式,既一颗控制组群树。控制组群树上的子节点控制组群是父节点控制组群的孩子,继承父控制组群的特定的属性。</li>
<li>子系统(<strong>subsystem</strong>)。一个子系统就是一个资源控制器,比如cpu子系统就是控制cpu时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制组群都受到这个子系统的控制。</li>
</ol>
<p>当前的cgroup有一下规则:<br>
1.每次在系统中创建新层级时,该系统中的所有任务都是那个层级的默认 cgroup(我们称之为 root cgroup ,此cgroup在创建层级时自动创建,后面在该层级中创建的cgroup都是此cgroup的后代)的初始成员。<br>
2.一个子系统最多只能附加到一个层级。 (一个层级不会附加两个同样的子系统)<br>
3.一个层级可以附加多个子系统<br>
4.一个任务可以是多个cgroup的成员,但是这些cgroup必须在不同的层级。<br>
5.系统中的进程(任务)创建子进程(任务)时,该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中,但开始时它总是继承其父任务的cgroup。</p>
<h2 id="cgroup子系统">cgroup子系统</h2>
<p>cgroups为每种可以控制的资源定义了一个子系统。典型的子系统介绍如下:</p>
<ol>
<li>
<strong>cpu</strong> 子系统,主要限制进程的 cpu 使用率。</li>
<li>
<strong>cpuacct</strong> 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。</li>
<li>
<strong>cpuset</strong> 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。</li>
<li>
<strong>memory</strong> 子系统,可以限制进程的 memory 使用量。</li>
<li>
<strong>blkio</strong> 子系统,可以限制进程的块设备 io。</li>
<li>
<strong>devices</strong> 子系统,可以控制进程能够访问某些设备。</li>
<li>
<strong>net_cls</strong> 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。</li>
<li>
<strong>freezer</strong> 子系统,可以挂起或者恢复 cgroups 中的进程。</li>
<li>
<strong>ns</strong> 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。</li>
</ol>
<h2 id="cgroups安装">cgroups安装</h2>
<p>如果系统还没有安装cgroups,可以通过下面的命令进行安装</p>
<pre class="brush:bash;toolbar:false">yum install libcgroup</pre><br><p>启动和查看服务状态:</p>
<pre class="brush:bash;toolbar:false">service cgconfig start
service cgconfig status</pre><br><p>Linux把cgroups实现成一个文件系统,各个子系统的挂载点配置在<code>/etc/cgconfig.conf</code>文件中:</p>
<pre class="brush:bash;toolbar:false">mount {
      cpuset= /cgroup/cpuset;
      cpu   = /cgroup/cpu;
      cpuacct = /cgroup/cpuacct;
      memory= /cgroup/memory;
      devices = /cgroup/devices;
      freezer = /cgroup/freezer;
      net_cls = /cgroup/net_cls;
      blkio   = /cgroup/blkio;
}</pre><br><p>或者也可以通过命令<code>lssubsys -m</code>或者<code>mount -t cgroup</code>挂载。</p>
<pre class="brush:bash;toolbar:false"># lssubsys -m            
cpuset /cgroup/cpuset
cpu /cgroup/cpu
cpuacct /cgroup/cpuacct
memory /cgroup/memory
devices /cgroup/devices
freezer /cgroup/freezer
net_cls /cgroup/net_cls
blkio /cgroup/blkio</pre><br><p>或者你单独挂载某几个子系统:</p>
<pre class="brush:bash;toolbar:false">mount -t cgroup -o remount,cpu,cpuset,memory cpu_and_mem /cgroup/cpu_and_mem</pre><br><h2 id="cgroups使用">cgroups使用</h2>
<p>挂载某一个 cgroups 子系统到挂载点之后,就可以通过在挂载点下面建立文件夹或者使用<code>cgcreate</code>命令的方法创建 cgroups 层级结构中的节点。比如通过命令<code>cgcreate -g cpu:test</code>就可以在 cpu 子系统下建立一个名为 test 的节点。结果如下所示:</p>
<pre class="brush:bash;toolbar:false"># cgcreate -g cpu:test
# ls /cgroup/cpu
cgroup.event_controlcpu.cfs_quota_us   cpu.shares         release_agent
cgroup.procs          cpu.rt_period_us   cpu.stat         tasks
cpu.cfs_period_us   cpu.rt_runtime_usnotify_on_releasetest</pre><br><p>然后可以通过写入需要的值到 test 下面的不同文件,来配置需要限制的资源。每个子系统下面都可以进行多种不同的配置,需要配置的参数各不相同,详细的参数设置需要参考 cgroups 手册。使用 <code>cgset</code> 命令也可以设置 cgroups 子系统的参数,格式为 <code>cgset -r parameter=value path_to_cgroup</code>。<br>
比如:<code>cgset -r cfs_quota_us=50000 test</code>限制进程组 test 使用50%的CPU。<br>
或者直接写文件:</p>
<pre class="brush:bash;toolbar:false">echo 50000 &gt; /cgroup/cpu/test/cpu.cfs_quota_us</pre><br><p>命令可以参考redhat的文档: Setting Parameters</p>
<p>当需要删除某一个 cgroups 节点的时候,可以使用 <code>cgdelete</code> 命令,比如要删除上述的 <em>test</em> 节点,可以使用 <code>cgdelete -r cpu:test</code>命令进行删除。</p>
<p>把进程加入到 cgroups 子节点也有多种方法,可以直接把 pid 写入到子节点下面的 task 文件中。也可以通过 <code>cgclassify</code> 添加进程,格式为 <code>cgclassify -g subsystems:path_to_cgroup pidlist</code>,也可以直接使用 <code>cgexec</code> 在某一个 cgroups 下启动进程,格式为<code>cgexec -g subsystems:path_to_cgroup command arguments</code>.</p>
<p>也可以在<code>/etc/cgconfig.conf</code>文件中定义group,格式如下:</p>
<pre class="brush:bash;toolbar:false">group &lt;name&gt; {
    [&lt;permissions&gt;]
    &lt;controller&gt; {
      &lt;param name&gt; = &lt;param value&gt;;
      …
    }
    …
}</pre><br><p>比如:</p>
<pre class="brush:bash;toolbar:false">mount {
                cpuset= /cgroup/cpuset;
                cpu   = /cgroup/cpu;
                cpuacct = /cgroup/cpuacct;
                memory= /cgroup/memory;
                devices = /cgroup/devices;
                freezer = /cgroup/freezer;
                net_cls = /cgroup/net_cls;
                blkio   = /cgroup/blkio;
}
   
   group mysql_g1 {   
       cpu {
               cpu.cfs_quota_us = 50000;
               cpu.cfs_period_us = 100000;
       }
       cpuset {   
               cpuset.cpus = "3";   
               cpuset.mems = "0";   
       }   
       cpuacct{
   
       }
       memory {   
               memory.limit_in_bytes=104857600;
               memory.swappiness=0;
               # memory.max_usage_in_bytes=104857600;
               # memory.oom_control=0;
       }   
       blkio{
            blkio.throttle.read_bps_device="8:0 524288";
            blkio.throttle.write_bps_device="8:0 524288";
       }   
   }</pre><br><p>还可以让一个服务Service启动的时候加入进程组,具体文档请参考: Starting_a_Service</p>
<p>Redhat的文档详细的介绍了cgroups的配置和使用方法,是很好的一个参考资料。</p>
<h2 id="实践,限制&lt;a href=" http: target="_blank">MongoDB的内存使用"&gt;实践,限制MongoDB的内存使用</h2>
<p>MongoDB是个吃内存的大户,它会尽可能的使用服务器的内存。在数据量巨大的时候,内存很快会被吃光,导致服务器上其它进程无法分配内存。<br>
我们可以使用cgroups来限制MongoDB的内存使用。实际上,在参考文档2中 Vadim Tkachenko 就介绍了他的实际方法。</p>
<p>配置有几个步骤:</p>
<ol>
<li>创建一个控制组群:<code>cgcreate -g memory:DBLimitedGroup</code>
</li>
<li>指定可用的最大内存16G: <code>echo 16G &gt; /sys/fs/cgroup/memory/DBLimitedGroup/memory.limit_in_bytes</code>
</li>
<li>将缓存页丢掉 (flush and drop): <code>sync; echo 3 &gt; /proc/sys/vm/drop_caches</code>
</li>
<li>将mongodb的进程加入控制组:<code>cgclassify -g memory:DBLimitedGroup</code>pid of mongod<code></code>
</li>
</ol>
<p>基本上就完成了任务,这样此MongoDB最多可以使用16G的内存。<br>
为了处理机器重启还得手工添加的问题,你可以按照上面的文档将Mongo服务加入到控制组中。</p>
<p>除此之外,作者还提到了 dirty cache flush的问题, 注意两个参数:<code>/proc/sys/vm/dirty_background_ratio</code>和<code>/proc/sys/vm/dirty_ratio</code>。</p>
<p>这里有一篇关于调整磁盘缓冲参数的介绍:<br><strong>1) /proc/sys/vm/dirty_ratio </strong><br>
这个参数控制文件系统的文件系统写缓冲区的大小,单位是百分比,表示系统内存的百分比,表示当写缓冲使用到系统内存多少的时候,开始向磁盘写出数据。增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,应该降低其数值,:<br><code>echo '1' &gt; /proc/sys/vm/dirty_ratio</code></p>
<p><strong>2) /proc/sys/vm/dirty_background_ratio </strong><br>
这个参数控制文件系统的pdflush进程,在何时刷新磁盘。单位是百分比,表示系统内存的百分比,意思是当写缓冲使用到系统内存多少的时候,pdflush开始向磁盘写出数据。增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,应该降低其数值,:</p>
<p><code>echo '1' &gt; /proc/sys/vm/dirty_background_ratio</code></p>
<p><strong>3) /proc/sys/vm/dirty_writeback_centisecs </strong><br>
这个参数控制内核的脏数据刷新进程pdflush的运行间隔。单位是 1/100 秒。缺省数值是500,也就是 5 秒。如果你的系统是持续地写入动作,那么实际上还是降低这个数值比较好,这样可以把尖峰的写操作削平成多次写操作。设置方法如下:</p>
<p><code>echo "100" &gt; /proc/sys/vm/dirty_writeback_centisecs</code><br>
如果你的系统是短期地尖峰式的写操作,并且写入数据不大(几十M/次)且内存有比较多富裕,那么应该增大此数值:</p>
<p><code>echo "1000" &gt; /proc/sys/vm/dirty_writeback_centisecs</code></p>
<p><strong>4) /proc/sys/vm/dirty_expire_centisecs </strong><br>
这个参数声明Linux内核写缓冲区里面的数据多“旧”了之后,pdflush进程就开始考虑写到磁盘中去。单位是 1/100秒。缺省是 30000,也就是 30 秒的数据就算旧了,将会刷新磁盘。对于特别重载的写操作来说,这个值适当缩小也是好的,但也不能缩小太多,因为缩小太多也会导致IO提高太快。</p>
<p><code>echo "100" &gt; /proc/sys/vm/dirty_expire_centisecs</code><br>
当然,如果你的系统内存比较大,并且写入模式是间歇式的,并且每次写入的数据不大(比如几十M),那么这个值还是大些的好。</p>
<p><strong>5) /proc/sys/vm/vfs_cache_pressure </strong><br>
该文件表示内核回收用于directory和inode cache内存的倾向;缺省值100表示内核将根据pagecache和swapcache,把directory和inode cache保持在一个合理的百分比;降低该值低于100,将导致内核倾向于保留directory和inode cache;增加该值超过100,将导致内核倾向于回收directory和inode cache</p>
<p>缺省设置:100</p>
<p><strong>6) /proc/sys/vm/min_free_kbytes </strong><br>
该文件表示强制Linux VM最低保留多少空闲内存(Kbytes)。<br>
缺省设置:724(512M物理内存)</p>
<p><strong>7) /proc/sys/vm/nr_pdflush_threads </strong><br>
该文件表示当前正在运行的pdflush进程数量,在I/O负载高的情况下,内核会自动增加更多的pdflush进程。<br>
缺省设置:2(只读)</p>
<p><strong>8) /proc/sys/vm/overcommit_memory </strong><br>
该文件指定了内核针对内存分配的策略,其值可以是0、1、2。<br>
0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。<br>
1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。<br>
2, 表示内核允许分配超过所有物理内存和交换空间总和的内存(参照overcommit_ratio)。</p>
<p>缺省设置:0</p>
<p><strong>9) /proc/sys/vm/overcommit_ratio </strong><br>
该文件表示,如果overcommit_memory=2,可以过载内存的百分比,通过以下公式来计算系统整体可用内存。<br>
系统可分配内存=交换空间+物理内存*overcommit_ratio/100</p>
<p><strong>10) /proc/sys/vm/page-cluster </strong><br>
该文件表示在写一次到swap区的时候写入的页面数量,0表示1页,1表示2页,2表示4页。<br>
缺省设置:3(2的3次方,8页)</p>
<p><strong>11) /proc/sys/vm/swapiness </strong><br>
该文件表示系统进行交换行为的程度,数值(0-100)越高,越可能发生磁盘交换。</p>
<h2 id="参考文档">参考文档</h2>
<ol>
<li>https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt</li>
<li>Using Cgroups to Limit MySQL and MongoDB memory usage</li>
<li>Red Hat Enterprise Linux 6 Resource Management Guide</li>
<li>cgroups介绍及安装配置使用详解</li>
<li>美团 Linux资源管理之cgroups简介</li>
<li>Docker基础技术:Linux CGroup</li>
<li>linux集群之--设置磁盘缓冲参数</li>
<li>http://centaurea.io/blog?name=mongodb-memory-allocation-and-cache-management</li>
</ol>
頁: [1]
查看完整版本: 使用cgroups限制MongoDB的内存使用