谈谈BlueFS

news2024/11/15 15:25:54

目录

    • 前言
    • 数据结构
      • 标识一个文件
      • 文件系统的全局记录
      • 事务记录
      • 超级块
    • 启动流程
    • 磁盘管理
    • 读写流程
      • 创建文件流程
      • 为文件写数据
      • 把数据下刷到磁盘
      • 读流程
    • 参考资料

前言

BlueFS具体是个什么东西呢?
在这里插入图片描述
如上图,在Ceph里,使用BlueStore作为默认的存储引擎。
作为存储引擎,它说白了就是存储文件的,但是一般情况下,文件分为原始数据与元数据。如上图,对于原始数据,BlueStore是直接操作裸盘进行存储的;对于元数据则使用RocksDB来进行保存。
单独看RocksDB,它是一个kv存储引擎,它里面存储的数据是需要进行持久化的,理论上来说吧Rocksdb的数据也直接写到裸盘上就行,但是实际上,Rocksdb本身不能直接操作裸盘,它需要一个文件系统来作为自己和裸盘的中间层,来提供文件的读写服务。
而咱们得BlueFS就是上面提到的中间层,向下把数据通过Allocator写到磁盘上,向上通过BlueRocksENV为Rocksdb提供文件的存储与读取。
至此,咱们对BlueFC就可以下一个定义了:BlueFC是一个专门为RocksDB提供文件读写支持的文件系统。
去掉各种定语,BlueFC就是一个文件系统

数据结构

咱们从下往上讲:

标识一个文件

// 物理磁盘的位移和长度,代表块设备的一个存储区域
class AllocExtent {
	public:
		uint64_t offset; // BlockDevice的物理地址
		uint32_t length; // 长度
};

class bluefs_extent_t : public AllocExtent{
	public:
	    // 属于哪个block device
	    // block device 包括以下几种:
	    /*
	      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;
		 */
		uint8_t bdev; 
};

// 文件的inode
truct bluefs_fnode_t {
	uint64_t ino; // inode编号
	uint64_t size; // 文件大小
	utime_t mtime; // 修改时间
	uint8_t prefer_bdev; // 优先使用哪个block device
	// 注意这是一个vector! 一块数据在磁盘上,可能因为碎片的问题
	// 分布在多个并不相连的区域上
	mempool::bluefs::vector<bluefs_extent_t> extents; // 文件对应的磁盘空间
	uint64_t allocated; // 文件实际占用的空间大小,extents的length之和。应该是小于等于size
};

文件系统的全局记录

上面的代码说明了一个文件在BlueFC里面的标识,那具体作为一个文件系统,还得知道他自己管理的系统下,都有哪些文件,哪些文件夹嘛,这部分代码如下:

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; // 目录包含的文件
		};

		// 文件系统的内存映像
		// dir_map的可以就是文件夹路径 DirRef里面放着一个file_map
		mempool::bluefs::map<string, DirRef> dir_map; // 所有的目录
		// file_map 的key就是文件的fnode
		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
		......
};

事务记录

这里关于数据结构还需要一个逻辑,就是假定一个空的文件系统启动了,然后新建了3个目录,再加了5个文件。然后系统断电关闭了。那启动的时候,那之前新建的目录和文件去哪里找呢?
BlueFC针对上面的问题,使用了WAL的方式来解决。具体来说,就是把用户的每次操作都记录成日志写到磁盘上,然后每次系统启动的时候,就读一下那文件,就知道系统之前都有哪些文件了。
那具体的操作记录是什么样子的呢?

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
};

对了有一个问题,需要说明大家想想,如果我把所有的操作记录一直保存着,那不管系统多大,都放不下这么多日志。那怎么办呢?假定对一个文件,首先创建,然后多次append,最终删除了。最开始肯定是有多个操作记录的,那请问文件最终都已经删除了,还保留那么多操作有意义么?
答案是肯定的,保留已经删除的问题的操作记录是没有意义的。所有每当操作记录的体积大于某个阈值,系统就会进行操作记录的合并。所谓合并就是删除哪些不需要的操作记录。那什么是不需要的操作n呢,上面说的已经删除的文件之前的操作记录就是不需要保留的操作。

超级块

