linux里面让出CPU使用权,从Linux进程调度写起

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

背景

话题的背景还要从前一阵生产环境上的遇到的一个问题说起,客户部署了Redis集群作为流处理缓存,系统请求时延要求<30ms,但实际时延一直在200ms左右,初步确定是由于Redis开启了RDB导致Redis读写性能受影响,默认RDB 策略(save 900 1;save 300 10;save 60 10000)导致频繁触发RDB。因此将RDB周期修改为save 10800 100000,即3个小时key更新达到10W次触发RDB。至此业务请求时延满足要求<30ms,但是每3h触发RDB时,RDB持续1min左右,此期间时延都会出现一次飙升,时延监控上看来会出现一个个的毛刺现象。同样的颇为不爽!

问题剖析

进一步对RDB期间时延飙升现象进行了分析,发现每次RDB持久化期间,持久化子进程占用CPU高达100%,同时Redis主进程(server进程)占用CPU仅1%左右。

众所周知Redis持久化期间会fork出一个子进程,进行数据的落盘。想必你也猜到了,对的!我们对Redis进程进行了CPU绑定操作(numactl –cpubind),这样也就带来了现在这个问题,fork的继承特性导致子进程继承父进程的cpu affinity,从而父进程(redis-server)和子进程(RDB)运行在同一个CPU逻辑核上。子进程对父进程CPU资源抢占导致了持久化期间,父进程无法调度到CPU,进而请求处理时延增高。

至此,大家有点着急了吧,裤子都脱了,你就给我看这个。别急问题来了:对于Redis RDB持久化这种IO密集型任务为什么会去抢占父进程的CPU,持久化磁盘IO不应该会让出CPU吗?

Linux进程公平调度(CFS),说好的公平呢?

拨云见雾

上面提到的两个问题先放一放,先来看看关于linux进程调度的几个概念。

1. 关于CFS

这里不对CFS做详细介绍。

CFS简而言之,给CPU设定一个调度周期(sched_latency_ns),目标是让每个进程在一个调度周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个调度周期;然后根据进程的数量,大家平分这个调度周期内的CPU使用权,由于进程的优先级即nice值不同,分割调度周期的时候要加权;每个进程的累计运行时间保存在自己的vruntime字段里,哪个进程的vruntime最小就获得本轮运行的权利。每个CPU的运行队列都维护该运行队列中所有进程的vruntime最小值,新进程的初始vruntime值就以它所在运行队列的min_vruntime为基础来设置,与老进程保持在合理的差距范围内。这里有一个内核参数sched_child_runs_first,该参数打开(1)则可以保证子进程在fork只有有限运行。

休眠的进程在唤醒时会抢占CPU吗?是的。为了保证交互式进程可以及时的获得响应,休眠进程在唤醒时会获得vruntime的补偿,所以它在唤醒时醒时极有可能抢占CPU。但是除了交互式进程以外,主动休眠的进程(调用sleep)或定时任务,往往并不要求快速响应,它们同样会在每次唤醒时获得vruntime补偿,这就导致可能对其它更重要的应用进程被抢占,从而影响整体性能。那能怎么办?这里有两个内核参数可供调整:禁用进程唤醒抢占特性1

2# 唤醒的进程不会立即抢占运行中的进程,而是要等到运行进程用完时间片之后。

sched_wakeup_granularity_ns1

2# echo 4000000 > /proc/kernel/sched_wakeup_granularity_ns

# 只有当唤醒进程的vruntime比当前进程的vruntime小、并且两者差距(vdiff)大于sched_wakeup_granularity_ns的情况下,才可以抢占,否则不可以。值越小发生抢占的概率越高。

2. 关于Redis IO多路复用

这里只说1点,redis的IO多路复用处理逻辑如下(详细代码见函数aeProcessEvents() ):1. 根据定时事件表计算需要等待的最短时间,判断是否有时间事件需要处理;

2. 调用aeApiPoll()进入监听轮询,如果没有事件发生就会进入睡眠状态;

3. 事件发生会被唤醒,处理已触发的。

这里说明主进程会频繁从睡眠状态被唤醒。

3. 关于fork子进程

fork出的子进程几乎继承了父进程的所有属性:环境变量

进程组号

工作目录

数据段、代码段、栈段、堆

scheduler class(Redis进程为sched_other)

nice值

CPU亲缘性

等等

这里需要注意的是fork采用“写时拷贝”技术(COW),即内核并不是复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。

这是最后一段

写到这里想必大家对于上面两个问题,基本已经知道答案了。

对于Redis RDB持久化这种IO密集型任务为什么会去抢占父进程的CPU,持久化磁盘IO不应该会让出CPU吗?

Redis采用非阻塞式IO,其在遍历字典期间会持续占用CPU,而不会因为IO导致主动让出CPU,AOF同理,具体可以参看Redis持久化源码。

Linux进程公平调度(CFS),说好的公平呢?

公平调度是指在两个任务都同时ready的情况下(相同优先级),可以获得相同的CPU运行时长,如果期间其中一个任务阻塞或主动让出CPU(sleep),CPU可以100%交给另一个任务。回到Redis的fork子进程,父进程在负载不足的情况下,监听的事件都未准备好时,会进入休眠状态,此时会主动让出CPU。

然而,这和上面说的关于子进程唤醒的两个系统参数有什么卵关系?当然,生产环境系统禁用了唤醒抢占特性,且sched_wakeup_granularity_ns该值设置为15000000(通常默认为4000000),直接导致了父进程在被唤醒时无法抢占到CPU,进而导致了子进程无限制的抢占CPU,Redis主进程不能及时被调度。

解决方法:生产环境只是将sched_wakeup_granularity_ns修改为了4000000,问题解决。

— end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值