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 */
}
因此,文章来源:https://www.toymoban.com/news/detail-499602.html
# 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
脏数据达到阈值后是怎么触发回刷的?文章来源地址https://www.toymoban.com/news/detail-499602.html
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性能优化-IO调度优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!