浅析Linux下进程的调度策略与优先级

浅析Linux下进程的调度策略与优先级,第1张

在 Linux 中,线程是由进程来实现的,可以认为线程就是一个轻量级的进程,因此,线程调度是按照进程调度的方式来进行的。这样设计,线程调度流程可以直接复用进程调度流程,没必要再设计一个进程内的线程调度器了。

在 Linux 中,进程调度器是基于进程的调度策略与调度优先级来决定调度哪个进程运行。

调度策略主要包括:

调度优先级的范围是 0~99,数值越大,表示优先级越高。

其中,SCHED_OTHER、SCHED_IDLE、SCHED_BACH 为非实时调度策略,其调度优先级为 0。而 SCHED_FIFO、SCHED_RR 是实时调度策略,其调度优先级范围为 1~99。

实时调度策略的进程总是比非实时调度策略的进程优先级高。

在 Linux 内部实现中,调度器会为每个可能的调度优先级维护一个可运行的进程列表,以最高优先级列表头部的进程作为下一次调度的进程,所有的调度都是抢占式的,如果一个具有更高调度优先级的进程转换为可运行状态,那么当前运行的进程将被强制进入其等待的队列中。

SCHED_OTHER

该调度策略是默认的 Linux 分时调度策略,该调度策略为非实时的,其调度优先级总是为 0。

对于该调度策略类型的进程,调度器是基于动态优先级来调度的。动态优先级跟属性 nice 有关,nice 的值会随着进程的运行时间而动态改变,以确保所有具有 SCHED_OTHER 策略的进程公平地得到调度。

在 Linux 中,nice 的值范围为-20 ~ +19,默认值为 0。nice 值越大,则优先级越低,因此相对较低 nice 值的进程可以获得更多的处理器时间。

通过命令 ps -el 查看系统中的进程列表,其中 NI 列就是进程对应的 nice 值。

使用 top 命令,看到的 NI 列也是进程的 nice 值。

调整 nice 值,可以通过 shell 命令 nice ,该命令可以按照指定的 nice 值运行 cmd ,命令的帮助信息为:

重新调整已运行进程的 nice 值,可通过 renice 命令实现,命令的帮助信息为:

另外,可以执行 top 命令,输入 r ,根据提示输入进程的 pid ,再输入 nice 数值,也可以调整进程的 nice 值。

SCHED_FIFO

该调度策略为先入先出调度策略,简单概括,就是一旦进程占用了 CPU,则一直运行,直到有更高优先级的任务抢占,或者进程自己放弃占用 CPU。

SCHED_RR

该调度策略为时间片轮转调度策略,该调度策略是基于 SCHED_FIFO 策略的演进,其在每个进程上增加一个时间片限制,当时间片使用完成后,调度器将该进程置于队列的尾端,放在尾端保证了所有具有相同调度优先级的进程的调度公平。

使用 top 命令,如果 PR 列的值为 RT ,则说明该进程采用的是实时调度策略,其调度策略为 SCHED_FIFO 或者 SCHED_RR,而对于非实时调度策略的进程,该列的值为 NI + 20 。

可以通过命令 ps -eo state,uid,pid,ppid,rtprio,time,comm 来查看进程对应的实时优先级,实时优先级位于 RTPRIO 列下,如果进程对应的列显示为 - ,说明该进程不是实时进程。

chrt 命令可以用来很简单地更改进程的调度策略与调度优先级。在 Linux 下查看 chrt 命令的帮助信息:

比如,获取某个进程的调度策略,使用如下命令:

在比如,设置某个进程的调度策略为 SCHED_FIFO,调度优先级为 70,使用如下命令:

调度策略值得是大家都在ready时,并且CPU已经被调度时,决定谁来运行,谁来被调度。

两者之间有一定矛盾。

响应的优化,意味着高优先级会抢占优先级,会花时间在上下文切换,会影响吞吐。

上下文切换的时间是很短的,几微妙就能搞定。上下文切换本身对吞吐并多大影响, 重要的是,切换后引起的cpu 的 cache miss.

