解析CEPH: 存儲引擎實現之一 filestore
Ceph作為一個高可用和強一致性的軟件定義存儲實現,去使用它非常重要的就是了解其內部的IO路徑和存儲實現。這篇文章主要介紹在IO路徑中最底層的ObjectStore的實現之一FileStore。 ObjectStoreObjectStore是Ceph OSD中最重要的概念之一,它封裝了所有對底層存儲的IO操作。從上圖中可以看到所有IO請求在Clieng端發出,在Message層統一解析後會被OSD層分發到各個PG,每個PG都擁有一個隊列,一個線程池會對每個隊列進行處理。 當一個在PG隊列裏的IO被提出後,該IO請求會被根據類型和相關附帶參數進行處理。如果是讀請求會通過ObjectStore提供的API獲得相應的內容,如果是寫請求也會利用ObjectStore提供的事務API將所有寫操作組合成一個原子事務提交給ObjectStore。ObjectStore通過接口對上層提供不同的隔離級別,目前PG層只采用了Serializable級別,保證讀寫的順序性。 ObjectStore主要接口分為三部分,第一部分是Object的讀寫操作,類似於POSIX的部分接口,第二部分是Object的屬性(xattr)讀寫操作,這類操作的特征是kv對並且與某一個Object關聯。第三部分是關聯Object的kv操作(在Ceph中稱為omap),這個其實與第二部分非常類似,但是在實現上可能會有所變化。 目前ObjectStore的主要實現是FileStore,也就是利用文件系統的POSIX接口實現ObjectStore API。每個Object在FileStore層會被看成是一個文件,Object的屬性(xattr)會利用文件的xattr屬性存取,因為有些文件系統(如Ext4)對xattr的長度有限制,因此超出長度的Metadata會被存儲在DBObjectMap裏。而Object的omap則直接利用DBObjectMap實現。因此,可以看出xattr和omap操作是互通的,在用戶角度來說,前者可以看作是受限的長度,後者更寬泛(API沒有對這些做出硬性要求)。 FileJournal為了縮小寫事務的處理時間,提高寫事務的處理能力並且實現事務的原子性,FileStore引入了FileJournal,所有寫事務在被FileJournal處理以後都會立即返回(上圖中的第二步)。FileJournal類似於數據庫的writeahead日誌,使用O_DIRECT和O_DSYNC每次同步寫入到journal文件,完成後該事務會被塞到FileStore的op queue。事務通常有若幹個寫操作組成,當在中間過程進程crash時,journal會OSD recover提供了完備的輸入。FileStore會存在多個thread從op queue裏獲取op,然後真正apply到文件系統上對應的Object(Buffer IO)。當FileStore將事務落到disk上之後,後續的該Object的讀請求才會繼續(上圖中的第五步)。當FileStore完成一個op後,對應的Journal可以丟棄這部分日誌。 實際上並不是所有的文件系統都按照這個順序,一般來說如Ceph推薦的Ext4和XFS文件系統會先寫入Journal,然後再寫入Filesystem,而COW(Copy on Write)文件系統如Btrfs和ZFS會同時寫入Journal和FileSystem。 DBObjectMapDBObjectMap是FileStore的一部分,利用KeyValue數據庫實現了ObjectStore的第三部分API,DBObjectMap主要復雜在其實現了clone操作的no-copy。因為ObjectStore提供了clone API,提供對一個Object的完全clone(包括Object的屬性和omap)。DBObjectMap對每一個Object有一個Header,每個Object聯系的omap(kv pairs)對會與該Header聯系,當clone時,會產生兩個新的Header,原來的Header作為這兩個新Header的parent,這時候無論是原來的Object還是cloned Object在查詢或者寫操作時都會查詢parent的情況,並且實現copy-on-write。那麽Header如何與omap(kv pairs)聯系呢,首先每個Header有一個唯一的seq,然後所有屬於該Header的omap的key裏面都會包含該seq,因此,利用KeyValueDB的提供的有序prefix檢索來實現對omap的遍歷。 上面提到FileStore會將每個Object作為一個文件,那麽Object的一些屬性會與Object Name一起作為文件名,Object 所屬的PG會作為文件目錄,當一個PG內所包含的文件超過一定程度時(在目錄內文件太多會造成文件系統的lookup性能損耗),PG會被分裂成兩個PG。 |
- Ceph
- 塊存儲
解析CEPH SNAPSHOT
經常有開發者在郵件列表中會問到Ceph Snapshot的實現方式,受限於目前有限的實現文檔和復雜的代碼結構和代碼量,弄清楚Ceph Snapshot並不是一件容易的事。正好最近在重構Ceph存儲引擎層的DBObjectMap,涉及到處理Snapshot間clone的問題,重新梳理了一次在Ceph IO路徑中占了非常大比重的snapshot相關代碼流程,在這裏並不會重點展現裏面的代碼或者數據結構,而是從高層設計角度展現Snapshot的實現。
在閱讀下文前務必先了解Ceph的基本情況和使用場景。什麽是ceph怎樣使用ceph
Ceph Snapshot使用場景
多數人嘗試Ceph的Snapshot往往從Ceph的RBD庫入手,也就是所謂的塊存儲。利用librbd通過簡單的命令可以快速創建卷和Snapshot。
rbd create image-name –size 1024 -p pool
rbd snap create pool/image-name –snap snap-name
第一條命令創建了一個名為”image-name”的卷,在這個過程中librbd庫只是創建了一個metadata而沒有實際向Ceph申請空間。關於librbd如何利用Rados實現塊存儲和管理更多的細節會在以後的文章中講到,這裏先留個坑。
第二條命令對”image-name”卷創建了一個名為”snap-name”的Snapshot,創建以後,對”image-name”卷的任意寫操作之後都可以在任意時間回滾到創建”snap-name”的Snapshot時的數據。如下面這條命令
rbd snap rollback pool/image-name –snap snap-name
在用戶實際嘗試過程中,會發現Ceph對於卷的操作和管理非常輕量,任意時刻,任意卷大小,任意集群大小的卷創建都是相同的操作量級,在其背後實質上也是完全相同的操作。開發者會對如何實現Snapshot更敢興趣,因為Snapshot的實現方式決定了如何有效的使用Snapshot。
Ceph Snapshot實現
在闡述之前,首先要了解Ceph有Pool的概念,也就是上面命令上涉及到的-p pool。一個Ceph Cluster可以創建多個Pool,每個Pool是邏輯上的隔離單位,不同的Pool可以有完全不同的數據處理方式。如Replication Size(副本數),Placement Groups(PG),CRUSH Rules,Snapshots,Ownership都是利用Pool進行隔離的。
因此,對Ceph的任意操作都需要先指定Pool才能進行,上面的image操作都是在一個名為”pool”的Pool上進行,名為”image-name”的Image也是存儲在”pool”中。
除了Pool概念外,Ceph實質上有兩種Snapshot模式,並且兩種Snapshot是不能同時應用到同一個Pool中。
- Pool Snapshot: 對整個Pool打一個Snapshot,該Pool中所有的對象都會受影響
- Self Managed Snapshot: 用戶管理的Snapshot,簡單的理解就是這個Pool受影響的對象是受用戶控制的。這裏的用戶往往是應用如librbd。
我們在前面利用rbd命令的操作實質上是使用第二種模式,因此我們先首先介紹第二種模式的實現。
在前面提到,Snapshot也是利用Pool隔離的,兩種Snapshot mode的實現是基本相似的,如何使用是造成兩種模式分離的重要原因。每個Pool都有一個snap_seq字段,該字段可以認為是整個Pool的Global Version。所有存儲在Ceph的Object也都帶有snap_seq,而每個Object會有一個Head版本的,也可能會存在一組Snapshot objects,不管是Head版本還是snapshot object都會帶有snap_seq,那麽接下來我們看librbd是如何利用該字段創建Snapshot的。
- 用戶申請為”pool”中的”image-name”創建一個名為”snap-name”的Snapshot
- librbd向Ceph Monitor申請得到一個”pool”的snap sequence,Ceph Monitor會遞增該Pool的snap_seq,然後返回該值給librbd。
- librbd將新的snap_seq替換原來image的snap_seq中,並且將原來的snap_seq設置為用戶創建的名為”snap-name”的Snapshot的snap_seq
從上面的操作中,對於版本控制實現熟悉的同學們可能就大致猜測出Ceph對於Snapshot的實現了。每個Snapshot都掌握者一個snap_seq,Image可以看成一個Head Version的Snapshot,每次IO操作對會帶上snap_seq發送給Ceph OSD,Ceph OSD會查詢該IO操作涉及的object的snap_seq情況。如”object-1″是”image-name”中的一個數據對象,那麽初始的snap_seq就”image-name”的snap_seq,當創建一個Snapshot以後,再次對”object-1″進行寫操作時會帶上新的snap_seq,Ceph接到請求後會先檢查”object-1″的Head Version,會發現該寫操作所帶有的snap_seq大於”object-1″的snap_seq,那麽就會對原來的”object-1″克隆一個新的Object Head Version,原來的”object-1″會作為Snapshot,新的Object Head會帶上新的snap_seq,也就是librbd之前申請到的。
Ceph的實現當然比上面提到的要復雜很多,要考慮更多的異常情況還有管理Object Snaps上。
上述提到的是第二種Snapshot Mode,那麽第一種模式實際上更簡單。既然第二種方式是應用(librbd)自己申請snap_seq,然後進行管理,那麽第一種是的場景可以是命令如”rados mksnap snap-name -p pool”進行全局pool的Snapshot,應用是不需要知道snap_seq的。這條命令會遞增”pool”的snap_seq,然後接下來所有”pool”下的objects對會受影響,因為所有的接下來的IO操作都會自動繼承”pool”的snap_seq,對object進行clone。在CephFS裏用到這個模式管理全局的Snapshot。
所以,更簡單的講,這兩者mode的區別就在於應用進行IO請求時是否附帶snap_seq。
Object Snapshot的存儲管理
上面提到的都是如何利用snap_seq向底層存儲查找相應的對象然後返回,那麽底層的存儲引擎是如何管理一個Object的不同版本的呢。
首先,任一個Object都是通過ObjectStore接口進行訪問,目前Ceph Master分支支持MemStore和FileStore兩種,FileStore是默認的存儲接口實現。以後的文章也會介紹具體的FileStore實現。
在Ceph中,每一個Object都有三種類型的存儲接口,分別是最主要的Object存儲,xattr存儲和omap存儲。Object存儲就是用戶實際數據的存放,xattr主要用來給CephFS提供XATTR數據存放,omap存儲可以理解成一個k/v存儲並且與某一個object相關聯。而一個Object的元數據(pool,PG,name等等)都有一個object_info_t的結構進行管理,有一個SnapSetContext結構管理Snapshots,兩者都作為一個object的k/v存儲持久化。默認的FileStore是利用LevelDB作為鍵值存儲,然後通過DBObjectMap類對LevelDB進行映射管理。
在Snapshot的實現上,最重要的其實就是Clone操作,那麽在FileStore層面,Object數據存儲是實際上就是一個文件,Object間克隆依賴OSD數據目錄的文件系統,如Ext4或者XFS會直接完全拷貝數據,使用Btrfs會利用ioctl的BTRFS_IOC_CLONE_RANGE命令,kv數據克隆通過一個巧妙的KeyMapping實現COW策略(略微復雜,後面文章解讀),而xattr則完全copy實現(xattr在Ceph中較少用到)。
本文出自 “西伯利亞·狼” 博客,請務必保留此出處http://kernal.blog.51cto.com/8136890/1540535
解析CEPH: 存儲引擎實現之一 filestore