ext4支持3种DATA模式,用来区分记录journal的行为。
ext4的journal类似于PostgreSQL的XLOG,可以用来做灾难恢复,以及确保数据的一致性。
文件在ext4中分两部分存储,一部分是文件的metadata,另一部分是data。
metadata和data的操作日志journal也是分开管理的。你可以让ext4记录metadata的journal,而不记录data的journal。
这取决于mount ext4时的data参数。
1.
data=journal All data are committed into the journal prior to being
written into the main file system. Enabling
this mode will disable delayed allocation and
O_DIRECT support.
在将data写入文件系统前,必须等待metadata和data的journal已经落盘了。性能最差,并且不支持文件操作的delalloc,O_DIRECT flag (参考 man open)。
当调用fsync时,文件系统的操作包含如下顺序:
fsync(data journal) -> fsync(metadata journal) -> fsync(data) -> fsync(metadata)
lock metadata long time between fsync(metadata journal) and fsync(metadata)?
2.
data=ordered (*) All data are forced directly out to the main file
system prior to its metadata being committed to the
journal.
这个模式不记录data的journal,只记录metadata的journal日志,但是在写metadata的journal前,必须先确保data已经落盘。
当调用fsync时,文件系统的操作包含如下顺序:
fsync(metadata journal) -> fsync(data)(确保data先落盘) -> fsync(metadata)
lock metadata long time between fsync(metadata journal) and fsync(metadata)?
3.
data=writeback Data ordering is not preserved, data may be written
into the main file system after its metadata has been
committed to the journal.
不记录data journal,仅记录metadata journal。并且不保证data比metadata先落盘。
当调用fsync时,文件系统的操作包含如下顺序:
fsync(metadata journal) -> fsync(metadata)
另外需要注意metadata的操作在单个ext4文件系统中是串行的,所以如果某个用户的metadata操作堵塞了的话,会影响所有人操作同一个文件系统的metadata。
即使使用writeback,也会有这样的情况发生,例如某个用户疯狂的写metadata的(例如大批量的创建小文件,调用fsync)。
典型的例子:
fsync(data)这一步如果很慢,会导致其他人写metadata等待的现象(写metadata包括很多,例如创建新文件,读写方式打开文件(改变文件大小))。
查看进程栈:
10249正在fsync:
[root@digoal ~]# cat /proc/10249/stack
[<ffffffffa009f0ad>] do_get_write_access+0x29d/0x520 [jbd2]
[<ffffffffa009f481>] jbd2_journal_get_write_access+0x31/0x50 [jbd2]
[<ffffffffa00fcb68>] __ext4_journal_get_write_access+0x38/0x80 [ext4]
[<ffffffffa00d02f3>] ext4_reserve_inode_write+0x73/0xa0 [ext4]
[<ffffffffa00d036c>] ext4_mark_inode_dirty+0x4c/0x1d0 [ext4]
[<ffffffffa00d0530>] ext4_dirty_inode+0x40/0x60 [ext4]
[<ffffffff811a18fb>] __mark_inode_dirty+0x3b/0x160
[<ffffffff81191da2>] file_update_time+0xf2/0x170
[<ffffffff81114070>] __generic_file_aio_write+0x210/0x470
[<ffffffff8111433f>] generic_file_aio_write+0x6f/0xe0
[<ffffffffa00c9171>] ext4_file_write+0x61/0x1e0 [ext4]
[<ffffffff81177c7a>] do_sync_write+0xfa/0x140
[<ffffffff81177f78>] vfs_write+0xb8/0x1a0
[<ffffffff81178981>] sys_write+0x51/0x90
[<ffffffff8100b0f2>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff
10255正在创建新文件,被堵塞:
[root@digoal ~]# cat /proc/10255/stack
[<ffffffffa009f0ad>] do_get_write_access+0x29d/0x520 [jbd2]
[<ffffffffa009f481>] jbd2_journal_get_write_access+0x31/0x50 [jbd2]
[<ffffffffa00fcb68>] __ext4_journal_get_write_access+0x38/0x80 [ext4]
[<ffffffffa00caca5>] ext4_new_inode+0x3d5/0x1260 [ext4]
[<ffffffffa00da603>] ext4_create+0xc3/0x150 [ext4]
[<ffffffff811858c4>] vfs_create+0xb4/0xe0
[<ffffffff811893cf>] do_filp_open+0xb2f/0xd60
[<ffffffff81175599>] do_sys_open+0x69/0x140
[<ffffffff811756b0>] sys_open+0x20/0x30
[<ffffffff8100b0f2>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff
[ 解决思路 ]
1. 通过调整内核刷dirty page的比例和唤醒时间,可以让内核频繁的收回脏页,从而降低以上问题出现的概率,不过这种做法有利有弊。
相关的参数:
[root@localhost group1]# sysctl -a|grep dirty
vm.dirty_background_ratio = 0
vm.dirty_background_bytes = 1638400
vm.dirty_ratio = 50
vm.dirty_bytes = 0
vm.dirty_writeback_centisecs = 100
vm.dirty_expire_centisecs = 3000
相关进程:
postgres@localhost-> ps -ewf|grep flush
root 1100 2 0 Aug14 ? 00:03:18 [flush-253:0]
root 1102 2 0 Aug14 ? 00:01:32 [flush-253:2]
root 26851 2 0 10:04 ? 00:00:04 [flush-253:1]
root 50052 2 0 10:40 ? 00:00:00 [flush-8:0]
在调整前,请务必了解这些参数的含义,以及Linux是如何处理的。
系统缓存相关的几个内核参数 (还有2个是指定bytes的,含义和ratio差不多):
1. /proc/sys/vm/dirty_background_ratio
该文件表示脏数据到达系统整体内存的百分比,此时触发pdflush进程把脏数据写回磁盘。
缺省设置:10
当用户调用write时,如果发现系统中的脏数据大于这阈值(或dirty_background_bytes ),会触发pdflush进程去写脏数据,但是用户的write调用会立即返回,无需等待。pdflush刷脏页的标准是让脏页降低到该阈值以下。
即使cgroup限制了用户进程的IOPS,也无所谓。
2. /proc/sys/vm/dirty_expire_centisecs
该文件表示如果脏数据在内存中驻留时间超过该值,pdflush进程在下一次将把这些数据写回磁盘。
缺省设置:3000(1/100秒)
3. /proc/sys/vm/dirty_ratio
该文件表示如果进程产生的脏数据到达系统整体内存的百分比,此时用户进程自行把脏数据写回磁盘。
缺省设置:40
当用户调用write时,如果发现系统中的脏数据大于这阈值(或dirty_bytes ),需要自己把脏数据刷回磁盘,降低到这个阈值以下才返回。
注意,此时如果cgroup限制了用户进程的IOPS,那就悲剧了。
4. /proc/sys/vm/dirty_writeback_centisecs
该文件表示pdflush进程的唤醒间隔,周期性把超过dirty_expire_centisecs时间的脏数据写回磁盘。
缺省设置:500(1/100秒)
系统一般在下面三种情况下回写dirty页:
1. 定时方式: 定时回写是基于这样的原则:/proc/sys/vm/dirty_writeback_centisecs的值表示多长时间会启动回写线程,由这个定时器启动的回写线程只回写在内存中为dirty时间超过(/proc/sys/vm/dirty_expire_centisecs / 100)秒的页(这个值默认是3000,也就是30秒),一般情况下dirty_writeback_centisecs的值是500,也就是5秒,所以默认情况下系统会5秒钟启动一次回写线程,把dirty时间超过30秒的页回写,要注意的是,这种方式启动的回写线程只回写超时的dirty页,不会回写没超时的dirty页,可以通过修改/proc中的这两个值,细节查看内核函数wb_kupdate。
2. 内存不足的时候: 这时并不将所有的dirty页写到磁盘,而是每次写大概1024个页面,直到空闲页面满足需求为止
3. 写操作时发现脏页超过一定比例:
当脏页占系统内存的比例超过/proc/sys/vm/dirty_background_ratio 的时候,write系统调用会唤醒pdflush回写dirty page,直到脏页比例低于/proc/sys/vm/dirty_background_ratio,但write系统调用不会被阻塞,立即返回.
当脏页占系统内存的比例超/proc/sys/vm/dirty_ratio的时候, write系统调用会被被阻塞,主动回写dirty page,直到脏页比例低于/proc/sys/vm/dirty_ratio
大数据量项目中的感触:
1 如果写入量巨大,不能期待系统缓存的自动回刷机制,最好采用应用层调用fsync或者sync。如果写入量大,甚至超过了系统缓存自动刷回的速度,就有可能导致系统的脏页率超过/proc/sys/vm/dirty_ratio, 这个时候,系统就会阻塞后续的写操作,这个阻塞有可能有5分钟之久,是我们应用无法承受的。因此,一种建议的方式是在应用层,在合适的时机调用fsync。
2 对于关键性能,最好不要依赖于系统cache的作用,如果对性能的要求比较高,最好在应用层自己实现cache,因为系统cache受外界影响太大,说不定什么时候,系统cache就被冲走了。
3 在logic设计中,发现一种需求使用系统cache实现非常合适,对于logic中的高楼贴,在应用层cache实现非常复杂,而其数量又非常少,这部分请求,可以依赖于系统cache发挥作用,但需要和应用层cache相配合,应用层cache可以cache住绝大部分的非高楼贴的请求,做到这一点后,整个程序对系统的io就主要在高楼贴这部分了。这种情况下,系统cache可以做到很好的效果。
2. 另一种思路是,先调用fdatasync,再调用fsync,来降低堵塞时间。fdatasync某些场景是不需要刷metadata的,但是某些场景依旧需要刷metadata(例如涉及到文件大小的改变),所以这个解决办法并不适用于所有场景。
最终的思路都是让刷metadata尽量快,尽量避免刷metadata时其data还没有刷到磁盘,等待刷其data的时间。
在某些应用场景,例如使用cgroup限制了IOPS的场景,需要非常注意。(这种场景下,内核进程刷脏页就非常有效,因为一般不限制内核进程的IOPS)。
详解:
Data Mode
=========
There are 3 different data modes:
* writeback mode
In data=writeback mode, ext4 does not journal data at all. This mode provides
a similar level of journaling as that of XFS, JFS, and ReiserFS in its default
mode - metadata journaling. A crash+recovery can cause incorrect data to
appear in files which were written shortly before the crash. This mode will
typically provide the best ext4 performance.
* ordered mode
In data=ordered mode, ext4 only officially journals metadata, but it logically
groups metadata information related to data changes with the data blocks into a
single unit called a transaction. When it's time to write the new metadata
out to disk, the associated data blocks are written first. In general,
this mode performs slightly slower than writeback but significantly faster than journal mode.
* journal mode
data=journal mode provides full data and metadata journaling. All new data is
written to the journal first, and then to its final location.
In the event of a crash, the journal can be replayed, bringing both data and
metadata into a consistent state. This mode is the slowest except when data
needs to be read from and written to disk at the same time where it
outperforms all others modes. Enabling this mode will disable delayed
allocation and O_DIRECT support.
ext4支持使用额外的journal设备来存储journal,如果你确定要这么做的话,建议使用IOPS性能好的块设备作为journal设备。
这和数据库的REDO日志要使用低延迟的块设备是一样的道理。
mkfs.ext4时可以指定journal设备。
[参考]
1. https://www.kernel.org/doc/Documentation/filesystems/ext4.txt
3. man open
http://man7.org/linux/man-pages/man2/open.2.html
O_DSYNC
Write operations on the file will complete according to the
requirements of synchronized I/O data integrity completion.
By the time write(2) (and similar) return, the output data has
been transferred to the underlying hardware, along with any
file metadata that would be required to retrieve that data
(i.e., as though each write(2) was followed by a call to
fdatasync(2)). See NOTES below.
2.6.33之前的内核不支持o_dsync,所以只能编程时使用先fdatasync再fsync的方式。
4. man fsync
DESCRIPTION
fsync() transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file descriptor fd to the disk device (or other permanent storage device) where that file resides. The call
blocks until the device reports that the transfer has completed. It also flushes metadata information associated with the file (see stat(2)).
Calling fsync() does not necessarily ensure that the entry in the directory containing the file has also reached disk. For that an explicit fsync() on a file descriptor for the directory is also needed.
fdatasync() is similar to fsync(), but does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be correctly handled. For example, changes to st_atime or st_mtime (respectively,
time of last access and time of last modification; see stat(2)) do not require flushing because they are not necessary for a subsequent data read to be handled correctly. On the other hand, a change to the file size (st_size, as made
by say ftruncate(2)), would require a metadata flush.
The aim of fdatasync() is to reduce disk activity for applications that do not require all metadata to be synchronized with the disk.
fsync会将脏data和metadata都刷到磁盘,fdatasync则只将脏data刷到磁盘(某些情况下也刷metadata)。
所以我们可以在调用fsync前,先调用fdatasync,将脏data刷到磁盘,再调用fsync,减少锁metadata的时间。