再往前追一步,系统重启了,磁盘的什么地方存放上面的操作日志呢?
如果熟悉文件系统的小伙伴应该知道,就是超级块。他固定就存放在BlueFC接管的第二个4K块里。其结构如下:

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; // 记录文件系统日志的文件
};

看完之后,咱们就可以看一下整个文件系统的静态数据结构图了:
在这里插入图片描述
journal就是我们的操作流水的记录。

启动流程

刚才已经提到超级块了,那大家就算猜也能猜出来,那这个BlueStore启动到BlueFS的时候,第一步就是去读那个超级块,然后读之前用户的操作记录流水,一步步恢复全局的dir_map和file_map。
代码基本的大致调用流程是:

BlueStore::mkfs()->
BlueStore::_open_db->
BlueFS::mount()

BlueFS::mount()的具体代码如下:

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中删除
		}
	}
	......
}


上面的get_super_offset 和get_super_length 代码如下:
看 写死了,都是4K

  // always put the super in the second 4k block.  FIXME should this be
  // block size independent?
  unsigned get_super_offset() {
    return 4096;
  }
  unsigned get_super_length() {
    return 4096;
  }

磁盘管理

文件系统对外的功能就是文件的读写与删除。但是从内部讲,来了一段数据,系统把它应该放在磁盘的哪个位置呢?这部分逻辑说白了就是文件系统的磁盘管理功能。
从逻辑上来说,BlueStore的磁盘管理使用的是位图法。

所谓位图法,就是借助一系列的比特流来表示磁盘上一个块的占据情况,例如0表示空闲,1表示已经被占用。

从实现上来说:

空闲空间列表用BitmapFreeListManager来管理
已分配空间列表用BitmapAllocator来管理

磁盘管理这块的内容比较细碎,这就不过多涉及了,大家可以参考:
https://zhuanlan.zhihu.com/p/643938193

读写流程

创建文件流程

本次分析的BlueFS的源码来自Ceph 17.2.5。
我们知道BlueFS虽然是一个文件系统,但是他的用户很单一,就只有一个RocksDB。所以写流程的起点就在Rocksdb里面。
具体调用链如下:

BlueRocksEnv::NewWritableFile
BlueFS::open_for_write

我尽量不直接贴代码,就用语言描述这个流程吧。
首先看,头文件的描述,英文写的很简单,我就不翻译了。

  // Create an object that writes to a new file with the specified
  // name.  Deletes any existing file with the same name and creates a
  // new file.  On success, stores a pointer to the new file in
    // *result and returns OK.  On failure, stores nullptr in *result and
  // returns non-OK.
  //
  // The returned file will only be accessed by one thread at a time.
    rocksdb::Status NewWritableFile(
    const std::string& fname,
    std::unique_ptr<rocksdb::WritableFile>* result,
    const rocksdb::EnvOptions& options) override;

NewWritableFile里面本身很薄,就是把fname分割成目录名和文件名,再就是把open_for_write的返回结果包装成BlueRocksWritableFile。
open_for_write里面
1 判断目录和文件是否存在,然后更新file_map
2 在操作流水里记录一个OP_DIR_LINK
3 创建文件句柄

为文件写数据

其实只是把数据写到了缓存

BlueRocksWritableFile::Append->
BlueFS::append_try_flush->
FileWriter::append->
ceph::buffer::list::page_aligned_appender::append
再强调一点,BlueFS的使用者是Rocksdb。所以append的最开始也是来源于rocksdb的append。

把数据下刷到磁盘

BlueRocksWritableFile::Sync->
BlueFS::fsync->
BlueFS::_flush_F->
BlueFS::_flush_range_F->
BlueFS::_flush_data->
KernelDevice::write

特殊的在上面的_flush_range_F里面如果发现为文件分配的大家不够了,那就需要找Allocate重新从磁盘上申请空间。

读流程

由于BlueFS的元数据都在内存中,所以读流程很简单,从内存中获取请求数据的物理位置和物理设备进行读取即可,不存在读放大。
代码流程如下:

BlueRocksRandomAccessFile::Read->
BlueFS::read_random->
BlueFS::_read_random
BlueFS::_bdev_read_random
KernelDevice::read_random

参考资料

