简介

我们知道,Linux用cache/buffer缓存数据,且有个回刷任务在适当时候把脏数据回刷到存储介质中。什么是适当的时候?换句话说,什么时候触发回刷?是脏数据达到多少阈值还是定时触发,或者两者都有?

不同场景对触发回刷的时机的需求也不一样,对IO回刷触发时机的选择,是IO性能优化的一个重要方法

Linux内核在/proc/sys/vm中有透出数个配置文件,可以对触发回刷的时机进行调整。内核的回刷进程是怎么运作的呢?这数个配置文件有什么作用呢?

配置概述

/proc/sys/vm中有以下文件与回刷脏数据密切相关:

配置文件 功能 默认值
dirty_background_ratio 触发回刷的脏数据占可用内存的百分比 0
dirty_background_bytes 触发回刷的脏数据量 10
dirty_bytes 触发同步写的脏数据量 0
dirty_ratio 触发同步写的脏数据占可用内存的百分比 20
dirty_expire_centisecs 脏数据超时回刷时间(单位:1/100s) 3000
dirty_writeback_centisecs 回刷进程定时唤醒时间(单位:1/100s) 500

对上述的配置文件,有几点要补充的:

  1. XXX_ratio 和 XXX_bytes 是同一个配置属性的不同计算方法,优先级 XXX_bytes > XXX_ratio
  2. 可用内存并不是系统所有内存,而是free pages + reclaimable pages
  3. 脏数据超时表示内存中数据标识脏一定时间后,下次回刷进程工作时就必须回刷
  4. 回刷进程既会定时唤醒,也会在脏数据过多时被动唤醒。
  5. dirty_background_XXX与dirty_XXX的差别在于前者只是唤醒回刷进程,此时应用依然可以异步写数据到Cache,当脏数据比例继续增加,触发dirty_XXX的条件,不再支持应用异步写。

关于同步与异步IO的说明,可以看另一篇博客《Linux IO模型》

更完整的功能介绍,可以看内核文档Documentation/sysctl/vm.txt

配置示例

单纯的配置说明毕竟太抽象。结合网上的分享,我们看看在不同场景下,该如何配置?

场景1:尽可能不丢数据

有些产品形态的数据非常重要,例如行车记录仪。在满足性能要求的情况下,要做到尽可能不丢失数据。

/* 此配置不一定适合您的产品,请根据您的实际情况配置 */
dirty_background_ratio = 5
dirty_ratio = 10
dirty_writeback_centisecs = 50
dirty_expire_centisecs = 100

这样的配置有以下特点:

  1. 当脏数据达到可用内存的5%时唤醒回刷进程
  2. 当脏数据达到可用内存的10%时,应用每一笔数据都必须同步等待
  3. 每隔500ms唤醒一次回刷进程
  4. 内存中脏数据存在时间超过1s则在下一次唤醒时回刷

由于发生交通事故时,行车记录仪随时可能断电,事故前1~2s的数据尤为关键。因此在保证性能满足不丢帧的情况下,尽可能回刷数据。

此配置通过减少Cache更加频繁唤醒回刷进程的方式,尽可能让数据回刷。

此时的性能理论上会比每笔数据都O_SYNC略高,比默认配置性能低,相当于用性能换数据安全。

场景2:追求更高性能

有些产品形态不太可能会掉电,例如服务器。此时不需要考虑数据安全问题,要做到尽可能高的IO性能。

/* 此配置不一定适合您的产品,请根据您的实际情况配置 */
dirty_background_ratio = 50
dirty_ratio = 80
dirty_writeback_centisecs = 2000
dirty_expire_centisecs = 12000

这样的配置有以下特点:

  1. 当脏数据达到可用内存的50%时唤醒回刷进程
  2. 当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待
  3. 每隔20s唤醒一次回刷进程
  4. 内存中脏数据存在时间超过120s则在下一次唤醒时回刷

与场景1相比,场景2的配置通过 增大Cache延迟回刷唤醒时间来尽可能缓存更多数据,进而实现提高性能

场景3:突然的IO峰值拖慢整体性能

什么是IO峰值?突然间大量的数据写入,导致瞬间IO压力飙升,导致瞬间IO性能狂跌,对行车记录仪而言,有可能触发视频丢帧。

/* 此配置不一定适合您的产品,请根据您的实际情况配置 */
dirty_background_ratio = 5
dirty_ratio = 80
dirty_writeback_centisecs = 500
dirty_expire_centisecs = 3000

这样的配置有以下特点:

  1. 当脏数据达到可用内存的5%时唤醒回刷进程
  2. 当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待
  3. 每隔5s唤醒一次回刷进程
  4. 内存中脏数据存在时间超过30s则在下一次唤醒时回刷

