Blobstore 介绍

blobstore位置

在这里插入图片描述

Blobstore

Blobstore是位于SPDK bdev之上的Blob管理层,支持更上层的服务,如数据库MySQL、K-V存储引擎Rocksdb以及分布式存储系统Ceph、Cassandra等。
在Blobstore下层,与SPDK bdev层对接。SPDK bdev层,是对底层不同类型设备的统一抽象管理,例如NVMe bdev、Malloc bdev、AIO bdev等。bloblstore通过bdev提供驱动接口来进行数据持久化,比如我向blob中写入数据,blob就会将数据存入绑定的bdev 设备NVMe SSD的。

特点:
  1. SPDK BlobStore 提供了一个相比纯粹块接口,更佳丰富的语意,方便应用使用,同时,它的特点是利用了 SPDK 进行高性能访问写入。

  2. Blobstore实现了对磁盘空间分配的动态管理,并保证断电不丢失数据,因此Blob具有持久性特性。Blobstore中的配置信息与数据信息均在super block与metadata region中管理,在重启后,若要保持持久性,可以通过Blobstore中所提供的load操作。

应用举例

通过一个简单应用举例来说明blob的使用。
Blobstore Filesystem (BlobFS)是基于Blobstore实现的轻量级文件系统,可以理解为是一个简陋版的文件系统。,对Blobstore进行封装,提供一些文件的常用接口,如read、write、open、sync等,其目的在于作为文件系统支持更上层的应用,例如Rocksdb。他的本质还是blobstore,命名叫blobFS。将对文件的操作转换为对Blob的操作,BlobFS中的文件与Blobstore中的Blob一一对应。

以Rocksdb为一个例子,通过BlobFS作为Rocksdb的存储后端,I/O经由BlobFS与Blobstore下发到bdev,随后由SPDK提供的接口通过NVME驱动,用户态写入磁盘。整个I/O流从发起到落盘均在用户态操作。此外,可以充分利用SPDK所提供的异步、无锁化、Zero Copy、轮询等机制,大幅度减少额外的系统开销。
在这里插入图片描述

blob

什么是blob

为了避免混淆,下面我们不使用文件对象,而使用术语“blob”。
Blobstore的设计可以在一个块设备上,进行异步、未压缩、并行读写,这个块设备会被分为很多组块,这些块称为“blobs”。
一个blob通常是比较大的,以至少数百KB的大小,并且总是底层块(实际物理层块)大小的倍数。

对上层来说,每个blob就是一个可存储的数据块,比如上层要创建一个a.txt文件,那么上层就可以调用创建blob接口,创建并打开一个blob,然后把a.txt文件放到这个blob中。(事先需要指定创建的blob大小还是自动分配?)

Blobstore实现了对Blob的管理,包括Blob的分配、删除、读取、写入、元数据的管理等。
每个blob会调用spdk的接口来对接后端块设备,比如NVME的SSD,或者AIO文件系统,具体存到哪看选用的是哪种存储介质。

blob的存储结构

从小到大
在这里插入图片描述

  • logic block:逻辑块由磁盘本身暴露出来,编号从0到N,其中N是磁盘中的块数。逻辑块通常是512B或4KiB。实际硬盘存储的基本单位。
  • page:在blobstore创建时,我们需要固定一个page中有多少个logic block(比如10个)并且这些logic block要是连续的。page也从磁盘开头开始编号,说白了就是把多个logic block组成一组。比如通常我们一个page大小为4K,而一个logic block是512B,那么我们一个page中就有8个logic block。page是读写的基本单位
  • cluster:创建blobstore时,我们还需要固定一个cluster中有多少个page,其实跟上面的操作一样,pages也是连续的,就是继续把page组合。比如:一个cluster默认是1M大小,那么他其中包含256个page。cluster的值需要是page的整数倍。
  • blob:每个blob由多个clusters组成,但是cluster不一定是连续的,也就是多个不同位置的cluster可以组成一个blob。blob被应用操作(创建,删除,调整大小等)。当应用创建了blob后,我们不会创建一个blob的名称,而是由blobstore给我们返回一个唯一标识符(ID),后面我们使用这个标识符(比如id)来访问特定的blob。blob内部是以page为单位来对数据进行读写的。应用还可以用键值对的方式来向blob中存储元数据。
  • blobstore:由基于Blobstore的应用程序初始化的块设备(SSD)称为“Blobstore”。Blobstore拥有整个底层设备,该设备由私有Blobstore元数据区域,以及应用管理的所有blobs。

