文章目录
概述
BlueStore数据存储
下图是BlueStore的基本架构,可以看到如下关键信息。
- 数据
- 数据直接存储在HDD裸盘上。
- 元数据
- 元数据以kv形式存储在RocksDB中。
- RocksDB的数据通过BlueRocksEnv接口传递给BlueFS。
- BlueFS将数据存储到SSD裸盘上。
为什么需要BlueFS?
RocksDB不支持直接将数据存储到SSD裸盘上,因此需要一个fs的模块。
BlueFS与RocksDB交互
RocksDB数据文件
- .sst文件:rocksDB中存放数据的file。
- CURRENT:保存了最新的manifest文件信息。
- IDENTITY:db的uuid。
- MANIFEST:保存了rocksDB中LSM结构的变化日志。
- LOCK:db锁持有信息,同时只允许有一个进程打开db。
- OPEIONS:db的配置属性。
- .log:rocksdb数据写时记录的wal数据。
上述数据文件只有CURRENT是修改写,但RocksDB是通过先将数据写到新的临时文件,然后将新文件重命名为CURRENT文件实现修改。因此RocksDB中所有的数据文件都是以追加写的方式完成的。
RocksDB数据在BlueFS中存储
针对rocksDB中的数据文件分类,BlueFS将数据分为wal、db、slow三种类型。
- wal
- 使用最快速的存储介质
- 存储.log文件,用于加速写入。
- db
- 使用SSD存储介质
- 存储.sst、CURRENT、IDENTITY、MANIFEST、LOCK、OPEIONS文件,这些文件通常在compaction时进行读写,需要保证一定的性能
- 当wal写满后本来写wal的数据会存储在db中。
- slow
- 使用低速存储介质
- 当db写满后,本来在db中保存的数据会溢出到slow中保存
下面是在真实设备中导出的BlueFS中数据,可以看出其存储分布
[root@localhost ceph-node]# ceph-bluestore-tool bluefs-export --path /var/lib/ceph/osd/ceph-0/ --out-dir /data/osd_ceph_0
inferring bluefs devices from bluestore path
slot 1 /var/lib/ceph/osd/ceph-0/block -> /var/lib/ceph/osd/ceph-0/block
db/
db/000021.sst
db/000023.sst
db/CURRENT
db/IDENTITY
db/LOCK
db/MANIFEST-000024
db/OPTIONS-000020
db/OPTIONS-000027
db.slow/
db.wal/
db.wal/000025.log
BlueFS持久化元数据
元数据组织结构
下图是BlueFS的元数据组织,包括如下关键元数据:
- superblock:记录文件系统全局信息。
- journal:BlueFS的元数据不像传统文件系统一样,用特定的数据结构和布局存放,而是通过将所有的操作记录到journal中,然后在加载的时候逐条回放journal中的记录,从而将元数据file加载到内存。
- file:真实数据的data的元数据。
- data:真实数据。
元数据类详解
bluefs_extent_t(data)
直接看类的实现即可理解。
class bluefs_extent_t {
uint64_t offset; // BlockDevice的物理地址
uint32_t length; // 数据长度
uint8_t bdev; // 属于哪个blockdevice
};
bluefs_fnode_t(file)
直接看类的实现即可理解。
struct 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
};
bluefs_super_t(superblock)
记录文件系统的全局信息,也是文件系统加载的入口,其位置固定存放于BlueFS的第二个block。
其中的log_fnode为journal的文件。
struct bluefs_super_t {
uuid_d uuid; // uuid
uuid_d osd_uuid; // osd uuid
uint64_t version; // 版本号,当且仅当日志进行压缩的时候递增,可通过版本号判断BlueFS进行日志压缩的次数
uint32_t block_size; // BlueFS中的块大小,即每次读写的最小单位
bluefs_fnode_t log_fnode; // 记录文件系统journal的文件
};
bluefs_transaction_t(journal)
journal实际上是一种特殊的文件,其fnode记录在superblock中,而其他文件的fnode作为日志内容记录在journal文件中。
journal是由一条条的事务transaction组成。
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; // 表示事务归属的bluefs对应的uuid
uint64_t seq; // 事务的全局唯一序列号
bufferlist op_bl; // 编码后的事务条目,可包含多条记录,每条记录由相关的操作码和操作所涉及的相关数据组成。
};
BlueFS journal机制
BlueFS的元数据不像传统文件系统一样,用特定的数据结构和布局存放,而是通过将所有的操作记录到journal中,然后在加载的时候逐条回放journal中的记录,从而将元数据加载到内存。
举例:创建.sst文件事务机制
在目录data下新建一个文件1.sst,然后向其中写入数据,将此作为一个事务提交,那么这个事务中最终会按顺序生成三条记录:
- 生成OP_FILE_UPDATE记录,包含记录类型和新生成的fnode结构;
- 生成OP_DIR_LINK记录,包含记录类型、目录名data、文件名1.sst以及文件fnode中的ino编号;
- 生成OP_FILE_UPDATE记录,包含记录类型和新生成的fnode结构;写入操作由于有分配新的空间并且需要更改操作时间,需要更新fnode结构,因此会新生成一条OP_FILE_UPDATE记录。
journal回放
日志回放会根据记录的顺序逐条解析,在上面例子中首先会
- 将第一条记录中的fnode解析到内存
- 第二步会在dirmap和filemap中建立文件和目录的映射
- 第三步会用新的fnode结构替换掉第一步中的fnode。
BlueFS内存元数据
元数据缓存机制
BlueFS的metadata全部加载在内存当中,主要包含superblock、目录和文件的集合、文件跟目录的映射关系以及文件到物理地址的映射关系;当下次文件系统加载时,将日志中的记录逐条回放到内存,从而还原出metadata。
元数据类详解(BlueFS)
元数据如下所示,包括:
- superblock:见上一章节。
- dir_map:记录BlueFS中的所有目录的目录名到实际目录结构的映射,目录为扁平结构,不存在隶属关系,例如目录/a/b与目录/a在同一级,/a/b即为目录名
- file_map:记录目录下的所有文件的文件名到实际文件结构的映射,每个文件结构中包含该文件的fnode结构。
class BlueFS {
public:
// 文件系统支持不同种类的块设备
static constexpr unsigned MAX_BDEV = 5;
static constexpr unsigned BDEV_WAL = 0;
static constexpr unsigned BDEV_DB = 1;
static constexpr unsigned BDEV_SLOW = 2;
static constexpr unsigned BDEV_NEWWAL = 3;
static constexpr unsigned BDEV_NEWDB = 4;
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; // 脏文件,根据序列号排列
// 文件系统超级块和日志
bluefs_super_t super;
bluefs_transaction_t log_t;
// 结构体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在加载时,会根据日志中记录的内容,在metadata的dir_map和file_map中添加、删除或修改相应的条目;
- 在BlueFS的使用过程中,也会不断变更此映射结构;
- BlueFS在定位一个具体的文件时会在内存中经过两次查找
- 第一次通过dir_map找到文件所在的最底层文件夹,
- 第二次通过该文件夹下的file_map找到对应的文件。
- 此外内存中还会记录一个dirty_files的列表,记录发生更改,但还未sync的文件集合(因为BlueFS的更新操作只有在调用sync时才会真正更改)。
内存元数据加载详见Ceph BlueStore:BlueFS元数据恢复replay流程详解 + 元数据encode/decode一文。
参考文献
http://blog.wjin.org/posts/ceph-bluestore-bluefs.html
https://zhuanlan.zhihu.com/p/46362124