0


linux性能优化-IO调度优化

Linux I/O调度器(Linux I/O Scheduler)Linux内核中的一个组成部分,用户可以通过调整这个调度器来优化系统性能,介于通用块层和块设备驱动程序之间。

I/O 调度算法

  • noop(No Operation) :通常用于内存存储的设备。
  • cfq(Completely Fair Scheduler ) :完全公平调度器,进程平均使用IO带宽。
  • deadline :针对延迟的调度器,每一个 I/O,都有一个最晚执行时间。
  • Anticipatory : 启发式调度,类似 Deadline 算法,但是引入预测机制提高性能。

设置io调度参数

1. 查看CentOS IO支持的调度算法

$ cat /sys/block/sda/queue/scheduler
noop [deadline] cfq

CentOS 7.x默认支持的是deadline算法,CentOS 6.x下默认支持的cfq算法,而一般我们会在SSD固态盘硬盘环境中使用noop算法

2. 查看系统block size

第一种
$ tune2fs -l /dev/sda4|grep "Block size"
Block size:               4096

第二种
$ stat /dev/sda3 /|grep "IO Block"
Size: 0             Blocks: 0          IO Block: 4096   block special file
Size: 4096          Blocks: 8          IO Block: 4096   directory

第三种
$ dumpe2fs /dev/sda1 |grep "Block size"
Block size:               4096

3. 设置磁盘IO调度算法

$ echo cfq> /sys/block/vdb/queue/scheduler

4. 设置磁盘io相关参数

# 磁盘队列长度,默认128,可提高到512个,控制磁盘允许分配的读写请求数量
# 此增大参数会增大系统内存占用,但能更加多的合并读写操作,降低io磁盘读写速率
$ cat /sys/block/vdb/queue/nr_requests  
128

# 读优化
# 应用场景为顺序读,采用预读技术可提高用户体验。默认128KB,增大此参数对读大文件非常有用,可以有效的减少读 seek 的次数
$ cat /sys/block/sda/queue/read_ahead_kb
4096 
注:"echo 4096 > /sys/block/sda/queue/read_ahead_kb"与"blockdedv -setra 8192 /dev/sda"等价,blockdedv命令定义可以预读多少个扇区,blockdev --getra /dev/sda 是/sys/block/sda/queue/read_ahead_kb的倍数关系,设置其中一个,另外一个自动就会发生变化.
"blockdev --getra /dev/sda":查看/dev/sda预读扇区数

# 控制发送到磁盘的最大I/O请求数,最小值等于系统block size,做大值小于等于max_hw_sectors_kb。
# 如果发送请求的大小(即max_sectors_kb大于block size),会导致磁盘性能下降,调整之前,需要测试磁盘block size大小
# max_hw_sectors_kb:单个数据传输中硬件(如磁盘)最大支持多少KB的数据;
# max_sectors_kb: 一次请求中block 层最大支持多少KB数据,<= max_hw_sectors_kb。
$ cat /sys/block/sda/queue/max_sectors_kb
256
$ cat /sys/block/sda/queue/max_hw_sectors_kb
256 

# 设置磁盘寻道逻辑,ssd磁盘设置为0,防止调度器使用寻道逻辑。
# 机械盘设置为1
$ cat /sys/block/sda/queue/rotational
1

# 磁盘调试配置,默认禁用,设置为0,关闭调试
$ cat /sys/block/sda/queue/nomerges
0

5. 脏数据回刷参数与调优(内存相关参数)

# 缓存释放策略,不涉及dirty page(脏页)内容,默认为0(即不释放缓存)
# dirty page:dirty是物理内存中页标志,如果该页被改写了,就称之为dirty page
# drop_caches=1:释放pagecache(页缓存)
# drop_caches=2:释放inode(文件句柄)和dentry(目录项)
# drop_caches=3:释放pagecache(页缓存)、inode(文件句柄)和dentry(目录项)
$ cat /proc/sys/vm/drop_caches
0

# 控制文件系统的文件系统写缓冲区的大小,单位为百分比,表示占用系统内存的百分比;
# 当写缓冲使用到系统内存多少的时候,开始向磁盘写出数据,增大会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能;
# 默认值20
$ cat /proc/sys/vm/dirty_ratio
40
注:在持续、恒定的写入场合时,应该降低其数值。

# 控制文件系统的pdflush进程在何时刷新磁盘,单位为百分比,表示当脏页占比因超过vm.dirty_ratio而触发强制写回后所能允许的脏页大小占比。
# 默认值10
# vm.dirty_ratio:让进程自己进行一个强制写回操作
# vm.dirty_background_ratio:调用per-BDI flush在后台写入
$ cat /proc/sys/vm/dirty_background_ratio
10
注:在持续、恒定的写入场合时,应该降低其数值。

#控制内核的脏数据刷新进程pdflush的运行间隔,单位是1/100 秒,默认值500,即5秒。
$ cat /proc/sys/vm/dirty_writeback_centisecs
500
注:持续写入动作,建议降低此参数,可以尖峰的写操作削平成多次写操作;若系统短期地尖峰式的写操作,并且写入数据不大(几十M/次)且内存有比较多富裕,建议增大此数值

# 控制脏页回写时间间隔,单位是1/100 秒,默认值30000,即30秒
# 声明Linux内核写缓冲区里面的数据多“旧”了之后,pdflush进程就开始考虑写到磁盘中去;
# 此参数不能设置太小,否则会导致频繁写磁盘,建议设置为 1500。
$ cat /proc/sys/vm/dirty_expire_centisecs
3000