这样的配置,通过 增大Cache总容量更加频繁唤醒回刷的方式,解决IO峰值的问题,此时能保证脏数据比例保持在一个比较低的水平,当突然出现峰值,也有足够的Cache来缓存数据。

内核代码实现

知其然,亦要知其所以然。翻看内核代码,寻找配置的实现,细细品味不同配置的细微差别。

基于内核代码版本:5.5.15

sysctl文件

kernel/sysctl.c中列出了所有的配置文件的信息。

static struct ctl_table vm_table[] = {
...
{
.procname = "dirty_background_ratio",
.data = &dirty_background_ratio,
.maxlen = sizeof(dirty_background_ratio),
.mode = 0644,
.proc_handler = dirty_background_ratio_handler,
.extra1 = &zero,
.extra2 = &one_hundred,
},
{
.procname = "dirty_ratio",
.data = &vm_dirty_ratio,
.maxlen = sizeof(vm_dirty_ratio),
.mode = 0644,
.proc_handler = dirty_ratio_handler,
.extra1 = &zero,
.extra2 = &one_hundred,
},
{
.procname = "dirty_writeback_centisecs",
.data = &dirty_writeback_interval,
.maxlen = sizeof(dirty_writeback_interval),
.mode = 0644,
.proc_handler = dirty_writeback_centisecs_handler,
},
}

为了避免文章篇幅过大,我只列出了关键的3个配置项且不深入代码如何实现。

我们只需要知道,我们修改/proc/sys/vm配置项的信息,实际上修改了对应的某个全局变量的值。

每个全局变量都有默认值,追溯这些全局变量的定义

<mm/page-writeback.c>

int dirty_background_ratio = 10;
unsigned long dirty_background_bytes;
int vm_dirty_ratio = 20;
unsigned long vm_dirty_bytes;
unsigned int dirty_writeback_interval = 5 * 100; /* centiseconds */
unsigned int dirty_expire_interval = 30 * 100; /* centiseconds */

总结如下:

配置项名 对应源码变量名 默认值
dirty_background_bytes dirty_background_bytes 0
dirty_background_ratio dirty_background_ratio 10
dirty_bytes vm_dirty_bytes 0
dirty_ratio vm_dirty_ratio 20
dirty_writeback_centisecs dirty_writeback_interval 500
dirty_expire_centisecs dirty_expire_interval 3000

回刷进程

通过ps aux,我们总能看到writeback的内核进程

$ ps aux | grep "writeback"
root 40 0.0 0.0 0 0 ? I< 06:44 0:00 [writeback]

这实际上是一个工作队列对应的进程,在default_bdi_init()中创建。

 /* bdi_wq serves all asynchronous writeback tasks */
struct workqueue_struct *bdi_wq; static int __init default_bdi_init(void)
{
...
bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_FREEZABLE |
WQ_UNBOUND | WQ_SYSFS, 0);
...
}

回刷进程的核心是函数wb_workfn(),通过函数wb_init()绑定。

static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi
int blkcg_id, gfp_t gfp)
{
...
INIT_DELAYED_WORK(&wb->dwork, wb_workfn);
...
}

唤醒回刷进程的操作是这样的

static void wb_wakeup(struct bdi_writeback *wb)
{
spin_lock_bh(&wb->work_lock);
if (test_bit(WB_registered, &wb->state))
mod_delayed_work(bdi_wq, &wb->dwork, 0);
spin_unlock_bh(&wb->work_lock);
}

表示唤醒的回刷任务在工作队列writeback中执行,这样,就把工作队列和回刷工作绑定了。

我们暂时不探讨每次会回收了什么,关注点在于相关配置项怎么起作用。在wb_workfn()的最后,有这样的代码:

void wb_workfn(struct work_struct *work)
{
...
/* 如果还有需要回收的内存,再次唤醒 */
if (!list_empty(&wb->work_list))
wb_wakeup(wb);
/* 如果还有脏数据,延迟唤醒 */
else if (wb_has_dirty_io(wb) && dirty_writeback_interval)
wb_wakeup_delayed(wb);
} static void wb_wakeup(struct bdi_writeback *wb)
{
spin_lock_bh(&wb->work_lock);
if (test_bit(WB_registered, &wb->state))
mod_delayed_work(bdi_wq, &wb->dwork, 0);
spin_unlock_bh(&wb->work_lock);
} void wb_wakeup_delayed(struct bdi_writeback *wb)
{
unsigned long timeout; /* 在这里使用dirty_writeback_interval,设置下次唤醒时间 */
timeout = msecs_to_jiffies(dirty_writeback_interval * 10);
spin_lock_bh(&wb->work_lock);
if (test_bit(WB_registered, &wb->state))
queue_delayed_work(bdi_wq, &wb->dwork, timeout);
spin_unlock_bh(&wb->work_lock);
}