blob使用说明

原子性

对于所有blobstore 的操作原子性保证,都由底层来保证操作一个page的原子性。

  • 数据写入:写入的数据通过以page为单位。每个page写入之后就会保存。举例:如果一个page更新数据时断电了,那么这个page的数据将不会写入新数据。也就是他不会更新。

异步回调

blobstore是回调驱动的,如果blolbstore中的某个API不能够继续进行,他不会阻塞住其他的API。当原始调用完成后,他会返回到控制点。
有些API不提供回调参数,那么这些函数就是同步的。
回调会与调用函数在同一个线程上。

后端支持

blobstore需要一个后端存储设备,这个存储设备直接驱动在blobstore上或者bdev层上。
blobstore通过调用在初始化时提供给它的函数指针来对后备块设备执行操作。
直接集成举例:例如,SPDK NVMe驱动器可以绕过少量bdev层而直接集成到blobstore。

元数据

有两种类型的元数据:

  1. 每个blob都有自己的元数据,可以通过调用API显示的同步。
    1. blobstore的全局元数据,他会在每次正确执行完成关闭后自动同步。由于不正确的关闭可能会导致数据丢失,所以每次正确关闭流程很重要。

由于Blobstore的设计是无锁的,因此元数据操作需要隔离到单个线程,以避免对内存中的数据结构进行锁定。
在Blobstore中,这被实现为元数据线程,并被定义为应用程序对其进行元数据相关调用的线程。
应用程序需要设置一个单独的线程来进行这些调用,并确保它不会将相关IO操作与元数据操作混合。

在Blobstore中,会将cluster 0作为一个特殊的cluster。该cluster用于存放Blobtore的所有信息以及元数据

channels通道

当引用执行IO操作的时候需要使用channel,应用会对channel进行IO。
channel和线程最好是1:1数量对应关系。

错误

异步的回调方法会将一个错误值返回给回调函数,如果非0,则代表错误。
同步的方法调用如果出现错误,则会返回一个错误值。

super blob

super blob只是一个blob ID,它可以作为全局元数据的一部分存储,也可以叫root blob。
应用可以选择使用这个blob来存储Blobstore上任何需要的相关信息以及数据结构(也就是我们自定义的)。

关键结构体

struct spdk_blob
这时一个内存中的数据结构包括主要的部分比如blob id
struct spdk_blob_mut_data
这时一个每个blob的数据结构,包括spdk_blob可以定义blob本身还有一些信息,比如每个blob被分配了多少个cluser
struct spdk_blob_store
保存了整个系统blobstore的元数据等信息

惯例

  1. 下划线开头的函数,只在内部调用
  2. 包含cpl字母的函数或者结构表示回调完成。

blob实例代码流程


/*
 * We'll use this struct to gather housekeeping hello_context to pass between
 * our events and callbacks.
 */
//该类为收集信息,并作为事件回调的存储信息介质
struct hello_context_t {
	struct spdk_blob_store *bs;
	struct spdk_blob *blob;
	spdk_blob_id blobid;
	struct spdk_io_channel *channel;
	uint8_t *read_buff;
	uint8_t *write_buff;
	uint64_t io_unit_size;
	int rc;
};

/*
 * Free up memory that we allocated.
 */
static void
hello_cleanup(struct hello_context_t *hello_context)
{
	spdk_free(hello_context->read_buff);
	spdk_free(hello_context->write_buff);
	free(hello_context);
}

/*
 * Callback routine for the blobstore unload.
 */
static void
unload_complete(void *cb_arg, int bserrno)
{
	struct hello_context_t *hello_context = cb_arg;

	spdk_app_stop(hello_context->rc);
}

/*
 * Unload the blobstore, cleaning up as needed.
 */
static void
unload_bs(struct hello_context_t *hello_context, char *msg, int bserrno)
{
	if (hello_context->bs) {
		if (hello_context->channel) {
			spdk_bs_free_io_channel(hello_context->channel);
		}
		spdk_bs_unload(hello_context->bs, unload_complete, hello_context);
	} else {
		spdk_app_stop(bserrno);
	}
}

/*
 * Callback routine for the deletion of a blob.
 */