https://blog.wjin.org/posts/ceph-bluestore-bluefs.html
https://blog.csdn.net/u014104588/article/details/87886764
https://zhuanlan.zhihu.com/p/46362124
https://zhuanlan.zhihu.com/p/643938193

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1436254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLM(大语言模型)——大模型简介

目录 概述 发展历程 大语言模型的概念 LLM的应用和影响 大模型的能力、特点 大模型的能力 涌现能力&#xff08;energent abilities&#xff09; 作为基座模型支持多元应用的能力 支持对话作为统一入口的能力 大模型的特点 常见大模型 闭源LLM&#xff08;未公开源…

datax离线同步oracle表到clickhouse实践1

时间&#xff1a;2024.01 目录1、安装启动 oracle19c 容器 2、rpm包安装clickhouse 3、datax安装 4、datax同步 目标库根据要同步的表&#xff0c;按照clickhouse建表规范建表 编写json文件 编写增量同步shell脚本&#xff0c;加入 crond 定时任务 1、安装启动 oracle19c 容器…

关于如何在Mac上安装Windows,看这篇文章就差不多了

启动转换(Boot Camp)助手可以让你在一些Mac电脑上安装Windows并引导到其中,就像在普通电脑上一样。现代苹果硅型号(M1、M2等)不支持启动转换助手,必须使用其他方法来运行Windows。 可以在M1或M2 Mac上使用启动转换助手吗 只有当Mac上有Intel(x86)处理器时,才能使用启…

Arcgis使用过程中常见问题解决方法

Arcgis无法连接数据库/数据库连接或创建失败解决方法 最近在使用arcgis过程中出现无法连接数据库或者是无法创建数据库。连接到数据库失败&#xff1b;无法创建新的数据库&#xff0c;权限被拒绝&#xff08;如下图&#xff09;。 出现这个原因是你所用的电脑系统文件dao360.…

生存类游戏《幻兽帕鲁》从部署服务器到开始体验全过程

SteamDB数据显示&#xff0c;《幻兽帕鲁》上线24小时内&#xff0c;在线人数峰值便突破200万&#xff0c;跻身Steam历史排行榜第二位。随着热度进一步发酵&#xff0c;《幻兽帕鲁》官方发布推文称&#xff0c;游戏发售不到6天&#xff0c;销量已经突破了 800万份。欢迎大家在阿…

香港公司员工遭遇2亿港币Deepfake诈骗; 阿里巴巴Qwen1.5震撼发布;通义千问App推新功能迎佳节

&#x1f989; AI新闻 &#x1f680; 香港公司员工遭遇2亿港币Deepfake诈骗 摘要&#xff1a;近日&#xff0c;香港一家英国跨国企业的员工被Deepfake技术仿造的视频诈骗&#xff0c;损失2亿港币。骗子通过伪造高管的面貌和声音&#xff0c;用视频会议的形式命令其转账&#…

双非本科准备秋招(17.1)—— 力扣二叉树

1、257. 二叉树的所有路径 要求返回根节点到叶子节点的所有路径&#xff0c;这里用前序遍历就好。 每次递归前&#xff0c;都让字符串s加上当前节点的值和“->”&#xff0c;然后判断是否为叶子节点&#xff0c;如果是的话&#xff0c;说明这条路径是一个答案&#xff0c;因…

基于springboot实现二次元商品购物系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现二次元商品购物系统演示 摘要 时代的变化速度实在超出人类的所料&#xff0c;21世纪&#xff0c;计算机已经发展到各行各业&#xff0c;各个地区&#xff0c;它的载体媒介-计算机&#xff0c;大众称之为的电脑&#xff0c;是一种特高速的科学仪器&#xff0…

python + 蒙特卡罗 = 股市神器! 能用到A股吗?

蒙特卡罗模拟作为一种在金融领域广泛应用的强大统计技术&#xff0c;能够对金融资产&#xff08;例如股票&#xff09;的行为进行模拟建模。在本文中&#xff0c;我们将深入探讨如何在Python编程环境中实现蒙特卡罗模拟&#xff0c;以预测股票市场未来可能出现的情况。我们将利…

MySQL进阶45讲【12】为什么你的MySQL偶尔会卡一下

1 前言 平时的工作中&#xff0c;不知道大家有没有遇到过这样的场景&#xff0c;一条SQL语句&#xff0c;正常执行的时候特别快&#xff0c;但是有时也不知道怎么回事&#xff0c;它就会变得特别慢&#xff0c;并且这样的场景很难复现&#xff0c;它不只随机&#xff0c;而且持…

