ViveNAS - 一个基于LSM tree的文件存储实现 (一)

news2024/11/25 2:34:04

 1. ViveNAS (GitHub - cocalele/ViveNAS)

ViveNAS 是一个开源分布式的网络文件系统(NAS), 具有下面的特点:
 - 通过不同存储介质的结合,在高性能、低成本间寻找动态的平衡
 - 解决数据的长期、低成本存储问题,支持磁带,SMR HDD等低成本介质,以及EC
 - 为CXL内存池、SCM等新技术的应用做好准备,并应用这些新技术产生澎湃性能,服务热点数据
 - 解决小文件存储难题
 - 为企业存储提供受控的分布式策略,以解决传统分布式存储在扩容、均衡、故障恢复时面临的各种难题
 - 绿色存储,充分利用数据中心超配而不能充分利用的内存、CPU资源提供服务,降低能源消耗

ViveNAS目前版本提供标准NFS接口。


ViveNAS提供上述能力的核心技术依赖于下列两项:

核心1,PureFlash 分布式SAN存储

   PureFlash 提供了我们这个存储系统所有跟分布式有关的特性,包括高可用机制、故障恢复机制、存储虚拟化、快照、克隆等。

   PureFlash是一个分布式的ServerSAN存储系统,他的核心思想继承自NetBRIC S5,一个以全FPGA硬件实现的全闪存储系统,因此PureFlash拥有一个极度简单的IO栈,最小的IO开销。

   区别与以hash算法为基础的分布式系统,PureFlash的数据分布是完全人为可控的,这提供了企业存储在运行时所需的稳定能力,因为数据分布的掌控权最终在“人”而不在“机器”。更多细节请参看github.com/cocalele/PureFlash

   PureFlash支持在一个集群里管理不同的存储质,包括从NVMe SSD、HDD、磁带,以及AOF文件访问,

   上述的这些为ViveNAS提供了坚实的数据存储保障。

核心2,以LSM tree为基础的ViveFS

   LSM tree有两个重要特点,一是多层级,二是每个层级都是顺序写。

   ViveFS将level 0 放在内存或者CXL内存池里,将其他层级放在PureFlash提供的不同存储介质里,而且每个层级都是分布式且具有高可靠性。

   顺序写这个特性对磁带/smr hdd介质非常的友好,这样ViveNAS就可以将比较低层级的数据放到这些低成本介质上进行长期存储。这也是ViveNAS最重要的目标,解决冷数据的长期存储成本与访问复杂难题,
   同时顺序写的AOF文件对ec也是很友好的,通过大因子EC可以进一步降低存储成本。
   

2. ViveNAS的 层级架构


 从上图可以看到,从一个最底层的SSD介质到方便使用的NAS文件服务中间需要很多层级的协作。

本文重点介绍里面的libvivefs的实现原理。正如libvivefs在图中的位置,他的下层是LSM tree, 在当前的实现里面我直接选用了rocksdb。他的上层对接到ganesha FSAL,提供标准的文件语义接口。标准文件语义,但并不是标准Posix接口,也没有对接在linux VFS之下。libvivefs提供的API包括:


inode_no_t vn_lookup_inode_no(struct ViveFsContext* ctx, inode_no_t parent_inode_no, const char* file_name, /*out*/struct  ViveInode** inode);
inode_no_t vn_create_file(struct ViveFsContext* ctx, inode_no_t parent_inode_no, const char* file_name, 
	                      int16_t mode, int16_t uid, int16_t gid, /*out*/ struct ViveInode** inode_out);
struct ViveFile* vn_open_file(struct ViveFsContext* ctx, inode_no_t parent_inode_no, const char* file_name, int32_t flags, int16_t mode);
struct ViveFile* vn_open_file_by_inode(struct ViveFsContext* ctx, struct ViveInode* inode, int32_t flags, int16_t mode);
size_t vn_write(struct ViveFsContext* ctx, struct ViveFile* file, const char* in_buf, size_t len, off_t offset);
size_t vn_writev(struct ViveFsContext* ctx, struct ViveFile* file, struct iovec in_iov[], int iov_cnt, off_t offset);
size_t vn_read(struct ViveFsContext* ctx, struct ViveFile* file, char* out_buf, size_t len, off_t offset);
size_t vn_readv(struct ViveFsContext* ctx, struct ViveFile* file, struct iovec out_iov[] , int iov_cnt, off_t offset);
struct vn_inode_iterator* vn_begin_iterate_dir(struct ViveFsContext* ctx, int64_t parent_inode_no);
struct ViveInode* vn_next_inode(struct ViveFsContext* ctx, struct vn_inode_iterator* it, char* entry_name, size_t buf_len);
int /*as bool*/ vn_iterator_has_next(struct vn_inode_iterator* it);
void vn_release_iterator(struct ViveFsContext* ctx, struct vn_inode_iterator* it);
int vn_fsync(struct ViveFsContext* ctx, struct ViveFile* file);
int vn_close_file(struct ViveFsContext* ctx, struct ViveFile* file);
int vn_unlink(struct ViveFsContext* ctx, int64_t parent_ino, const char* fname);
int vn_rename_file(struct ViveFsContext* ctx, inode_no_t old_dir_ino, const char* old_name, inode_no_t new_dir_ino, const char* new_name);
struct ViveFsContext* vn_mount(const char* db_path);
int vn_umount(struct ViveFsContext* ctx);

