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