根据kernel/sysctl.c的内容,我们知道dirty_writeback_centisecs配置项对应的全局变量是dirty_writeback_interval

可以看到,dirty_writeback_intervalwb_wakeup_delayed()中起作用,在wb_workfn()的最后根据dirty_writeback_interval设置下一次唤醒时间。

我们还发现通过msecs_to_jiffies(XXX * 10)来换算单位,表示dirty_writeback_interval乘以10之后的计量单位才是毫秒msecs。怪不得说dirty_writeback_centisecs的单位是1/100秒。

脏数据量

脏数据量通过dirty_background_XXXdirty_XXX表示,他们又是怎么工作的呢?

根据kernel/sysctl.c的内容,我们知道dirty_background_XXX配置项对应的全局变量是dirty_background_XXXdirty_XXX对于的全局变量是 vm_dirty_XXX

我们把目光聚焦到函数domain_dirty_limits(),通过这个函数换算脏数据阈值。

static void domain_dirty_limits(struct dirty_throttle_control *dtc)
{
...
unsigned long bytes = vm_dirty_bytes;
unsigned long bg_bytes = dirty_background_bytes;
/* convert ratios to per-PAGE_SIZE for higher precision */
unsigned long ratio = (vm_dirty_ratio * PAGE_SIZE) / 100;
unsigned long bg_ratio = (dirty_background_ratio * PAGE_SIZE) / 100;
...
if (bytes)
thresh = DIV_ROUND_UP(bytes, PAGE_SIZE);
else
thresh = (ratio * available_memory) / PAGE_SIZE; if (bg_bytes)
bg_thresh = DIV_ROUND_UP(bg_bytes, PAGE_SIZE);
else
bg_thresh = (bg_ratio * available_memory) / PAGE_SIZE; if (bg_thresh >= thresh)
bg_thresh = thresh / 2; dtc->thresh = thresh;
dtc->bg_thresh = bg_thresh;
}

上面的代码体现了如下的特征

  1. dirty_background_bytes/dirty_bytes的优先级高于dirty_background_ratio/dirty_ratio
  2. dirty_background_bytes/ratio和dirty_bytes/ratio最终会统一换算成做计量单位
  3. dirty_background_bytes/dirty_bytes做进一除法,表示如果值为4097Bytes,换算后是2页
  4. dirty_background_ratio/dirty_ratio相乘的基数是available_memory,表示可用内存
  5. 如果dirty_background_XXX大于dirty_XXX,则取dirty_XXX的一半

可用内存是怎么计算来的呢?

static unsigned long global_dirtyable_memory(void)
{
unsigned long x; x = global_zone_page_state(NR_FREE_PAGES);
/*
* Pages reserved for the kernel should not be considered
* dirtyable, to prevent a situation where reclaim has to
* clean pages in order to balance the zones.
*/ x += global_node_page_state(NR_INACTIVE_FILE);
x += global_node_page_state(NR_ACTIVE_FILE); if (!vm_highmem_is_dirtyable)
x -= highmem_dirtyable_memory(x); return x + 1; /* Ensure that we never return 0 */
}

所以,

可用内存 = 空闲页 - 内核预留页 + 活动文件页 + 非活动文件页 ( - 高端内存)

脏数据达到阈值后是怎么触发回刷的呢?我们再看balance_dirty_pages()函数

static void balance_dirty_pages(struct bdi_writeback *wb,
unsigned long pages_dirtied)
{
unsigned long nr_reclaimable; /* = file_dirty + unstable_nfs */
...
/*
* Unstable writes are a feature of certain networked
* filesystems (i.e. NFS) in which data may have been
* written to the server's write cache, but has not yet
* been flushed to permanent storage.
*/
nr_reclaimable = global_node_page_state(NR_FILE_DIRTY) +
global_node_page_state(NR_UNSTABLE_NFS);
...
if (nr_reclaimable > gdtc->bg_thresh)
wb_start_background_writeback(wb);
} void wb_start_background_writeback(struct bdi_writeback *wb)
{
wb_wakeup(wb);
}

总结下有以下特征:

  1. 可回收内存 = 文件脏页 + 文件系统不稳定页(NFS)
  2. 可回收内存达到dirty_background_XXX计算的阈值,只是唤醒脏数据回刷工作后直接返回,并不会等待回收完成,最终回收工作还是看writeback进程