里面的open/close, read/write类函数想必大家都不陌生,只是换了个名字,毕竟跟系统的标准API重名会自找麻烦。还有一些函数,包括lookup_inode, iterator, unlink这些函数,研究过linux内核文件系统实现的朋友也一定都见到过。

上面的这组接口就是libvivefs想要提供出来能力。他的下层rocksdb提供的接口也是相当的简洁:put/get, prefix_iterator, merge, transaction commit。

put/get已经能够实现数据的写入、读出。但是put/get是KV操作,是对目标对象整体的访问。对于文件语义必须要提供随机IO能力,也就是指定任意Offset, 操作任意长度的数据。libvivefs的作用就是弥合这二者间的差异。

3. libvivefs的数据布局

看一个文件系统的原理一定要看他的磁盘布局,磁盘布局决定了需要什么样的算法。磁盘布局跟介质密切相关,一定要按介质特点设计,因此针对机械盘,NAND flash, pmem, 磁带等有各种适配文件系统。

libvivefs在rocksdb里创建两个column family, 分别储存文件系统的元数据和数据。数据CF (column family)顾名思义就是文件的内容了,其K-V是这样的:

Value是文件内容按64KB大小切割的数据块加上extent_head,Key是extent的标识。

Key 由16Byte的binary构成,结构如下:

struct vn_extent_key{
     union { 
          struct {
               _le64 extent_index;
               _le64 inode_no;
          };
          char keybuf[16];
      }
}

`vn_extent_key` 结构里的两个关键变量:

inode_no: 也就是Inode号,这和其他文件系统对inode号的定义一样,唯一标识一个文件,并且不会随着文件改名而变化。

extent_index:表示这个数据块是文件的第几个数据块。也就是说这些数据块顺次保存了文件的内容。

extent_head的构成如下:

struct vn_extent_head {
	int8_t flags;
	int8_t pad0;
	union {
		uint16_t data_bmp;
		uint16_t merge_off;
	};
	char pad1[12];
};

有了extent_head的存在就能够支持任意长度的写入,而不必等凑满整个extent才能写入。这个能力也是借助了rocksdb的merge操作实现的。

反正LSM-tree是需要对数据进行compaction,因此写入的时候没必要每次都进行RMW(Read-Modify-Write),只需要将改动的部分数据写入。在compaction或者read的时候,会触发merge操作将同一个key的不同数据分片、版本合并成一个完整的extent。extent_head里面的信息在merge操作时会被用到。

4. write操作的实现

有了上面的数据布局知识,write操作的实现就很明显了,将数据按照在文件中的位置找到对应的extent,按照写入是否写满了整个extent分别调用mege或者put操作。

size_t vn_write(struct ViveFsContext* ctx, struct ViveFile* file, const char * in_buf, size_t len, off_t offset )
{
	int64_t start_ext = offset / file->inode->i_extent_size;
	int64_t end_ext = (offset + len + file->inode->i_extent_size - 1) / file->inode->i_extent_size;
	void* buf = malloc(file->inode->i_extent_size + PFS_EXTENT_HEAD_SIZE);
	
	S5LOG_DEBUG("call vn_write, len:%ld off:%ld", len, offset);
	DeferCall _1([buf]() {free(buf); });
	struct vn_extent_head* head = (struct vn_extent_head*)buf;
	Transaction* tx = ctx->db->BeginTransaction(ctx->data_opt);
	DeferCall _2([tx]() {delete tx; });
	Cleaner _c;
	Status s;
	_c.push_back([tx]() {tx->Rollback(); });

	int64_t buf_offset = 0;

	for (int64_t index = start_ext; index < end_ext; index++) {

		//string ext_key = format_string("%ld_%ld", file->i_no, index);
		vn_extent_key ext_key = { {{extent_index: (__le64)index, inode_no : (__le64)file->i_no}} };

		int64_t start_off = (offset + buf_offset) % file->inode->i_extent_size; //offset in extent
		size_t segment_len = std::min(len - buf_offset, (size_t)file->inode->i_extent_size - start_off);
		
		*head = { 0 };
		memcpy((char*)buf + PFS_EXTENT_HEAD_SIZE, in_buf + buf_offset, segment_len);
		
		Slice segment_data((char*)buf, segment_len + PFS_EXTENT_HEAD_SIZE);
		if(segment_len != file->inode->i_extent_size){
            //不是满条带,就执行Merge操作
			head->merge_off = (uint16_t)start_off;
			s = tx->Merge(ctx->data_cf, Slice((const char*)&ext_key, sizeof(ext_key)), segment_data);
		} else {
            //满条带,就执行Put操作
			head->data_bmp = (uint16_t)start_off; 
			s = tx->Put(ctx->data_cf, Slice((const char*)&ext_key, sizeof(ext_key)), segment_data);
		}
		
		buf_offset += segment_len;
	}
	
	file->inode->i_mtime = time(NULL);
	file->dirty = 1;
	
	if (offset + len > file->inode->i_size) {
        //如果文件长度也发生了改变,就更新长度。 否则会现在lazy 模式更新元数据,也就是上面的修改时间不会每次更新。
		file->inode->i_size = offset + len;
		s = _vn_persist_inode(ctx, tx, file->inode);
		
		file->dirty = 0;
	}
	s = tx->Commit();

	_c.cancel_all();
	return len;
}