每次切换APP, 数据都要重新load一次。

Linux 会尽可能的在响应与吞吐之间寻找平衡。比如在编译linux的时候,会让你选择 kernal features ->Preemption model.

抢占模型会影响linux的调度算法。

所以 ARM 的架构都是big+LITTLE, 一个很猛CPU+ 多个 性能较差的 CPU, 那么可以把I/O型任务的调度 放在 LITTLE CPU上。需要计算的放在big上。

早期2.6 内核将优先级划分了 0-139 bit的优先级。数值越低,优先级越高。0-99优先级 都是 RT(即时响应)的 ,100-139都是非RT的,即normal。

调度的时候 看哪个bitmap 中的 优先级上有任务ready。可能多个任务哦。

在普通优先级线程调度中,高优先级并不代表对低优先级的绝对优势。会在不同优先级进行轮转。

100 就是比101高,101也会比102高,但100 不会堵着101。

众屌丝进程在轮转时,优先级高的:

初始设置nice值为0,linux 会探测 你是喜欢睡眠,还是干活。越喜欢睡,linux 越奖励你,优先级上升(nice值减少)。越喜欢干活,优先级下降(nice值增加)。所以一个进程在linux中,干着干着 优先级越低,睡着睡着 优先级越高。

后期linux补丁中

红黑树,数据结构, 左边节点小于右边节点

同时兼顾了 CPU/IO 和 nice。

数值代表着 进程运行到目前为止的virtual runtime 时间。

(pyhsical runtime) / weight * 1024(系数)。

优先调度 节点值(vruntime)最小的线程。权重weight 其实有nice 来控制。

一个线程一旦被调度到,则物理运行时间增加,vruntime增加,往左边走。

weight的增加,也导致vruntime减小,往右边走。

总之 CFS让线程 从左滚到右,从右滚到左。即照顾了I/O(喜欢睡,分子小) 也 照顾了 nice值低(分母高).所以 由喜欢睡,nice值又低的线程,最容易被调度到。

自动调整,无需向nice一样做出奖励惩罚动作,个人理解权重其实相当于nice

但是 此时 来一个 0-99的线程,进行RT调度,都可以瞬间秒杀你!因为人家不是普通的,是RT的!

一个多线程的进程中,每个线程的调度的策略 如 fifo rr normal, 都可以不同。每一个的优先级都可以不一样。

实验举例, 创建2个线程,同时开2个:

运行2次,创建两个进程

sudo renice -n -5(nice -5级别) -g(global), 会明显看到 一个进程的CPU占用率是另一个的 3倍。

为什么cpu都已经达到200%,为什么系统不觉得卡呢?因为,我们的线程在未设置优先级时,是normal调度模式,且是 CPU消耗型 调度级别其实不高。

利用chrt工具,可以将进程 调整为 50 从normal的调度策略 升为RT (fifo)级别的调度策略,会出现:

chrt , nice renice 的调度策略 都是以线程为单位的,以上 设置的将进程下的所有线程进行设置nice值

线程是调度单位,进程不是,进程是资源封装单位!

两个同样死循环的normal优先级线程,其中一个nice值降低,该线程的CPU 利用率就会比另一个CPU的利用率高。

导读:本文将有助于你找出Redis 响应延迟的问题所在。如果你正在经历响应延迟问题,你或许能够根据应用程序的具体情况算出它的延迟响应时间。

关键词:Redis 数据库延迟 响应延迟

本文将有助于你找出Redis 响应延迟的问题所在。

文中出现的延迟(latency)均指从客户端发出一条命令到客户端接受到该命令的反馈所用的最长响应时间。Reids通常处理(命令的)时间非常的慢,大概在次微妙范围内,但也有更长的情况出现。

计算延迟时间

