Linux I/O調(diào)度器(Linux I/O Scheduler)Linux內(nèi)核中的一個(gè)組成部分,用戶可以通過調(diào)整這個(gè)調(diào)度器來優(yōu)化系統(tǒng)性能,介于通用塊層和塊設(shè)備驅(qū)動程序之間。
I/O 調(diào)度算法
- noop(No Operation) :通常用于內(nèi)存存儲的設(shè)備。
- cfq(Completely Fair Scheduler ) :完全公平調(diào)度器,進(jìn)程平均使用IO帶寬。
- deadline :針對延遲的調(diào)度器,每一個(gè) I/O,都有一個(gè)最晚執(zhí)行時(shí)間。
- Anticipatory : 啟發(fā)式調(diào)度,類似 Deadline 算法,但是引入預(yù)測機(jī)制提高性能。
設(shè)置io調(diào)度參數(shù)
1. 查看CentOS IO支持的調(diào)度算法
$ cat /sys/block/sda/queue/scheduler
noop [deadline] cfq
CentOS 7.x默認(rèn)支持的是deadline算法,CentOS 6.x下默認(rèn)支持的cfq算法,而一般我們會在SSD固態(tài)盤硬盤環(huán)境中使用noop算法
2.?查看系統(tǒng)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. 設(shè)置磁盤IO調(diào)度算法
$ echo cfq> /sys/block/vdb/queue/scheduler
4.?設(shè)置磁盤io相關(guān)參數(shù)
# 磁盤隊(duì)列長度,默認(rèn)128,可提高到512個(gè),控制磁盤允許分配的讀寫請求數(shù)量
# 此增大參數(shù)會增大系統(tǒng)內(nèi)存占用,但能更加多的合并讀寫操作,降低io磁盤讀寫速率
$ cat /sys/block/vdb/queue/nr_requests?
128
# 讀優(yōu)化
# 應(yīng)用場景為順序讀,采用預(yù)讀技術(shù)可提高用戶體驗(yàn)。默認(rèn)128KB,增大此參數(shù)對讀大文件非常有用,可以有效的減少讀 seek 的次數(shù)
$ cat /sys/block/sda/queue/read_ahead_kb
4096
注:"echo 4096 > /sys/block/sda/queue/read_ahead_kb"與"blockdedv -setra 8192 /dev/sda"等價(jià),blockdedv命令定義可以預(yù)讀多少個(gè)扇區(qū),blockdev --getra /dev/sda 是/sys/block/sda/queue/read_ahead_kb的倍數(shù)關(guān)系,設(shè)置其中一個(gè),另外一個(gè)自動就會發(fā)生變化.
"blockdev --getra /dev/sda":查看/dev/sda預(yù)讀扇區(qū)數(shù)
# 控制發(fā)送到磁盤的最大I/O請求數(shù),最小值等于系統(tǒng)block size,做大值小于等于max_hw_sectors_kb。
# 如果發(fā)送請求的大小(即max_sectors_kb大于block size),會導(dǎo)致磁盤性能下降,調(diào)整之前,需要測試磁盤block size大小
# max_hw_sectors_kb:單個(gè)數(shù)據(jù)傳輸中硬件(如磁盤)最大支持多少KB的數(shù)據(jù);
# max_sectors_kb: 一次請求中block 層最大支持多少KB數(shù)據(jù),<= max_hw_sectors_kb。
$ cat /sys/block/sda/queue/max_sectors_kb
256
$ cat /sys/block/sda/queue/max_hw_sectors_kb
256
# 設(shè)置磁盤尋道邏輯,ssd磁盤設(shè)置為0,防止調(diào)度器使用尋道邏輯。
# 機(jī)械盤設(shè)置為1
$ cat /sys/block/sda/queue/rotational
1
# 磁盤調(diào)試配置,默認(rèn)禁用,設(shè)置為0,關(guān)閉調(diào)試
$ cat /sys/block/sda/queue/nomerges
0
5. 臟數(shù)據(jù)回刷參數(shù)與調(diào)優(yōu)(內(nèi)存相關(guān)參數(shù))
# 緩存釋放策略,不涉及dirty page(臟頁)內(nèi)容,默認(rèn)為0(即不釋放緩存)
# dirty page:dirty是物理內(nèi)存中頁標(biāo)志,如果該頁被改寫了,就稱之為dirty page
# drop_caches=1:釋放pagecache(頁緩存)
# drop_caches=2:釋放inode(文件句柄)和dentry(目錄項(xiàng))
# drop_caches=3:釋放pagecache(頁緩存)、inode(文件句柄)和dentry(目錄項(xiàng))
$ cat /proc/sys/vm/drop_caches
0
# 控制文件系統(tǒng)的文件系統(tǒng)寫緩沖區(qū)的大小,單位為百分比,表示占用系統(tǒng)內(nèi)存的百分比;
# 當(dāng)寫緩沖使用到系統(tǒng)內(nèi)存多少的時(shí)候,開始向磁盤寫出數(shù)據(jù),增大會使用更多系統(tǒng)內(nèi)存用于磁盤寫緩沖,也可以極大提高系統(tǒng)的寫性能;
# 默認(rèn)值20
$ cat /proc/sys/vm/dirty_ratio
40
注:在持續(xù)、恒定的寫入場合時(shí),應(yīng)該降低其數(shù)值。
# 控制文件系統(tǒng)的pdflush進(jìn)程在何時(shí)刷新磁盤,單位為百分比,表示當(dāng)臟頁占比因超過vm.dirty_ratio而觸發(fā)強(qiáng)制寫回后所能允許的臟頁大小占比。
# 默認(rèn)值10
# vm.dirty_ratio:讓進(jìn)程自己進(jìn)行一個(gè)強(qiáng)制寫回操作
# vm.dirty_background_ratio:調(diào)用per-BDI flush在后臺寫入
$ cat /proc/sys/vm/dirty_background_ratio
10
注:在持續(xù)、恒定的寫入場合時(shí),應(yīng)該降低其數(shù)值。
#控制內(nèi)核的臟數(shù)據(jù)刷新進(jìn)程pdflush的運(yùn)行間隔,單位是1/100 秒,默認(rèn)值500,即5秒。
$ cat /proc/sys/vm/dirty_writeback_centisecs
500
注:持續(xù)寫入動作,建議降低此參數(shù),可以尖峰的寫操作削平成多次寫操作;若系統(tǒng)短期地尖峰式的寫操作,并且寫入數(shù)據(jù)不大(幾十M/次)且內(nèi)存有比較多富裕,建議增大此數(shù)值
# 控制臟頁回寫時(shí)間間隔,單位是1/100 秒,默認(rèn)值30000,即30秒
# 聲明Linux內(nèi)核寫緩沖區(qū)里面的數(shù)據(jù)多“舊”了之后,pdflush進(jìn)程就開始考慮寫到磁盤中去;
# 此參數(shù)不能設(shè)置太小,否則會導(dǎo)致頻繁寫磁盤,建議設(shè)置為 1500。
$ cat /proc/sys/vm/dirty_expire_centisecs
3000
場景案例
注:配置均為參考值,可根據(jù)業(yè)務(wù)場景合理配置
場景一:盡可能不丟數(shù)據(jù)
針對數(shù)據(jù)非常重要的場景,在滿足性能要求的情況下,要做到盡可能不丟失數(shù)據(jù)。
dirty_background_ratio = 5
dirty_ratio = 10
dirty_writeback_centisecs = 50
dirty_expire_centisecs = 100
- 當(dāng)臟數(shù)據(jù)達(dá)到可用內(nèi)存的5%時(shí)喚醒回刷進(jìn)程
- 當(dāng)臟數(shù)據(jù)達(dá)到可用內(nèi)存的10%時(shí),應(yīng)用每一筆數(shù)據(jù)都必須同步等待
- 每隔500ms喚醒一次回刷進(jìn)程
- 內(nèi)存中臟數(shù)據(jù)存在時(shí)間超過1s則在下一次喚醒時(shí)回刷
特征:通過減少Cache,更加頻繁喚醒回刷進(jìn)程的方式,盡可能讓數(shù)據(jù)回刷
整體資源消耗:CPU消耗升高,內(nèi)存使用降低,IO調(diào)度頻率增高,IO占比降低
場景二:追求更高性能
不需要考慮數(shù)據(jù)安全問題,要做到盡可能高的IO性能
dirty_background_ratio = 50
dirty_ratio = 80
dirty_writeback_centisecs = 2000
dirty_expire_centisecs = 12000
- 當(dāng)臟數(shù)據(jù)達(dá)到可用內(nèi)存的50%時(shí)喚醒回刷進(jìn)程
- 當(dāng)臟數(shù)據(jù)達(dá)到可用內(nèi)存的80%時(shí),應(yīng)用每一筆數(shù)據(jù)都必須同步等待
- 每隔20s喚醒一次回刷進(jìn)程
- 內(nèi)存中臟數(shù)據(jù)存在時(shí)間超過120s則在下一次喚醒時(shí)回刷
特征:增大Cache,延遲回刷喚醒時(shí)間來盡可能緩存更多數(shù)據(jù),進(jìn)而實(shí)現(xiàn)提高性能
整體資源消耗:CPU、磁盤IO消耗低,內(nèi)存使用增高
場景三:突然的IO峰值拖慢整體性能
什么是IO峰值?突然間大量的數(shù)據(jù)寫入,導(dǎo)致瞬間IO壓力飆升,導(dǎo)致瞬間IO性能狂跌
dirty_background_ratio = 5
dirty_ratio = 80
dirty_writeback_centisecs = 500
dirty_expire_centisecs = 3000
- 當(dāng)臟數(shù)據(jù)達(dá)到可用內(nèi)存的5%時(shí)喚醒回刷進(jìn)程
- 當(dāng)臟數(shù)據(jù)達(dá)到可用內(nèi)存的80%時(shí),應(yīng)用每一筆數(shù)據(jù)都必須同步等待
- 每隔5s喚醒一次回刷進(jìn)程
- 內(nèi)存中臟數(shù)據(jù)存在時(shí)間超過30s則在下一次喚醒時(shí)回刷
特征:增大Cache總?cè)萘?/strong>,更加頻繁喚醒回刷進(jìn)程的方式,解決IO峰值的問題,此時(shí)能保證臟數(shù)據(jù)比例保持在一個(gè)比較低的水平,當(dāng)突然出現(xiàn)峰值,也有足夠的Cache來緩存數(shù)據(jù)
整體資源消耗:CPU、內(nèi)存使用增高,IO調(diào)度頻率增高
內(nèi)核源碼
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配置項(xiàng)的信息,實(shí)際上修改了對應(yīng)的某個(gè)全局變量的值,如dirty_background_ratio對應(yīng)的變量為&dirty_background_ratio。
每個(gè)全局變量都有默認(rèn)值,追溯這些全局變量的定義
<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 */
默認(rèn)值:
配置項(xiàng)名 |
對應(yīng)源碼變量名 |
默認(rèn)值 |
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 |
回刷進(jìn)程
通過ps aux,我們總能看到writeback的內(nèi)核進(jìn)程。
$ ps aux | grep "writeback"
root 21 0.0 0.0 0 0 ? S< Mar19 0:00 [writeback]
實(shí)際上是一個(gè)工作隊(duì)列對應(yīng)的進(jìn)程,在default_bdi_init()中創(chuàng)建。
/* 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);
...
}
回刷進(jìn)程的核心是函數(shù)wb_workfn(),通過函數(shù)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);
...
}
喚醒回刷進(jìn)程的操作如下:
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);
}
表示喚醒的回刷任務(wù)在工作隊(duì)列writeback中執(zhí)行,這樣,就把工作隊(duì)列和回刷工作綁定了
在wb_workfn()的最后,有如下代碼:
void wb_workfn(struct work_struct *work)
{
...
/* 如果還有需要回收的內(nèi)存,再次喚醒 */
if (!list_empty(&wb->work_list))
wb_wakeup(wb);
/* 如果還有臟數(shù)據(jù),延遲喚醒 */
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,設(shè)置下次喚醒時(shí)間 */
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);
}
根據(jù)kernel/sysctl.c的內(nèi)容,我們知道dirty_writeback_centisecs配置項(xiàng)對應(yīng)的全局變量是dirty_writeback_interval。
可以看出dirty_writeback_interval在wb_wakeup_delayed()中起作用,在wb_workfn()的最后根據(jù)dirty_writeback_interval設(shè)置下一次喚醒時(shí)間。
我們還發(fā)現(xiàn)通過msecs_to_jiffies(XXX * 10)來換算單位,表示dirty_writeback_interval乘以10之后的計(jì)量單位才是毫秒msecs。怪不得說dirty_writeback_centisecs的單位是1/100秒。
臟數(shù)據(jù)量
臟數(shù)據(jù)量通過dirty_background_XXX和dirty_XXX表示,根據(jù)kernel/sysctl.c的內(nèi)容,我們知道dirty_background_XXX配置項(xiàng)對應(yīng)的全局變量是dirty_background_XXX,dirty_XXX對于的全局變量是vm_dirty_XXX。
我們把目光聚焦到函數(shù)domain_dirty_limits(),通過這個(gè)函數(shù)換算臟數(shù)據(jù)閾值。
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的優(yōu)先級高于dirty_background_ratio/dirty_ratio
- dirty_background_bytes/ratio和dirty_bytes/ratio最終會統(tǒng)一換算成頁做計(jì)量單位
- dirty_background_bytes/dirty_bytes做進(jìn)一除法,表示如果值為4097Bytes,換算后是2頁
- dirty_background_ratio/dirty_ratio相乘的基數(shù)是available_memory,表示可用內(nèi)存
- 如果dirty_background_XXX大于dirty_XXX,則取dirty_XXX的一半
內(nèi)存計(jì)算方法:
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 */
}
因此,文章來源:http://www.zghlxwxcb.cn/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
可用內(nèi)存 = 空閑頁 - 內(nèi)核預(yù)留頁 + 活動文件頁 + 非活動文件頁 ( - 高端內(nèi)存)
即:
$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);
簡化為:
可用內(nèi)存 = 空閑頁 + 可回收頁
可回收頁 = Active_file + Inactive_file + SReclaimable
臟數(shù)據(jù)達(dá)到閾值后是怎么觸發(fā)回刷的?文章來源地址http://www.zghlxwxcb.cn/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);
}
總結(jié)
- 可回收內(nèi)存 = 文件臟頁 + 文件系統(tǒng)不穩(wěn)定頁(NFS)
- 可回收內(nèi)存達(dá)到dirty_background_XXX計(jì)算的閾值,只是喚醒臟數(shù)據(jù)回刷工作后直接返回,并不會等待回收完成,最終回收工作還是看writeback進(jìn)程
到了這里,關(guān)于linux性能優(yōu)化-IO調(diào)度優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!