【Linux驱动】块设备驱动(二)—— 块设备读写(使用请求队列)

块设备的操作函数并没有类似于字符驱动中的read 和write函数&#xff0c;要实现读写操作&#xff0c;只能在请求处理函数中实现。这就分为两种&#xff0c;是否要使用请求队列&#xff0c;请求队列的主要作用是管理和调度IO请求。在以下情况中&#xff0c;一般需要用到请求队队…

Kafka 使用手册

kafka3.0 文章目录 kafka3.01. 什么是kafka&#xff1f;2. kafka基础架构3. kafka集群搭建4. kafka命令行操作主题命令行【topic】生产者命令行【producer】消费者命令行【consumer】 5. kafka生产者生产者消息发送流程Producer 发送原理普通的异步发送带回调函数的异步发送同步…

虚幻5源码版打包服务端

适用情况&#xff0c;windows系统&#xff0c;已经安装vs2022之类的&#xff0c;和UE5适配的版本 源码版使用 1.下载源码版&#xff0c;推荐下载 压缩包 tar.gz那个&#xff0c;zip和git clone我老是下载不下载来&#xff0c;只是这个压缩包要解压1个多小时… 2.点击 源码的…

C语言-4

排序算法简介 /*学习内容&#xff1a;冒泡排序&#xff08;最基本的排序方法&#xff09;选择排序&#xff08;冒泡的优化&#xff09;插入排序&#xff08;在合适的位置插入合适的数据&#xff09; *//*排序分类&#xff1a;1.内部排序待需要进行排序的数据全部存放到内存中&…

【Python基础】案例分析:电商分析

电商分析 1 案例:某年淘宝双12部分购物数据 1.1 数据&#xff1a;某年淘宝双12部分购物数据&#xff1b; 来源&#xff1a;天池数据平台 1.2 分析目的&#xff1a; 熟悉电商常用分析指标用户行为分析用户价值模型 2 熟悉数据 2.1 导入数据 import pandas as pd fpath …

Vscode编译运行多个C++文件

1.摘要 在使用Vscode编译单个文件网上很多教程&#xff0c;但是对多个文件编译会发现经常出问题&#xff0c;通过不断的借阅网友的教程改进终于完成C运行多个文件教程如下&#xff1a; 2.编译运行过程 2.1 初始配置过程 &#xff08;1&#xff09;Vscode以及MinGW配置教程很…

EasyRecovery数据恢复软件2024最新特别绿色版下载

EasyRecovery数据恢复软件是一款功能强大的工具&#xff0c;最新版本通常包含更多优化和新增功能&#xff0c;以提升用户体验和数据恢复成功率。由于软件版本会不断更新&#xff0c;我无法提供特定于最新版本的详细介绍&#xff0c;但我可以概述EasyRecovery的一般功能以及下载…

20240206在WIN10下下载安装RX580的驱动程序

20240206在WIN10下下载安装RX580的驱动程序 2024/2/6 19:24 百度搜索&#xff1a;RX580 驱动 我用的是X99主板的渲染服务器&#xff0c;只能装WIN10的64位系统。直接下载并安装最新的驱动程序就可以了&#xff01; 另&#xff1a;我以前的电脑插的是RX550的显卡&#xff0c;直接…

推动海外云手机发展的几个因素

随着科技的不断发展&#xff0c;海外云手机作为一种新兴技术&#xff0c;在未来呈现出令人瞩目的发展趋势。本文将在用户需求、技术创新和全球市场前景等方面&#xff0c;探讨海外云手机在未来的发展。 1. 用户需求的引领&#xff1a; 随着人们对移动性和便捷性的需求不断增长&…

【数据结构和算法】--- 基于c语言排序算法的实现(1)

目录 一、排序的概念及其应用1.1排序的概念1.2 排序的应用1.3 常见的排序算法 二、插入排序2.1直接插入排序2.2 希尔排序2.2.1 预排序2.2.2 缩小gap2.2.3 小结 三、选择排序3.1 直接选择排序3.2 堆排序 一、排序的概念及其应用 1.1排序的概念 排序&#xff1a; 所谓排序&…