如果你正在经历响应延迟问题,你或许能够根据应用程序的具体情况算出它的延迟响应时间,或者你的延迟问题非常明显,宏观看来,一目了然。不管怎样吧,用redis-cli可以算出一台Redis 服务器的到底延迟了多少毫秒。踹这句:

 redis-cli --latency -h `host` -p `port`

网络和通信引起的延迟

当用户连接到Redis通过TCP/IP连接或Unix域连接,千兆网络的典型延迟大概200us,而Unix域socket可能低到30us。这完全基于你的网络和系统硬件。在通信本身之上,系统增加了更多的延迟(线程调度,CPU缓存,NUMA替换等等)。系统引起的延迟在虚拟机环境远远高于在物理机器环境。

实际情况是即使Redis处理大多数命令在微秒之下,客户机和服务器之间的交互也必然消耗系统相关的延迟。一个高效的客户机因而试图通过捆绑多个命令在一起的方式减少交互的次数。服务器和大多数客户机支持这种方式。聚合命令象MSET/MGET也可以用作这个目的。从Redis 2.4版本起,很多命令对于所有的数据类型也支持可变参数。

这里有一些指导:

如果你负担的起,尽可能的使用物理机而不是虚拟机来做服务器

不要经常的connect/disconnect与服务器的连接(尤其是对基于web的应用),尽可能的延长与服务器连接的时间。

如果你的客户端和服务器在同一台主机上,则使用Unix域套接字

尽量使用聚合命令(MSET/MGET)或可变参数命令而不是pipelining

如果可以尽量使用pipelining而不是序列的往返命令。

针对不适合使用原始pipelining的情况,如某个命令的结果是后续命令的输入,在以后的版本中redis提供了对服务器端的lua脚本的支持,实验分支版本现在已经可以使用了。

在Linux上,你可以通过process placement(taskset)、cgroups、real-time priorities(chrt)、NUMA配置(numactl)或使用低延迟内核的方式来获取较低的延迟。请注意Redis 并不适合被绑到单个CPU核上。redis会在后台创建一些非常消耗CPU的进程,如bgsave和AOF重写,这些任务是绝对不能和主事件循环进程放在一个CPU核上的。大多数情况下上述的优化方法是不需要的,除非你确实需要并且你对优化方法很熟悉的情况下再使用上述方法。

Redis的单线程属性

Redis 使用了单线程的设计, 意味着单线程服务于所有的客户端请求,使用一种复用的技术。这种情况下redis可以在任何时候处理单个请求, 所以所有的请求是顺序处理的。这和Node.js的工作方式很像, 所有的产出通常不会有慢的感觉,因为处理单个请求的时间非常短,但是最重要的是这些产品被设计为非阻塞系统调用,比如从套接字中读取或写入数据。

我提到过Redis从2.4版本后几乎是单线程的,我们使用线程在后台运行一些效率低下的I/O *** 作, 主要关系到硬盘I/O,但是这不改变Redis使用单线程处理所有请求的事实。

低效 *** 作产生的延迟

单线程的一个结果是,当一个请求执行得很慢,其他的客户端调用就必须等待这个请求执行完毕。当执行GET、SET或者LPUSH 命令的时候这不是个问题,因为这些 *** 作可在很短的常数时间内完成。然而,对于多个元素的 *** 作,像SORT,LREM, SUNION 这些,做两个大数据集的交叉要花掉很长的时间。文档中提到了所有 *** 作的算法复杂性。 在使用一个你不熟悉的命令之前系统的检查它会是一个好办法。

如果你对延迟有要求,那么就不要执行涉及多个元素的慢 *** 作,你可以使用Redis的replication功能,把这类慢 *** 作全都放到replica上执行。可以用Redis 的Slow Log来监控慢 *** 作。此外,你可以用你喜欢的进程监控程序(top, htop, prstat, 等...)来快速查看Redis进程的CPU使用率。如果traffic不高而CPU占用很高,八成说明有慢 *** 作。

延迟由fork产生

Redis不论是为了在后台生成一个RDB文件,还是为了当AOF持久化方案被开启时重写Ap

