Author:Eric
Source:http://blog.wjin.org/posts/ceph-bluestore-bluefs.html
Declaration: this work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuUTM4BDOvwFMuQzLcNmbtknYvwFbvw1Zy9mLz52bt12bjVmdpRXYlJ3Yuk2Lc9CX6MHc0RHaiojIsJye.png)
Introduction
BlueStore存儲引擎實作中,需要存儲資料和中繼資料。由于kv存儲系統自身的高效性以及對事務的支援,是以選擇kv存儲中繼資料是理所當然的(對象的omap屬性算作資料,也是存放在kv中的)。Luminous目前預設采用RocksDB來存儲中繼資料(RocksDB本身存在寫放大以及compaction的問題,後續可能會針對Ceph的場景量身定制kv),但是BlueStore采用裸裝置,RocksDB不支援raw disk,幸運的是,RocksDB提供RocksEnv運作時環境來支援跨平台操作,那麼能夠想到的方案就是Ceph自己實作一個簡單的檔案系統,這個檔案系統隻提供RocksEnv需要的操作接口,這樣就可以支援RocksDB的運作,而這個檔案系統就是BlueFS。
作為檔案系統本身,需要存放日志,保護檔案系統資料的一緻性。對于RocksDB,也可以對.log檔案單獨配置性能更好的磁盤。是以在BlueFS内部實作的時候,支援多種不同類型的裝置(wal/db/slow),實作非常靈活,大緻原則是RocksDB的.log檔案和BlueFS自身的日志檔案優先使用wal,BlueFS中的普通檔案(RocksDB的.sst檔案)優先使用db,當目前裝置空間不足的時候,自動降級到下一級的裝置。
檔案系統本身需要使用磁盤空間存放資料,但是BlueFS并不需要管理磁盤空閑空間,它将檔案配置設定和釋放空間的操作記錄在日志檔案中。每次重新加載的時候,掃描檔案系統的日志,在記憶體中還原整個檔案系統的中繼資料資訊。運作過程中,磁盤空間使用情況大緻如下(借用Ceph作者Sage的圖):
Data Structure
先看看在BlueFS中辨別一個檔案的inode:
// 實體磁盤的位移和長度,代表塊裝置的一個存儲區域
class AllocExtent {
public:
uint64_t offset; // BlockDevice的實體位址
uint32_t length; // 長度
};
class bluefs_extent_t : public AllocExtent{
public:
uint8_t bdev; // 屬于哪個block device
};
// 檔案的inode
truct bluefs_fnode_t {
uint64_t ino; // inode編号
uint64_t size; // 檔案大小
utime_t mtime; // 修改時間
uint8_t prefer_bdev; // 優先使用哪個block device
mempool::bluefs::vector<bluefs_extent_t> extents; // 檔案對應的磁盤空間
uint64_t allocated; // 檔案實際占用的空間大小,extents的length之和。應該是小于等于size
};
和一般檔案系統類似,需要一個檔案系統超級塊,在mount檔案系統的時候,需要讀取超級塊裡的資料,才能識别檔案系統:
struct bluefs_super_t {
uuid_d uuid; // 唯一的uuid
uuid_d osd_uuid; // 對應的osd的uuid
uint64_t version; // 版本
uint32_t block_size; // 塊大小
bluefs_fnode_t log_fnode; // 記錄檔案系統日志的檔案
};
接下來就是檔案系統的操作,這些操作對檔案系統進行修改,需要封裝成事務并記錄在日志中:
struct bluefs_transaction_t {
typedef enum {
OP_NONE = 0,
OP_INIT, ///< initial (empty) file system marker
// 給檔案配置設定和釋放空間
OP_ALLOC_ADD, ///< add extent to available block storage (extent)
OP_ALLOC_RM, ///< remove extent from availabe block storage (extent)
// 建立和删除目錄項
OP_DIR_LINK, ///< (re)set a dir entry (dirname, filename, ino)
OP_DIR_UNLINK, ///< remove a dir entry (dirname, filename)
// 建立和删除目錄
OP_DIR_CREATE, ///< create a dir (dirname)
OP_DIR_REMOVE, ///< remove a dir (dirname)
// 檔案更新
OP_FILE_UPDATE, ///< set/update file metadata (file)
OP_FILE_REMOVE, ///< remove file (ino)
// bluefs日志檔案的compaction操作
OP_JUMP, ///< jump the seq # and offset
OP_JUMP_SEQ, ///< jump the seq #
} op_t;
uuid_d uuid; ///< fs uuid
uint64_t seq; ///< sequence number
bufferlist op_bl; ///< encoded transaction ops
};
最後看看檔案系統本身的結構:
class BlueFS {
public:
// 檔案系統支援不同種類的塊裝置
static constexpr unsigned MAX_BDEV = 3;
static constexpr unsigned BDEV_WAL = 0;
static constexpr unsigned BDEV_DB = 1;
static constexpr unsigned BDEV_SLOW = 2;
enum {
WRITER_UNKNOWN,
WRITER_WAL, // RocksDB的log檔案
WRITER_SST, // RocksDB的sst檔案
};
// 檔案
struct File : public RefCountedObject {
bluefs_fnode_t fnode; // 檔案inode
int refs; // 引用計數
uint64_t dirty_seq; // dirty序列号
bool locked;
bool deleted;
boost::intrusive::list_member_hook<> dirty_item;
// 讀寫計數
std::atomic_int num_readers, num_writers;
std::atomic_int num_reading;
};
// 目錄
struct Dir : public RefCountedObject {
mempool::bluefs::map<string,FileRef> file_map; // 目錄包含的檔案
};
// 檔案系統的記憶體映像
mempool::bluefs::map<string, DirRef> dir_map; // 所有的目錄
mempool::bluefs::unordered_map<uint64_t,FileRef> file_map; // 所有的檔案
map<uint64_t, dirty_file_list_t> dirty_files; // 髒檔案,根據序列号排列
// 檔案系統超級塊和日志
......
// 結構體FileWriter/FileReader/FileLock,用來對一個檔案進行讀寫和加鎖
......
vector<BlockDevice*> bdev; // BlueFS能夠使用的所有BlockDevice,包括wal/db/slow
vector<IOContext*> ioc; // bdev對應的IOContext
vector<interval_set<uint64_t> > block_all; // bdev對應的磁盤空間
vector<Allocator*> alloc; // bdev對應的allocator
......
};
BlueFS Init
BlueFS的使用者(RocksDB/RocksEnv)隻會對檔案系統進行正常的操作,比如建立/删除檔案,打開檔案進行讀寫等操作。但是在使用檔案系統之前,檔案系統必須格式化,這個是由BlueStore存儲引擎統一管理的,部署osd的時候會完成BlueFS的初始化,流程如下:
int OSD::mkfs(CephContext *cct, ObjectStore *store, const string &dev,
uuid_d fsid, int whoami)
{
......
ret = store->mkfs();
......
}
int BlueStore::mkfs()
{
......
r = _open_db(true);
......
}
int BlueStore::_open_db(bool create)
{
......
bluefs = new BlueFS(cct);
......
// 依次添加 slow/db/wal等裝置
bluefs->add_block_device(...) // 添加裝置,會建立一個BlockDevice及其對應的IOContext
bluefs->add_block_extent(...) // 添加裝置的存儲空間,一般為SUPER_RESERVED到磁盤空間的上限,SUPER_RESERVED為8192,即從第三個4k開始
......
bluefs->mkfs(fsid); // 格式化檔案系統,主要工作包括生成檔案系統的超級塊/log檔案等
bluefs->mount(); // mount檔案系統
......
}
整個流程比較簡單,這裡隻需要明白,BlueFS是一個記憶體檔案系統,mount的時候,通過掃碼日志,在記憶體中還原出整個檔案系統的狀況,包括dir_map和file_map等。之是以這樣做,是因為BlueFS僅僅為RocksDB服務,檔案系統本身隻包含少量的檔案,記憶體空間和磁盤日志空間占用均不大。
int BlueFS::mount()
{
// 讀取超級塊
int r = _open_super();
......
// 初始化allocator為磁盤所有的空間
_init_alloc();
......
// 回放檔案系統日志,日志項即為上面的事務OP,針對每個事務進行回放,檔案系統的dir_map/file_map就會被更新
r = _replay(false);
for (auto& p : file_map) {
for (auto& q : p.second->fnode.extents) {
alloc[q.bdev]->init_rm_free(q.offset, q.length); // 将檔案已經占用的内容從allocator中删除
}
}
......
}
mount完成後,檔案系統的所有資料,包括檔案和目錄,在記憶體中就初始化完成,後續就可以對檔案系統進行讀寫等操作。打開檔案進行寫的操作是open_for_write,如果是新檔案,更新dir_map,注意打開檔案的時候,檔案的prefer_bdev預設采用的BDEV_DB裝置,然後會根據目錄名稱(slow/wal目錄有特殊字尾)進行适當調整,是以BlueFS的普通檔案,優先使用db裝置,而不會使用空間較少的wal裝置。
檔案系統提供的API比較少,其他實作也比較簡單,同樣是記錄日志和更新記憶體中的dir_map和file_map等。當發生異常或重新開機程序的時候,回放檔案系統的日志,将檔案系統的記憶體狀态還原到之前的狀态。
另外一個值得注意的地方是BlueFS檔案系統日志的compact操作,這個分為sync和async兩種,大緻流程是将檔案系統的記憶體映像(檔案和目錄)重新生成事務,然後寫入新的日志檔案,然後将舊的日志檔案删除,而不會對舊的日志檔案做讀寫。
Config
// 普通檔案
bluefs_alloc_size // 最小配置設定大小,預設為1MB
bluefs_max_prefetch // 預讀時的最大位元組數,預設為1MB,主要用在順序讀場景
// 日志檔案
bluefs_min_log_runway // bluefs日志檔案的可用空間小于此值時,新配置設定空間。預設為1MB
bluefs_max_log_runway // bluefs日志檔案的單次配置設定大小,預設為4MB
bluefs_log_compact_min_ratio // 通過目前日志檔案大小和預估的日志檔案的大小的比率控制compact,預設為5
bluefs_log_compact_min_size // 通過日志檔案大小控制compact,小于此值不做compact。預設為16MB
bluefs_compact_log_sync // 日志檔案compact的方式,有sync和async兩種,預設為false,即采用async方式
bluefs_min_flush_size // 因為寫檔案内容是寫到記憶體中的,當檔案内容超過此值就重新整理到磁盤。預設為512kb
bluefs_buffered_io // bluefs調用BlockDevice的read/write時的參數,預設為false,即采用fd_direct
bluefs_sync_write // 是否采用synchronous寫。預設為false,即采用aio_write。這時候在flush block device的時候,需要等待aio完成。參見函數_flush_bdev_safely
bluefs_allocator // bluefs配置設定磁盤空間的配置設定器,預設為stupid,即基于extent的方式。
bluefs_preextend_wal_files // 是否預先更新rocksdb wal檔案的大小。預設為false
// 另外還有一些參數和BlueStore相關,以bluestore_bluefs_開頭,這些參數主要控制将BlueStore的slow存儲空間配置設定給BlueFS使用
Metric
監控名額比較好了解,看看描述就能明白,重點應該關注db/wal的使用情況,因為通常情況下會使用更快的ssd,不要因為BlueFS的空間不夠而使用BlueStore中的slow空間。
"bluefs": {
"gift_bytes": 0,
"reclaim_bytes": 0,
"db_total_bytes": 240043163648,
"db_used_bytes": 631242752,
"wal_total_bytes": 0,
"wal_used_bytes": 0,
"slow_total_bytes": 0,
"slow_used_bytes": 0,
"num_files": 14,
"log_bytes": 5361664,
"log_compactions": 0,
"logged_bytes": 0,
"files_written_wal": 0,
"files_written_sst": 0,
"bytes_written_wal": 0,
"bytes_written_sst": 0
},
Summary
- BlueFS同時支援多個裝置(wal/db/slow)
- BlueFS是個簡單的記憶體檔案系統,隻提供簡單的操作用來支援RocksDB運作
- BlueFS有自己的日志,用來記錄對檔案系統的修改。異常情況下,回放日志可重建檔案系統的完整的記憶體映像