Redis与磁盘IO阻塞

内存中的数据写到磁盘,会经过vfs~fs~逻辑卷/软raid~pagecache~块操作调度~磁盘等一系列过程。
磁盘IO阻塞会涉及到操作系统方面的很多细节。了解这些细节,对编程开发以及运维工作都是有利的。
本文中将结合Redis,讲述磁盘IO阻塞。

一、IO阻塞场景

Redis容易出现磁盘IO阻塞场景:
a)追加aof日志
b)rewrite aof
c)频繁dump rdb

分析这几个场景的代码,发现在Redis中调用的posix接口中,WritefSyncCloseRenameUnlink都有可能造成阻塞。
下面我们结合代码来分析它们是如何造成阻塞的。

二、场景分析

1.追加aof日志

#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
void flushAppendOnlyFile(int force) {
    // 每秒 fsync
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
       // 有 fsync 正在后台进行 
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                /*
                 * 前面没有推迟过 write 操作,将推迟写操作的时间记录下来
                 * 不执行 write 或者 fsync
                 */
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                /*
                 * 如果之前已经因为 fsync 而推迟了 write 操作
                 * 但是推迟的时间不超过 2 秒
                 * 不执行 write 或者 fsync
                 */
                return;
            }
            / *
             * 如果后台还有 fsync 在执行,并且 write 已经推迟 >= 2 秒
             * 那么执行写操作
             */
            redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
        }
    }
    /*
     * 执行到这里,程序会对 AOF 文件进行写入。
     */
    nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
         }
}

从上面的流程可以看出,当有bio线程运行fsync,就会推迟write(aof_buf)
推迟超过2s之后,不管是否有bio线程运行fsync,都会直接调用wirte
这个时候,如果fsync正在执行的的话,就会导致write阻塞,Redis服务也就阻塞了。
这里的IO阻塞跟IO繁忙无关,只是因为fsyncwrite都在操作同一个fd

When the fsync policy is set to 'everysec' we may delay the flush if there  is still an fsync() going on in the background thread, since for instance on Linux write(2) will be blocked by the background fsync anyway.

题外话:那么为什么要将fsync放入后台线程中执行呢?

比如,在SSD上连续写1G,如果每次写入4k,就使用fsync刷page cache的话,需要20+min才能执行完成。
而如果所有1G先write再调用fsync()刷盘,2s就写成功了。
时间相差600倍。可见频繁fsync会导致redis性能大大降低。

2.重写aof日志:

其中有三处可能会出现阻塞:
1)将累积的aof_rewrite_bufwritetmpfile文件中
2)rename tmpfileaof

  1. 将1)中write的缓存,fsync到磁盘中
    4)删除旧aof文件

2.1 对于3)fysnc

是因为fsync本身是一个耗时的操作,所以放入bio线程中执行。

2.2 对于2)、4)

作者将close放入bio线程中执行。这里比较难理解,需要我们理解close,unlink,rename和文件删除的关系。

先来看下linux man中说明(只讨论文件):
Close:关闭文件描述符

a)调用过unlink(使得链接数为0),且close的这个fd是对该file最后一个引用,会触发文件的删除。
if the file descriptor was the last reference to a file which has been removed using unlink(2), the file is deleted.
b)当调用close的时候,缓存pagecache并不会刷入磁盘。
Typically, filesystems do not flush buffers when a file is closed.  If you need to be sure that the data is physically stored on the underlying disk, use fsync(2). 

所以说如果close发生了阻塞,应该就是close触发了文件删除。

Unlink:删除目录项(i节点),并将pathname所引用文件链接数(硬链接)计数减一。

int unlink(const char *pathname)
只有当链接数到达0,文件内容才可能被删除。但是,当有进程打开这个文件,文件内容也不能被删除。

所以说:
a)close本身并不会删除文件,除非之前调用过unlink。使得引用数和链接数都为0。
b)unlink本身也不会删除文件,除非此时引用数和链接为0。
只有引用数和链接数都为0,closeunlink才会删除文件,导致阻塞。

Rename:

int rename(const char *oldname, const char *newname) 
Rename这个系统调用会unlink newname对应的文件,然后将旧文件的名字改成新名字。
涉及到删除文件,遵守上文所述删除文件规则,也就是说rename也不一定会真正删除文件。
If the link named by the new argument exists, it shall be removed and old renamed to new.