//13、删除blob完成
static void
delete_complete(void *arg1, int bserrno)
{
	struct hello_context_t *hello_context = arg1;

	SPDK_NOTICELOG("entry\n");
	/* We're all done, we can unload the blobstore. */
	unload_bs(hello_context, "", 0);
}

/*
 * Function for deleting a blob.
 */
//12、删除blob
static void
delete_blob(void *arg1, int bserrno)
{
	struct hello_context_t *hello_context = arg1;
	//先关闭blob,然后再删除该blob
	spdk_bs_delete_blob(hello_context->bs, hello_context->blobid,
			    delete_complete, hello_context);
}

/*
 * Callback function for reading a blob.
 */
//11 、读blob完成后回调
static void
read_complete(void *arg1, int bserrno)
{
	struct hello_context_t *hello_context = arg1;
	int match_res = -1;

	/* Now let's make sure things match. */
	//我们测试一下看读到的数据和我们写入的数据是否相同
	match_res = memcmp(hello_context->write_buff, hello_context->read_buff,
			   hello_context->io_unit_size);
	if (match_res) {
		unload_bs(hello_context, "Error in data compare", -1);
		return;
	} else {
		SPDK_NOTICELOG("read SUCCESS and data matches!\n");
	}

	//关闭blob,我们写完数据了,这时也就是说我们已经将数据写入到硬盘了,那么我们可以将blob关闭了。
	spdk_blob_close(hello_context->blob, delete_blob, hello_context);
}

/*
 * Function for reading a blob.
 */
//10、读blob中的数据
static void
read_blob(struct hello_context_t *hello_context)
{

	//还是分配一块内存给readbuffer
	hello_context->read_buff = spdk_malloc(hello_context->io_unit_size,
					       0x1000, NULL, SPDK_ENV_LCORE_ID_ANY,
					       SPDK_MALLOC_DMA);

	/* Issue the read and compare the results in the callback. */
	//从blob中读取数据,将读到的数据放入readbuffer
	spdk_blob_io_read(hello_context->blob, hello_context->channel,
			  hello_context->read_buff, 0, 1, read_complete,
			  hello_context);
}

/*
 * Callback function for writing a blob.
 */
//10:写数据完成后回调
static void
write_complete(void *arg1, int bserrno)
{
	struct hello_context_t *hello_context = arg1;
	/* Now let's read back what we wrote and make sure it matches. */
	//写完我们可以试一下读blob中的数据
	read_blob(hello_context);
}

/*
 * Function for writing to a blob.
 */
//9. 将数据写入blob
static void
blob_write(struct hello_context_t *hello_context)
{
	SPDK_NOTICELOG("entry\n");

	//要写的数据先要放到一个buffer中,所以我们需要分配一块内存给buffer
	hello_context->write_buff = spdk_malloc(hello_context->io_unit_size,
						0x1000, NULL, SPDK_ENV_LCORE_ID_ANY,
						SPDK_MALLOC_DMA);

	//使用c语言中memset函数,内存空间初始化
	//memset(地址指针, 值, 大小)将地址指向的区域连续大小的内存区域填充为值
	//这一步是我们将writebuffer中的数据都填充为0x5a
	memset(hello_context->write_buff, 0x5a, hello_context->io_unit_size);

	//在写操作钱我们需要分配IO
	//在分配IO的时候我们需要将IO分配到channel上
	hello_context->channel = spdk_bs_alloc_io_channel(hello_context->bs);

	/* Let's perform the write, 1 io_unit at offset 0. */
	//执行blob写操作,将writebuffer中的数据写入blob
	spdk_blob_io_write(hello_context->blob, hello_context->channel,
			   hello_context->write_buff,
			   0, 1, write_complete, hello_context);
}

/*
 * Callback function for sync'ing metadata.
 */
static void
sync_complete(void *arg1, int bserrno)
{
	struct hello_context_t *hello_context = arg1;

	//blob创建并且打开,并resize了,现在我们可以想blob中写入数据了
	blob_write(hello_context);
}

// 7:blob大小设置完成回调函数
static void
resize_complete(void *cb_arg, int bserrno)
{
	struct hello_context_t *hello_context = cb_arg;
	uint64_t total = 0;

	//拿到该blob的大小,看他大小是多少个cluster
	total = spdk_blob_get_num_clusters(hello_context->blob);
	SPDK_NOTICELOG("resized blob now has USED clusters of %" PRIu64 "\n",
		       total);

	//手动同步blob中的元数据,当blob关闭时也会自动完成该动作。
	spdk_blob_sync_md(hello_context->blob, sync_complete, hello_context);
}