Linux 脏数据回刷参数与调优的更多相关文章

  1. 阿里云下 centos7下启动程序总是被killed ,看内存占用情况以检查哪些服务存在问题并调整参数作调优

    很久不搭理自己的网站了,几天突然发现启动程序总是被killed, 于是查看了系统日志 vi /var/log/messages 发现出现 kernel: Out of memory: Kill pro ...

  2. mysql的从头到脚优化之服务器参数的调优

    一. 说到mysql的调优,有许多的点可以让我们去做,因此梳理下,一些调优的策略,今天只是总结下服务器参数的调优  其实说到,参数的调优,我的理解就是无非两点: 如果是Innodb的数据库,innod ...

  3. Spark数据本地化-->如何达到性能调优的目的

    Spark数据本地化-->如何达到性能调优的目的 1.Spark数据的本地化:移动计算,而不是移动数据 2.Spark中的数据本地化级别: TaskSetManager 的 Locality L ...

  4. spark submit参数及调优

    park submit参数介绍 你可以通过spark-submit --help或者spark-shell --help来查看这些参数.   使用格式:  ./bin/spark-submit \   ...

  5. spark submit参数及调优(转载)

    spark submit参数介绍 你可以通过spark-submit --help或者spark-shell --help来查看这些参数. 使用格式:  ./bin/spark-submit \ -- ...

  6. RandomForest 随机森林算法与模型参数的调优

    公号:码农充电站pro 主页:https://codeshellme.github.io 本篇文章来介绍随机森林(RandomForest)算法. 1,集成算法之 bagging 算法 在前边的文章& ...

  7. Spark 常用参数及调优

    spark streaming 调优的几个角度: 高效地利用集群资源减少批数据的处理时间 设置正确的批容量(size),使数据的处理速度能够赶上数据的接收速度 内存调优 Spark SQL 可以通过调 ...

  8. linux 内核参数VM调优 之 参数调节和场景分析

    1. pdflush刷新脏数据条件 (linux IO 内核参数调优 之 原理和参数介绍)上一章节讲述了IO内核调优介个重要参数参数. 总结可知cached中的脏数据满足如下几个条件中一个或者多个的时 ...

  9. linux下TCP/IP及内核参数优化调优(转)

    Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等. 如下配置是写在sysctl.con ...

随机推荐

  1. 纯CSS实现元素垂直水平居中-非固定宽度

    这里不讨论行内元素的居中!! 盒子垂直居中+水平居中的需求时经常遇到的,看到的较多实现逻辑是固定content-box的宽度,通过让margin-left和margin-top等于宽或高的负一半来实现 ...

  2. swoole 异步非堵塞 server/端 client/端 代码,已经测试完毕。贴代码

    服务器环境  centos7.0  swoole4.3 php7.2 pcre4.8  nginx1.8   php-fpm server.php <?php class Server { pr ...

  3. 遍历Map的四种方式(Java)

    public static void main(String[] args) { Map<String, String> map = new HashMap<String, Stri ...

  4. 3L-最好、最坏、平均、均摊时间复杂度

    关注公众号 MageByte,设置星标点「在看」是我们创造好文的动力.后台回复 "加群" 进入技术交流群获更多技术成长. 本文来自 MageByte-青叶编写 上次我们说过 时间复 ...

  5. php中的进程

    pcntl扩展:主要的进程扩展,完成进程创建于等待操作. posix扩展:完成posix兼容机通用api,如获取进程id,杀死进程等. sysvmsg扩展:实现system v方式的进程间通信之消息队 ...

  6. ZooKeeper原理解析

    目录 ZooKeeper中的各种角色 ZooKeeper与客户端 Zookeeper节点数据操作流程 Paxos 算法概述(ZAB 协议) ZooKeeper 的选主机制 选择机制中的概念 选举消息内 ...

  7. tableZen maxHeight 解决方案 如果数据条数小于N,不进行高度设置,超过N条,直接设置高度,解决原生iview Table 对于右侧固定列,不能计算出正确数值的解决方案

    tableZen maxHeight 解决方案 如果数据条数小于N,不进行高度设置,超过N条,直接设置高度,解决原生iview Table 对于右侧固定列,不能计算出正确数值的解决方案 if (thi ...

  8. Asp.net 的DropDownList 控件动态加载

    在做连接数据库表,在页面上用DropDownList 下拉框查询某条数据时,因为数据库里的数据,随时都有可能增加或减少,而下拉框关联的某个字段的值并不一定是固定的. 表信息: CREATE TABLE ...

  9. 2020年大厂Java面试前复习的正确姿势(800+面试题附答案解析)

    前言 个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事.所以,劝各位不要因为面试失败而灰心. 丧失斗志.也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! 本篇分享的面试题内容 ...

  10. kubernetes 1.17.2结合ceph13.2.8 实现jenkins部署并用traefik2.1代理

    注:关于ceph.kubernetes集群的部署在此不声明,相信搜到本篇博文,你一定对ceph.kubernetes的部署环节手刃有余. 注:本篇博文牵扯到的技术点有:ceph.kubernetes. ...