所以2)中rename tmpfile时,由于fd仍然被引用,并不会真正的删除文件。到4)时,调用close,才会真正删除文件。由于We don't want close(2) or rename(2) calls to block the server on old file deletion.此时将close放入bio线程中执行,避免服务阻塞。

2.3 对于1)

其实这种情况在开始aofredis实例中并不少见。

以下为一个故障现象整理:

子进程做aof,对redis资源消耗。此时redis服务正常。
a)aof rewrite耗时20:04:41-20:26:41共9分钟
b)aof_rewrite_buf使用9280M
c)用户大部分时间平均每秒写入10M/S,高峰写入50M/S
子进程生成新aof,主进程将aof_rewrite_buf写入aof文件中。
此时redis阻塞,不相应外部服务
耗时:20:26:42-20:29:51共3分11秒。

正常情况下,这应该是秒级别就完成的操作。
之所以阻塞,是由页回写机制造成的,我们有两个方向可以尝试解决这个问题:

2.3.1 在程序层面,将集中write改成多次频繁写。

redis4.0利用管道优化aofwrite,具体可参见 https://yq.aliyun.com/articles/177819

2.3.2 系统层面调优,优化内核参数,将写活动高峰分布成频繁的多次写。

首先我们需要了解,脏页是什么时候回写的:

a)空闲内存低于阈值:/proc/sys/vm/dirty_background_ratio 
vm.dirty_background_ratio is the percentage of system memory that can be filled with dirty pages — memory pages that still need to be written to disk — before the pdflush/flush/kdmflush background processes kick in to write it to disk.

b)脏页在内存中驻留的时间超过一个特定的阈值:/proc/sys/vm/dirty_expire_centisecs
When the pdflush/flush/kdmflush processes kick in they will check to see how old a dirty page is, and if it’s older than this value it’ll be written asynchronously to disk.

c)进程调用sync/fsync

d)进程调用write写文件刷新缓存。
WRITE写的时候,缓存超过dirty_ratio,则会阻塞写操作,回刷脏页,直到缓存低于dirty_ratio;如果缓存高于background_writeout,则会在写操作时,唤醒pdflush进程刷脏页,不阻塞写操作。

注:
 在Linux-3.2新内核中,page cache和buffer cache的刷新机制发生了改变。放弃了原有的pdflush机制,改成了bdi_writeback机制。这种变化主要解决原有pdflush机制存在的一个问题:在多磁盘的系统中,pdflush管理了所有磁盘的page/buffer cache,从而导致一定程度的IO性能瓶颈。bdi_writeback机制为每个磁盘都创建一个线程,专门负责这个磁盘的pagecache或者buffer cache的数据刷新工作,从而实现了每个磁盘的数据刷新程序在线程级的分离,这种处理可以提高IO性能。
https://blog.csdn.net/younger_china/article/details/55187057

从d)中可以看出,当磁盘写入繁忙,导致脏页占用内存比率增大。此时调用wirte,fsync都会导致调用进程时间挂起。这也是前面aof write耗费3分11秒的原因。

针对上述脏页回收时机,可以做如下参数调优:

减少内存使用 
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
最大化的使用内存 
vm.dirty_background_ratio = 50
vm.dirty_ratio = 80
优化写入性能, 可以使用内存, 但是等到空闲的时候希望内存被回收, 比较经常用在应对突然有峰值的这种情况 
vm.dirty_background_ratio = 5
vm.dirty_ratio = 80

参见 https://mp.weixin.qq.com/s/9AwI6UfMTk3bcs1BlpfCYA

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容

  • 一、Redis高可用概述 在介绍Redis高可用之前,先说明一下在Redis的语境中高可用的含义。 我们知道,在w...
    空语阅读 1,597评论 0 2
  • 1.1 资料 ,最好的入门小册子,可以先于一切文档之前看,免费。 作者Antirez的博客,Antirez维护的R...
    JefferyLcm阅读 17,066评论 1 51
  • 本文档翻译自http://redis.io/topics/persistence。 这篇文章提供了 Redis 持...
    daos阅读 694评论 0 10
  • 今天来姐姐家玩,看到姐姐的朋友和姐姐两个人为了自己娃娃的作业而心力交瘁的样子我有些不知所措 侄儿上小学二年级,我记...
    鑫心儿阅读 577评论 0 0
  • 与姝会友阅读 127评论 0 0