// 6
/*
 * Callback function for opening a blob.
 */
static void
open_complete(void *cb_arg, struct spdk_blob *blob, int bserrno)
{
	struct hello_context_t *hello_context = cb_arg;
	uint64_t free = 0;

	hello_context->blob = blob;
	//先查看blobstore中free cluster的数量,也就是实际容量抽象为多少个cluster
	free = spdk_bs_free_cluster_count(hello_context->bs);

	//在使用该blob之前我们需要resize一下,因为他的初始大小为0
	//第一个参数为需要重置的blob,第二个参数为设置这个blob的大小,设置的单位是cluster,也就是
	//需要设置这个blob有多少个cluster
	//这个例子中我们将blobstore中剩余的cluster整个都放入一个blob。
	spdk_blob_resize(hello_context->blob, free, resize_complete, hello_context);
}

// 5 
/*
 * Callback function for creating a blob. 
 */
static void
blob_create_complete(void *arg1, spdk_blob_id blobid, int bserrno)
{
	struct hello_context_t *hello_context = arg1;
	//创建blob后系统会返回blobID
	hello_context->blobid = blobid;
	SPDK_NOTICELOG("new blob id %" PRIu64 "\n", hello_context->blobid);
	//创建完blob后,如果要操作这个blob首先我们需要打开这个blob,打开成功后回调
	spdk_bs_open_blob(hello_context->bs, hello_context->blobid,
			  open_complete, hello_context);
}
// 4
/*
 * Function for creating a blob.
 */
static void
create_blob(struct hello_context_t *hello_context)
{
	SPDK_NOTICELOG("entry\n");
	//调用创建blob的接口函数,创建完成回调
	spdk_bs_create_blob(hello_context->bs, blob_create_complete, hello_context);
}

// 3 
/*
 * Callback function for initializing the blobstore.
 */
static void
bs_init_complete(void *cb_arg, struct spdk_blob_store *bs,
		 int bserrno)
{
	struct hello_context_t *hello_context = cb_arg;

	//Get the io unit size in bytes.
	//拿到io的单位大小
	hello_context->io_unit_size = spdk_bs_get_io_unit_size(hello_context->bs);

	//blobstore就已经初始化完成了,下面进行创建blob
	create_blob(hello_context);
}

static void
base_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev,
		   void *event_ctx)
{
	SPDK_WARNLOG("Unsupported bdev event: type %d\n", type);
}

// 2。入口函数
/*
 * Our initial event that kicks off everything from main().
 */
static void
hello_start(void *arg1)
{
	//此时传入的参数是hello_context
	struct hello_context_t *hello_context = arg1;
	struct spdk_bs_dev *bs_dev = NULL;
	int rc;

	SPDK_NOTICELOG("entry\n");

	//第一个参数指定bdev的名称,bdev可以是nvme,文件IO,或者内存
	//bdev就是决定下层使用的存储介质,比如nvme或者malloc,或者文件IO
	rc = spdk_bdev_create_bs_dev_ext("Malloc0", base_bdev_event_cb, NULL, &bs_dev);

	//创建完bdev之后,我们需要创建blobstore,与指定的bdev设备关联上
	spdk_bs_init(bs_dev, NULL, bs_init_complete, hello_context);
}

//1
int
main(int argc, char **argv)
{
	struct spdk_app_opts opts = {};
	int rc = 0;
	struct hello_context_t *hello_context = NULL;

	SPDK_NOTICELOG("entry\n");

	/* Set default values in opts structure. */
	//设置spdk的默认值
	spdk_app_opts_init(&opts);

	//设置名称
	opts.name = "hello_blob";
	//配置文件信息
	opts.json_config_file = argv[1];
	//给hello_context分配空间
	hello_context = calloc(1, sizeof(struct hello_context_t));

	//启动到hello_start函数中,并将hello_context作为参数传入
	rc = spdk_app_start(&opts, hello_start, hello_context);

}

参考文章

https://zhuanlan.zhihu.com/p/112502960
https://mp.weixin.qq.com/s/jiS3jUNLeL0XzcljF8OhQA

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Charles Ray

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值