原文:http://blog.csdn.net/cywosp/article/details/8767327
一、UNIX寫盤操作模型。
1. 傳統的UNIX實現在內核中設有緩衝區高速緩存或頁面高速緩存,大多數磁盤I/O都通過緩衝進行。當將數據寫入文件時,內核通常先將該數據複製到其中一個緩衝區中,如果該緩衝區尚未寫滿,則並不將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩衝區以便存放其他磁盤塊數據時,再將該緩衝排入輸出隊列,然後待其到達隊首時,才進行實際的I/O操作。這種輸出方式被稱爲延遲寫(delayed write)(Bach [1986]第3章詳細討論了緩衝區高速緩存)。
2. 延遲寫減少了磁盤讀寫次數,但是卻降低了文件內容的更新速度,使得欲寫到文件中的數據在一段時間內並沒有寫到磁盤上。當系統發生故障時,這種延遲可能造成文件更新內容的丟失。爲了保證磁盤上實際文件系統與緩衝區高速緩存中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數。
- sync函數只是將所有修改過的塊緩衝區排入寫隊列,然後就返回,它並不等待實際寫磁盤操作結束, 通常稱爲update的系統守護進程會週期性地(一般每隔30秒)調用sync函數。這就保證了定期沖洗內核的塊緩衝區。命令sync(1)也調用sync函數。
- fsync函數只對由文件描述符filedes指定的單一文件起作用,並且等待寫磁盤操作結束,然後返回。fsync可用於數據庫這樣的應用程序,這種應用程序需要確保將修改過的塊立即寫到磁盤上。
- fdatasync函數類似於fsync,但它隻影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。
二、sync、fsync,fdatasync詳細分析
對於提供事務支持的數據庫,在事務提交時,都要確保事務日誌(包含該事務所有的修改操作以及一個提交記錄)完全寫到硬盤上,才認定事務提交成功並返回給應用層。
一個簡單的問題:在*nix操作系統上,怎樣保證對文件的更新內容成功持久化到硬盤?
1. write不夠,需要fsync
#include <unistd.h> 2 int fsync(int fd);
1 #incude <sys/mman.h> 2 int msync(void *addr, size_t length, int flags)
msync需要指定同步的地址區間,如此細粒度的控制似乎比fsync更加高效(因爲應用程序通常知道自己的髒頁位置),但實際上(Linux)kernel中有着十分高效的數據結構,能夠很快地找出文件的髒頁,使得fsync只會同步文件的修改內容。
2. fsync的性能問題,與fdatasync
"Unfortunately fsync() will always initialize two write operations : one for the newly written data and another one in order to update the modification time stored in the inode. If the modification time is not a part of the transaction concept fdatasync() can be used to avoid unnecessary inode disk write operations."
多餘的一次IO操作,有多麼昂貴呢?根據Wikipedia的數據,當前硬盤驅動的平均尋道時間(Average seek time)大約是3~15ms,7200RPM硬盤的平均旋轉延遲(Average rotational latency)大約爲4ms,因此一次IO操作的耗時大約爲10ms左右。這個數字意味着什麼?下文還會提到。
Posix同樣定義了fdatasync,放寬了同步的語義以提高性能:
1 #include <unistd.h> 2 int fdatasync(int fd);
"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."
3. 使用fdatasync優化日誌同步
2.每次log文件創建時,先寫文件的最後1個page,將log文件擴展爲10MB大小
3.向log文件中追加記錄時,由於文件的尺寸不發生變化,使用fdatasync可以大大優化寫log的效率
4.如果一個log文件寫滿了,則新建一個log文件,也只有一次同步metadata的開銷
接下來談談flush dirty page,也就是前面說的同步寫(沒寫完的話阻塞後面,直到寫完才返回)。爲什麼是刷髒頁?髒頁表示緩存中的頁(一般也就是內存中)也物理設備上的頁處於不一致,不一致是由於在內存中被修改。所以爲了使內存中的修改持久化到物理磁盤上我們需要將其從內存中flush到物理磁盤上。根據我的理解,一般來說緩存分成這幾種:1>應用程序自己帶了緩存,比如InnoDB的buffer pool;2>os層面上的緩存 ;3>磁盤設備自己的緩存,比如raid卡一般都管理着自己的緩存;4>磁盤本身或許會有一點點緩存(這個不確定,自己猜想的,這個即使有估計也是極小的)。好了,那麼大部分的時候我們說的flush dirty page都是指從應用程序的緩存->os的緩存->物理設備,如果物理設備沒有緩存的話,此時也就相當於持久化成功,但是像磁盤做了raid,raid卡有緩存的話,實際上還沒真正持久化成功,因爲此時還只到了raid卡的緩存,沒到物理設備,但是由於raid卡一般都帶有備用電池,所以即使此時斷電也不會造成數據丟失。
剛纔說了很多時候應用自己也有緩存機制,那麼你是否想過此時與os的緩存有重複呢?答案是:會的。剛纔說了我是通過研究MySQL的一個參數innodb_flush_method注意這些的,innodb_flush_method表示flush策略,MySQL提供了fdatasync/O_DSYNC/O_DIRECT這三個選項,默認是fdatasync(詳情可參看博文)我這裏主要說明爲什麼會提供選項:O_DIRECT。這個選項告訴os,InnoDB在讀寫數據的時候都不經過os的緩存,因爲剛纔說過InnoDB會維護自己的緩存buffer pool,如果還使用os的緩存那麼兩者就會有一定的重複。在前面參考的文章裏面說O_DIRECT對大量隨即讀寫有效率提升,順序讀寫則會下降。所以根據自己的需求來定,不過如果你的MySQL用在是OLTP上,基本上選擇O_DIRECT沒錯。
三、dirty page解釋:
由於頁高速緩存的緩存作用,寫操作實際上會被延遲,當頁高速緩存中的數據比後臺存儲的數據更新時,該數據被稱爲髒數據。以下情況下髒頁被寫回磁盤:
1)當空閒內存低於一個特定的閾值時
2)當髒頁在內存中駐留時間超過一個特定的閾值時
寫回操作是由一組可並行執行的內核線程完成,這類線程叫做pdflush線程。