pend Only文件,都会在后台fork出一个进程。fork *** 作(在主线程中被执行)本身会引发延迟。在大多数的类unix *** 作系统中,fork是一个很消耗的 *** 作,因为它牵涉到复制很多与进程相关的对象。而这对于分页表与虚拟内存机制关联的系统尤为明显。

对于运行在一个linux/AMD64系统上的实例来说,内存会按照每页4KB的大小分页。为了实现虚拟地址到物理地址的转换,每一个进程将会存储一个分页表(树状形式表现),分页表将至少包含一个指向该进程地址空间的指针。所以一个空间大小为24GB的redis实例,需要的分页表大小为 24GB/4KB*8 = 48MB。当一个后台的save命令执行时,实例会启动新的线程去申请和拷贝48MB的内存空间。这将消耗一些时间和CPU资源,尤其是在虚拟机上申请和初始化大块内存空间时,消耗更加明显。

在不同系统中的Fork时间

除了Xen系统外,现代的硬件都可以快速完美的复制页表。Xen系统的问题不是特定的虚拟化,而是特定的Xen.例如使用VMware或者Virutal Box不会导致较慢的fork时间。下面的列表比较了不同Redis实例的fork时间。数据包含正在执行的BGSAVE,并通过INFO指令查看thelatest_fork_usecfiled。

Linux beefy VM on VMware 6.0GB RSS forked 77 微秒 (每GB 12.8 微秒 ).

Linux running on physical machine (Unknown HW) 6.1GB RSS forked 80 微秒(每GB 13.1微秒)

Linux running on physical machine (Xeon @ 2.27Ghz) 6.9GB RSS forked into 62 微秒 (每GB 9 微秒).

Linux VM on 6sync (KVM) 360 MB RSS forked in 8.2 微秒 (每GB 23.3 微秒).

Linux VM on EC2 (Xen) 6.1GB RSS forked in 1460 微秒 (每GB 239.3 微秒).

Linux VM on Linode (Xen) 0.9GBRSS forked into 382 微秒 (每GB 424 微秒).

你能看到运行在Xen上的VM的Redis性能相差了一到两个数量级。我们相信这是Xen系统的一个验证问题,我们希望这个问题能尽快处理。

swapping ( *** 作系统分页)引起的延迟

Linux (以及其他一些 *** 作系统) 可以把内存页存储在硬盘上,反之也能将存储在硬盘上的内存页再加载进内存,这种机制使得内存能够得到更有效的利用。如果内存页被系统移到了swap文件里,而这个内存页中的数据恰好又被redis用到了(例如要访问某个存储在内存页中的key),系统就会暂停redis进程直到把需要的页数据重新加载进内存。这个 *** 作因为牵涉到随机I/O,所以很慢,会导致无法预料的延迟。

系统之所以要在内存和硬盘之间置换redis页数据主要因为以下三个原因:

系统总是要应对内存不足的压力,因为每个运行的进程都想申请更多的物理内存,而这些申请的内存的数量往往超过了实际拥有的内存。简单来说就是redis使用的内存总是比可用的内存数量更多。

redis实例的数据,或者部分数据可能就不会被客户端访问,所以系统可以把这部分闲置的数据置换到硬盘上。需要把所有数据都保存在内存中的情况是非常罕见的。

一些进程会产生大量的读写I/O。因为文件通常都有缓存,这往往会导致文件缓存不断增加,然后产生交换(swap)。请注意,redis RDB和AOF后台线程都会产生大量文件。

所幸Linux提供了很好的工具来诊断这个问题,所以当延迟疑似是swap引起的,最简单的办法就是使用Linux提供的工具去确诊。

首先要做的是检查swap到硬盘上的redis内存的数量,为实现这个目的要知道redis实例的进程id:

$ redis-cli info | grep process_id

process_id:5454

进入进程目录:

