轉自:https://www.ustack.com/blog/ceph-internal-rbdcache/
RBDCache 是Ceph的塊存儲接口實現庫 Librbd 的用來在客戶端側緩存數據的目的,它主要提供了讀數據緩存,寫數據匯聚寫回的目的,用來提高順序讀寫的性能。需要說明的是,Ceph 既支持以內核模塊的方式來實現對 Linux 動態增加塊設備,也支持以 QEMU Block Driver 的形式給使用 QEMU 虛擬機增加虛擬塊設備,而且兩者使用不同的庫,前者是內核模塊的形式,后者是普通的用戶態庫,本文討論的 RBDCache 針對后者,前者使用內核的 Page Cache 達到目的。更多關於 Librbd 的情況參見解析Ceph: Librbd–塊存儲庫。

RBDCache 的實現
RBDCache 目前在 Librbd(以下統指用戶態庫)中主要以 Object Buffer Extent 為基本單位進行緩存,一個 RBD 塊設備在 Lirbd 層會以固定大小分為若干個對象,而讀寫請求通常會有不同的 IO 大小,每個請求的 Buffer 大小都會以 Object 為單位放到一個或多個 Object Buffer Extent 中。目前 RBDCache 只支持以內存的形式存在,因此需要提供一些策略來不斷回寫到 Ceph 集群來實現持久化。在 Lirbd 中有若干選項來控制 RBDCache 的大小和回寫策略:
- rbd_cache_size: Librbd 能使用的最大緩存大小
- rbd_cache_max_dirty: 緩存中允許臟數據的最大值,用來控制回寫大小,不能超過 rbd_cache_size
- rbd_cache_target_dirty: 開始執行回寫過程的臟數據大小,不能超過 rbd_cache_max_dirty
- rbd_cache_max_dirty_age: 緩存中單個臟數據最大的存在時間,避免可能的臟數據因為遲遲未達到開始回寫的要求而長時間存在
除了當滿足緩存回寫要求大小或者時間才會回寫數據外,Librbd 提供的 Flush 接口同樣能將緩存中的臟數據全部回寫。
RBDCache 由於只是以內存的形式存在,因此大部分人可能會關心是否由於意外的 Kernel Crash 或者 Host 端掉電而導致潛在的數據丟失情況,那么下面就主要討論這種情況。
Cache 在內核
熟悉 Linux Kernel 的人都知道在內核的存儲體系中主要有兩種緩存,一是 Page Cache,二是 Buffer Cache。Page Cache 是在 Linux IO 棧中為文件系統服務的緩存,而 Buffer Cache 是處於更下層的 Block Device 層,由於應用大部分的使用存儲數據是基於文件系統,因此 Buffer Cache 實際上只是引用了 Page Cache 的數據,而只有在直接使用塊設備跳過文件系統時,Page Cache 才真正掌握緩存。關於 Page Cache 和 Buffer Cache 更多的討論參加What is the major difference between the buffer cache and the page cache?。
這些 Cache 都由內核中專門的數據回寫線程負責來刷新到塊設備中,應用可以使用如 fsync(2), fdatasync(2) 之類的系統調用來完成強制執行對某個文件數據的回寫。像數據一致性要求高的應用如 MySQL 這類數據庫服務通常有自己的日志用來保證事務的原子性,日志的數據被要求每次事務完成前通過 fsync(2) 這類系統調用強制寫到塊設備上,否則可能在系統崩潰后造成數據的不一致。
而 fsync(2) 的實現取決於文件系統,文件系統會將要求數據從緩存中強制寫到持久設備中。但是這里還有另外一個大“麻煩”,通常成為 Block Device Cache(塊設備緩存),這類緩存並不存在歸 Kernel 管理,它或許是傳統磁盤上的控制器緩存,RAID 控制器緩存或者就像本文提到的 RBDCache,它主要是被塊設備自己管理。
塊設備緩存
傳統硬件塊設備提供緩存的目的與 RBDCache 的意義是一致的,它們同樣面臨在機器掉電情況下,存在於磁盤控制器上的緩存丟失的情況。但是現代磁盤控制器或者 RAID 卡都會配置一個小型電容用來實現在機器掉電后對緩存數據的回寫,但是 Linux Kernel 無法知曉到底是否存在這類“急救”裝置來實現持久性,因此,大多數文件系統在實現 fsync 這類接口時,同時會使用 Kernel Block 模塊提供的 “blkdev_issue_flush” API 去給塊設備發送一個 Flush Request,塊設備收到 Flush Request 后就會回寫自身的緩存。但是如果機器上存在電容,那么實際上 Flush Request 會大大降低文件系統的讀寫性能,因此,文件系統會提供如 barrier 選項來讓用戶選擇是否需要發送 Flush Request,比如 XFS 在 mount 時就支持 “barrier=0″ 來選擇不發送 Flush Request (Write barrier support.)。
相關的文件系統對持久化的 Trick 參考深入文件的讀與寫—文件系統保證陷阱。
QEMU 中的緩存
回到 RBDCache 的使用情況里,用戶往往是使用 QEMU 實現的 VM 來使用 RBD 塊設備,那么 Linux Kernel 中的塊設備驅動是 virtio_blk。它會對塊設備各種請求封裝成一個消息通過 virtio 框架提供的隊列發送到 QEMU 的 IO 線程,QEMU 收到請求后會轉給相應的 QEMU Block Driver 來完成請求。用戶在使用本地文件或者 Host 提供的 LVM 分區時,跟 RBDCache 同樣性質的緩存包括了 Guest Cache 和 Host Page Cache,在本文暫且不提這種情況下的緩存,相關信息參考KVM storage performance and cache settings on Red Hat Enterprise Linux 6.2。
而當 QEMU Block Driver 是 RBD 時,緩存就會交給 Librbd 自身去維護,也就是一直所說的 RBDCache。用戶在使用了開啟 RBDCache 的 RBD 塊設備 VM 時需要給 QEMU 傳入 “cache=writeback” 確保 QEMU 知曉有緩存的存在,不然 QEMU 會認為后端並沒有緩存而選擇將 Flush Request 忽略。
QEMU 作為最終使用 Librbd 中 RBDCache 的用戶,它在 VM 關閉、QEMU 支持的熱遷移操作或者 RBD 塊設備卸載時都會調用 QEMU Block Driver 的 Flush 接口。
RBDCache 可能造成的數據破壞
通過上面的梳理,可以發現開啟 RBDCache 的 RBD 塊設備實際上就是一個不帶電容的磁盤,我們需要讓文件系統開啟 barrier 模式,幸運的是,這也是文件系統的默認情況。除此之外,因為文件系統實際上可能管理的是通過 LVM 這種邏輯卷管理工具得到的分區,因此必須確保文件系統下面的 Linux Device Mapping 層也能夠支持 Flush Request,LVM 在較早版本的 Kernel 中就已經支持 Flush Request,而其他 DM-* 模塊可能就會忽略該請求,這就需要用戶非常明確的了解。
幸運的是,rbd 會默認開啟一個叫”rbd_cache_writethrough_until_flush”的一個選項,它的作用就是為了避免一些不支持 “flush” 的 VM 來使用 RBDCache,它的主要方式是在用戶開啟 RBDCache 的情況下,在收到來自 VM 的第一個 Flush 請求前,它是不會在邏輯上啟用 Cache 的。這樣就避免了舊內核不支持 Flush 的問題。