场景案例

注:配置均为参考值,可根据业务场景合理配置

场景一:尽可能不丢数据

针对数据非常重要的场景,在满足性能要求的情况下,要做到尽可能不丢失数据。

dirty_background_ratio = 5
dirty_ratio = 10
dirty_writeback_centisecs = 50
dirty_expire_centisecs = 100
  • 当脏数据达到可用内存的5%时唤醒回刷进程
  • 当脏数据达到可用内存的10%时,应用每一笔数据都必须同步等待
  • 每隔500ms唤醒一次回刷进程
  • 内存中脏数据存在时间超过1s则在下一次唤醒时回刷

特征:通过减少Cache,更加频繁唤醒回刷进程的方式,尽可能让数据回刷

整体资源消耗:CPU消耗升高,内存使用降低,IO调度频率增高,IO占比降低

场景二:追求更高性能

不需要考虑数据安全问题,要做到尽可能高的IO性能

dirty_background_ratio = 50
dirty_ratio = 80
dirty_writeback_centisecs = 2000
dirty_expire_centisecs = 12000
  • 当脏数据达到可用内存的50%时唤醒回刷进程
  • 当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待
  • 每隔20s唤醒一次回刷进程
  • 内存中脏数据存在时间超过120s则在下一次唤醒时回刷

特征:增大Cache延迟回刷唤醒时间来尽可能缓存更多数据,进而实现提高性能

整体资源消耗:CPU、磁盘IO消耗低,内存使用增高

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

什么是IO峰值?突然间大量的数据写入,导致瞬间IO压力飙升,导致瞬间IO性能狂跌

dirty_background_ratio = 5
dirty_ratio = 80
dirty_writeback_centisecs = 500
dirty_expire_centisecs = 3000
  • 当脏数据达到可用内存的5%时唤醒回刷进程
  • 当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待
  • 每隔5s唤醒一次回刷进程
  • 内存中脏数据存在时间超过30s则在下一次唤醒时回刷

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

整体资源消耗:CPU、内存使用增高,IO调度频率增高

内核源码

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,
    },
}

修改/proc/sys/vm配置项的信息,实际上修改了对应的某个全局变量的值,如dirty_background_ratio对应的变量为&dirty_background_ratio。

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

<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

30

dirty_writeback_centisecs

dirty_writeback_interval

500

dirty_expire_centisecs

dirty_expire_interval

3000

回刷进程

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

$ ps aux | grep "writeback" 
root 21 0.0 0.0 0 0 ? S< Mar19 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_interval在wb_wakeup_delayed()中起作用,在wb_workfn()的最后根据dirty_writeback_interval设置下一次唤醒时间。

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

脏数据量

脏数据量通过dirty_background_XXX和dirty_XXX表示,根据kernel/sysctl.c的内容,我们知道dirty_background_XXX配置项对应的全局变量是dirty_background_XXX,dirty_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;
  • dirty_background_bytes/dirty_bytes的优先级高于dirty_background_ratio/dirty_ratio
  • dirty_background_bytes/ratio和dirty_bytes/ratio最终会统一换算成页做计量单位
  • dirty_background_bytes/dirty_bytes做进一除法,表示如果值为4097Bytes,换算后是2页
  • dirty_background_ratio/dirty_ratio相乘的基数是available_memory,表示可用内存
  • 如果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 */
}

因此,

# cat /proc/meminfo
MemTotal:        3880184 kB
MemFree:          562764 kB
MemAvailable:    3263916 kB
Buffers:          372732 kB
Cached:          2330740 kB
SwapCached:            0 kB
Active:          2064100 kB
Inactive:         849240 kB
Active(anon):     216636 kB
Inactive(anon):      240 kB
Active(file):    1847464 kB
Inactive(file):   849000 kB
Unevictable:        7936 kB
Mlocked:            7936 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:            12 kB
AnonPages:        217868 kB
Mapped:           140412 kB
Shmem:               584 kB
Slab:             319784 kB
SReclaimable:     293908 kB
SUnreclaim:        25876 kB
KernelStack:        3920 kB
PageTables:         6392 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     1940092 kB
Committed_AS:    2266836 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       13392 kB
VmallocChunk:   34359718524 kB
Percpu:              352 kB
HardwareCorrupted:     0 kB
AnonHugePages:     40960 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      100208 kB
DirectMap2M:     4093952 kB
DirectMap1G:     2097152 kB

可用内存 = 空闲页 - 内核预留页 + 活动文件页 + 非活动文件页 ( - 高端内存)
即:
$available = $memfree - $water_low_total;

$pagecache = $Active_file + $Inactive_file;
$pagecache -=min($pagecache/2, $water_low_total);
$available += $pagecache;

$available += $SReclaimable - min($SReclaimable/2, $water_low_total);

简化为:
可用内存 = 空闲页 +  可回收页
可回收页 = Active_file + Inactive_file + SReclaimable

脏数据达到阈值后是怎么触发回刷的?

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);
}

总结

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

本文转载自: https://blog.csdn.net/ygq13572549874/article/details/128810313
版权归原作者 alden_ygq 所有, 如有侵权,请联系我们删除。

“linux性能优化-IO调度优化”的评论:

还没有评论