$ cd /proc/5454在这里你会发现一个名为smaps 的文件,它描述了redis进程的内存布局 (假定你使用的是Linux 2.6.16或者更新的版本)。这个文件包括了很多进程所使用内存的细节信息,其中有一项叫做Swap的正是我们所关心的。不过仅看这一项是不够的,因为smaps文件包括有redis进程的多个不同的的内存映射区域的使用情况(进程的内存布局远不是线性排列那么简单)。

从我们对所有进程的内存交换情况感兴趣以来,我们首先要做的事情是使用grep命令显示进程的smaps文件

$ cat smaps | grep 'Swap:'

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 12 kB

Swap: 156 kB

Swap: 8 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 4 kB

Swap: 0 kB

Swap: 0 kB

Swap: 4 kB

Swap: 0 kB

Swap: 0 kB

Swap: 4 kB

Swap: 4 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB

Swap: 0 kB假如所有的数据显示为0kb或者某些数据偶尔显示为4kb,表示当前一切正常。实际上我们的例子是一个真实的运行着Redis并每秒为数百的用户提供服务的网站,会显示更多的交换页。为了研究是否存在一个严重的问题,我们改变命令打印出分配的内存尺寸

$ cat smaps | egrep '^(Swap|Size)'

Size: 316 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 8 kB

Swap: 0 kB

Size: 40 kB

Swap: 0 kB

Size: 132 kB

Swap: 0 kB

Size: 720896 kB

Swap: 12 kB

Size: 4096 kB

Swap: 156 kB

Size: 4096 kB

Swap: 8 kB

Size: 4096 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 1272 kB

Swap: 0 kB

Size: 8 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 16 kB

Swap: 0 kB

Size: 84 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 8 kB

Swap: 4 kB

Size: 8 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 4 kB

Swap: 4 kB

Size: 144 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 4 kB

Swap: 4 kB

Size: 12 kB

Swap: 4 kB

Size: 108 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

Size: 272 kB

Swap: 0 kB

Size: 4 kB

Swap: 0 kB

在输出信息中,你能看到有一个720896kb的内存分配(有12kb的交换)还有一个156kb的交换是另一个进程的。基本上我们的内存只会有很小的内存交换,因此不会产生任何的问题.

假如进程的内存有相当部分花在了swap上,那么你的延迟可能就与swap有关。假如redis出现这种情况那么可以用vmstat 命令来验证一下猜测:

$ vmstat 1

procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----

r b swpd free buff cache si so bi bo in cs us sy id wa

0 0 3980 697932 147180 1406456 0 0 2 2 2 0 4 4 91 0

0 0 3980 697428 147180 1406580 0 0 0 0 19088 16104 9 6 84 0

0 0 3980 697296 147180 1406616 0 0 0 28 18936 16193 7 6 87 0

0 0 3980 697048 147180 1406640 0 0 0 0 18613 15987 6 6 88 0

2 0 3980 696924 147180 1406656 0 0 0 0 18744 16299 6 5 88 0

0 0 3980 697048 147180 1406688 0 0 0 4 18520 15974 6 6 88 0

输出中我们最感兴趣的两行是si 和 so,这两行分别统计了从swap文件恢复到内存的数量和swap到文件的内存数量。如果在这两行发现了非0值那么就说明系统正在进行swap。

最后,可以用iostat命令来查看系统的全局I/O行为。

$ iostat -xk 1

avg-cpu: %user %nice %system %iowait %steal %idle

13.55 0.04 2.92 0.53 0.00 82.95

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util

sda 0.77 0.00 0.01 0.00 0.40 0.00 73.65 0.00 3.62 2.58 0.00

sdb 1.27 4.75 0.82 3.54 38.00 32.32 32.19 0.11 24.80 4.24 1.85

如果确认延迟是由于swap引起的,那么就需要减小系统的内存压力,要么给机器增加内存,要么不要在同一个机器上运行其他消耗内存的程序。

 转载,仅供参考,祝你愉快,满意请采纳。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/8331684.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-15
下一篇 2023-04-15

发表评论

登录后才能评论

评论列表(0条)

保存