read操作的实现逻辑和write相似且更简单。

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

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

相关文章

JVM-0428

执行引擎 执行引擎做什么的 执行引擎是Java虚拟机核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念&#xff0c;这两种机器都有代码执行能力&#xff0c;其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的&#xff0c;而虚拟机的执行引…

java基础入门-03

Java基础入门-03 10、字符串10.1.API10.1.1API概述10.1.2如何使用API帮助文档 10.2.String类10.2.1 String类概述10.2.2 String类的特点10.2.3 String类的构造方法10.2.4 创建字符串对象两种方式的区别10.2.5 字符串的比较10.2.5.1 号的作用10.2.5.2 equals方法的作用 10.2.6 用…

java web会话管理

在人机交互过程中&#xff0c;会话管理是指保持用户的整个会话活动的交互与计算机系统跟踪的过程。会话管理分为桌面会话管理、浏览器会话管理、Web会话管理。本书讨论的是Web会话管理(通常指的是session以及Cookie) &#xff0c; 也称为会话跟踪。 会话管理基本原理 使用隐藏…

机器学习笔记 图像特征提取器(卷积变体)的技术发展与演变

一、图像特征提取器简述 图像特征提取器是可用于从图像中学习表示的函数或模块。最常见的特征提取器类型是卷积,其中内核在图像上滑动,允许参数共享和平移不变性。 在深度学习技术的快速发展过程中,基于卷积也演变出来了若干新技术由于图像特征的提取,这里进行了一下简单梳…

[oeasy]python0145_版本控制_git_备份还原

git版本控制 回忆上次内容 上次我们了解了 try 的完全体 try 尝试运行 except 发现异常时运行的代码块 else 没有发现异常时运行的代码块 finally 无论是否发现异常最终都要运行的代码块 发现导入部分 可以再分为两个子模块一个输入 a一个输入 b 可以再拆分么&#xff1f;&…

【Python基础练习100题--第一篇:文件篇】

前言 这些题都是在B站的练习题&#xff0c;链接在这 对于刚学python的新手来说十分的适合&#xff0c; 可以加强和巩固我们的基础。 嘿嘿 一起噶油吧&#xff01;&#x1f349; &#x1f349;1.对学生成绩排序 # 这里对字典进行排序&#xff0c;同事使用到了sorted函数 # 这…

【安全工具】Httpx信息收集

文章目录 前言一、下载二、使用步骤1.帮助文档2.常用命令常用组合命令&#xff1a; 总结 前言 HTTPX 是一个功能强大的 HTTP 客户端工具&#xff0c;用于执行各种网络任务&#xff0c;例如发现 Web 应用程序漏洞、探测域名和端口等。它使用了 retryablehttp 库来运行多个探测器…

[计算机图形学]相机与透镜(前瞻预习/复习回顾)

一、相机 1.成像方法 成像方法有两种&#xff0c;合成和捕捉&#xff0c;我们之前所说的光栅化和光线追踪都属于合成的方法&#xff0c;也就是实际上不存在的东西。而另一种成像方法叫做捕捉成像&#xff0c;也就是把真实世界中存在的一些东西变成照片这就是捕捉成像&#xf…

【量化课程】01_投资与量化投资

文章目录 1.1 什么是投资1.1.1 经济意义上的投资1.1.2 投资的分类1.1.3 金融投资1.1.4 个人投资者投资品种1.1.5 投资VS投机 1.2 股票投资的基本流程1.3 常见的股票投资分析流派1.3.1 投资者分析流派 1.4 什么是量化投资1.4.1 量化投资基本概念1.4.2 量化投资的优势1.4.3 量化投…

C多线程、锁、同步、信号量

文章目录 一 线程函数1.1 创建线程1.2 线程退出1.3 线程回收1.4 线程分离&#xff1a;1.5 其他线程函数1.5.1 线程取消1.5.2 线程ID比较 二 线程同步2.1 互斥锁2.1.1定义2.1.2 初始化2.1.3 销毁2.1.4 加锁 、 常试锁、解锁2.1.5 互斥锁使用 2.2 死锁2.3 如何避免死锁2.4 读写锁…

05_从0运行,重定位,初始化,中断再到定时器

总结 这边简单讲讲,代码上电后从0开始发生了什么,为什么要重定位把代码复制到sdram, bin文件前面几条跳转函数都跳转去哪 中断产生后发生什么 重定位问题 1.为什么需要重定位 把程序从一个位置移到另一个位置 叫重定位 可以只重定位部分段的数据 也可以把所有的都重定位到sd…

Linux运维之shell基础

一.流程控制 1.if判断 基本语法&#xff1a; if [ 条件判断式 ] then 程序 elif [ 条件判断式 ] then 程序 else 程序 fi注意事项&#xff1a; ①[ 条件判断式 ]&#xff0c;中括号和条件判断式之间必须有空格②if 后要有空格 例如&#xff0c;现在写一个if.sh脚本文件 #…

echarts关于自定义饼图数据刷新和颜色渲染问题

在使用echarts的自定义饼图Customized Pie时&#xff0c;定义的动态数据会发生颜色无法渲染的问题&#xff0c;如下图所示&#xff1a; 该图表的颜色是根据itemStyle内的color属性而来&#xff0c;如下&#xff1a; itemStyle: {color: #4d90fe, /* 图表的颜色 */shadowBlur:…

【python知识】importlib包详解

importlib — The implementation of import — Python 3.11.3 documentation 目录 一、说明 二、 模块导入简介 2.1 最简单的 importlib用途 2.2 importlib 包的目的有三个 2.3 import_module() 和__import__() 三、高级模块使用 3.1 动态引入 3.2 模块引入检查 3…

SpringBoot整合Mybatis-Plus、Jwt实现登录token设置

Spring Boot整合Mybatis-plus实现登录常常需要使用JWT来生成用户的token并设置用户权限的拦截器。本文将为您介绍JWT的核心讲解、示例代码和使用规范&#xff0c;以及如何实现token的生成和拦截器的使用。 一、JWT的核心讲解 JWT&#xff08;JSON Web Token&#xff09;是一种…

JavaWeb——HTML和CSS

HTML和CSS定义 标记语言 :比如XML:可扩展的标记语言&#xff0c;标签可以自己定义&#xff0c;解析时需要按照定义的规则去解析。 学习目的:掌握常见标签和常见样式的使用 HTML 结构: 特点: 1.不区分大小写&#xff0c;不管是<html>还是<HTML>都是一样的作用 …

错题笔记第一篇

目录 1. strlen的用法2. case3. switch4. 二分查找 1. strlen的用法 正确答案 &#xff1a;C strlen计算的是字符串的长度&#xff0c;二字符串是以\0结尾&#xff0c;而咱们并没有存储\0&#xff0c;后序的空间是未知的&#xff0c;strlen找不到\0就会一直找&#xff0c;所以它…

如何使 VSCode 中 CMake Debug 的输出显示在 cmd 上而不是自带的 debug console

如何使 VSCode 中 CMake Debug 的输出显示在 cmd 上而不是自带的 debug console 首先需要明确的一点是从 VSCode 插件商店下载的 CMake 是默认打印输出的结果在 debug console 中的&#xff0c;就像下面这样&#xff1a; 可以看到&#xff0c;一个问题是在加载 dll 时候会频繁…

82. Python split方法-分割字符串

82. split方法-分割字符串 文章目录 82. split方法-分割字符串1. 什么是split( )函数2. split( )方法的语法格式如下&#xff1a;3. 实操练习4. 列表索引取值知识回顾5. 用split方法分解网址提取有效信息6. 从地址信息中拆分省、市、区信息 1. 什么是split( )函数 split[splɪ…

深度学习模型压缩与优化加速

1. 简介 深度学习&#xff08;Deep Learning&#xff09;因其计算复杂度或参数冗余&#xff0c;在一些场景和设备上限制了相应的模型部署&#xff0c;需要借助模型压缩、系统优化加速、异构计算等方法突破瓶颈&#xff0c;即分别在算法模型、计算图或算子优化以及硬件加速等层…