1.大文件系统背景
对于需要存储大量数据的平台,数据不可能只保存在普通的单个文件或单台服务器中。
原因:
EMC高端存储一台上百万元,但是两台也才不到一百个T,07年以前高端小型存储,后去IOE化(以前系统里面使用了很多IBM的小型机,oracle的数据库),EMC的存储,成本太高,解决不了海量存储的问题。
针对于海量非结构化数据(图片等)的存储,淘宝设计出了一款分布式文件系统TFS,其构筑在普通的linux集群上,可以为外部提高高可靠和高并发的存储访问。
结构化数据一般是存储在数据库中,但是对于图片等非结构化数据一般存储在文件系统中。
2.什么是文件系统
文件系统是一种把数据组织成文件和目录的存储方式。
(1)文件系统接口
文件系统,提供了基于文件的存取接口,并通过文件权限控制访问。
文件系统由操作系统管控,与应用程序交互属于逻辑IO,与磁盘进行交互属于物理IO。
下图为文件系统的层次结构:
(2)文件系统存储单位
对于硬盘来说,扇区是其最小的存储单位。一般每个扇区存储512字节(相当于0.5KB)
磁盘的每一面被分为很多条磁道,一个“圈”就是一个磁道,最内侧磁道上的扇区面积最小,因此数据密度最大。
一个磁道又被划分成一个个的扇区,每一个扇区就是一个“磁盘块”。
对于文件来说,文件存取的最小单位是“块”,其大小最常见的是8*512B = 4KB,即8个连续的扇区组成一个块。
(3)文件系统的访问
操作系统格式化分区时自动将硬盘分成3个区域。
- 目录项区–存放目录下文件的列表信息
- 数据区–存放文件数据
- inode区–存放inode所包含的信息
为什么是这样分区?(扩展)
一个文件对应一个FCB文件控制块,FCB的有序集合称为“文件目录”,一个FCB就是一个文件目录项。 FCB包括其文件的文件名、文件存放的物理地址等。FCB实现了文件名和文件之间的映射。使得用户可以实现“按名存取”。
在没有索引节点时,查找各级目录找到对应匹配的文件名时,需要读出该文件的其他信息,这样会使得目录项区域大小过大。
如:
假设一个FCB大小是64B,磁盘块的大小为1KB,则每个盘块中只能存放16个FCB。若一个文件目录中共有640个目录项,则一共需要占用640/16=40个盘块。如果按照某文件名检索该目录,平均需要查询320个目录项,平均需要启动磁盘20次(每个磁盘I/O读入一块)。
但是如果使用索引节点机制,文件名占14B,索引结点指针占2B,则每个盘口可存放64个目录项,那么按文件名检索目录评价只需要读入320/64=5个盘块。显然,这样将大大提升文件检索速度。
当找到文件名对应的目录项时,才需要将索引结点调入内存,索引结点中记录了文件的各种信息,包括文件在外存中的存放位置,根据“存放位置”即可找到文件。
再补充一下索引节点inode:
索引节点,存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
使用ls -i即可查看inode号。
inode节点大小 - 一般是128字节或256字节。inode节点的总数,格式化时就给定了,一般是每1KB或2KB就设置一个inode。在一块1GB的硬盘中,每1KB就设置一个inode,每个inode大小为128B,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
系统读取文件的方式
左边为没有使用索引节点机制时的系统读取文件,右边为使用了索引节点机制时系统读取文件。
现在一般的操作系统都是使用索引节点机制。
3.海量存储使用小文件存储的缺点
如果选用普通文件存储海量小数据,会引发以下问题:
-
大规模的小文件存取,磁头需要频繁的寻道和换道,因此在读取上容易带来较长的延时。
查找一次文件需要三次寻址换道:
那么如果是海量小文件,不停地寻址换道,会浪费发送几十上百MB数据的时间。 -
频繁的新增删除操作导致磁盘碎片,降低磁盘利用率和IO读写效率。
-
Inode占用大量的磁盘空间,降低了缓存的效果。
4.大文件存储引擎的设计思路
本引擎的核心技术:内存映射和哈希存储引擎。
实现思路:
- 以block“块”文件的形式存放数据文件(一般64M一个block),每一个块都有唯一的一个整数编号,块在使用之前所有用到的存储空间都会预先分配和初始化。
- 每一个块由一个索引文件、一个主块文件和若干个扩展块组成,“小文件”主要存放在主块中,扩展块主要用来存放溢出的数据。
- 每个索引文件存放对应的块信息和“小文件”索引信息,索引文件会在服务启动时映射(mmap)到内存,以便极大的提高文件检索速度。“小文件”索引信息采用在索引文件中的数据结构哈希链表来实现。
- 每个文件都有对应的文件编号,文件编号从1开始,依次递增,同时作为哈希查找算法的key来定位“小文件”在主块和扩展块中的偏移量。 文件编号+块编号按照某种算法可得到“小文件”对应的文件名。
原理图
文件映射
文件映射一般用于进程间共享信息、实现文件数据从磁盘到内存的映射,极大提升应用程序访问文件的速度。
具体可以看后续的另一篇文章:内存映射
1.哈希链表
系统根据索引文件中的文件编号快速定位到相应的主块中的小文件就是使用的哈希链表,每个链表节点中都保存自己本身的文件编号(key)和下一个节点的位置等信息。具体可以看后面索引节点MetaInfo的结构。
2.大文件存储结构图
上面已经说到每一个块都是由一个索引文件、一个主块文件和一个扩展块所构成,具体结构见下图。
在本文中,每次新建一个块,都会有其对应的索引文件类IndexHandle、主块文件类BlockInfo来进行相关的初始化或加载等处理。
3.文件哈希链表实现图(文件哈希索引块)
上面的左边索引IndexHandle图,可以看到前六个可以组成索引头部IndexHeader,后面的每一个文件哈希索引块MetaInfo就是记录对应的小文件在主块中的一些信息。
注意结合上面说到的哈希链表结构,这里前几个MetaInfo实际上是哈希桶,即保存本桶的首个MetaInfo的位置(偏移量)。也就是说如果只有一个小文件,那么哈希桶里面就是保存的这个小文件在索引文件中的偏移位置,如果有多个小文件,就要根据key值(文件编号),定位到对应的哈希桶,然后从哈希桶存的首节点进行查找,直到找到一样key值的MetaInfo则成功。
看看TFS中给的索引文件结构示意图:
关键数据结构与系统函数
1. 块结构:
解释:当前版本号是因为服务器有多台,各个版本可能不一样。
2. 索引节点(即就是日常的文件)结构:
3. 文件映射相关函数:
文件映射mmap函数:
内存磁盘同步msync函数:
重新映射mremap函数:
扩大(或缩小,一般扩大)现有的内存映射
5.大文件存储引擎的实现
内存映射MMapFile类
本类主要做的事情和流程:
可以指定一个文件名,用open系统函数打开拿到fd文件句柄,然后将文件句柄传给内存映射类构造函数进行该类的一些初始化,接着利用map_file进行文件的内存映射操作,并在该函数中利用mmap系统函数拿到映射成功的内存首地址,且注意在该函数中也会利用ensure_file_size进行磁盘的扩容。在映射成功后,还可以测试重新映射方法,同步内容方法和解除映射方法,最后关闭句柄。
mmap_file.h
三个内存初始化构造函数:(一般使用第三个)
- MMapFile();
- explicit MMapFile(const int fd);
- MMapFile(const MMapSizeOption& mmap_size_option,const int fd);
文件映射到内存相关函数:
- bool map_file(const bool write=false);//进行文件内存映射操作,同时设置映射区的保护方式
- void* get_data() const; //拿到映射成功的内存首地址
- int32_t get_size() const;//拿到映射数据的大小
- bool munmap_file(); //解除映射
- bool remap_file(); //重新映射(扩大/缩小现有映射的内存大小)
内存同步到文件磁盘相关函数:
- bool sync_file(); //同步
- bool ensure_file_size(const int32_t size);//磁盘扩容(private)
内存映射的属性
- int32_t size_;//映射数据的大小
- int fd_; //文件句柄
- void* data_;//映射的内存首地址
- struct MMapSizeOption mmapfile_size_option;
struct MMapSizeOption
{
int32_t max_mmap_size; //最大映射大小 8M
int32_t first_mmap_size; //第一次映射的大小 4K
int32_t per_mmap_size; //每次再映射增加的映射大小 4K
};
#ifndef MMAP_FILE_H
#define MMAP_FILE_H
#include<unistd.h>
#include"common.h"
namespace program
{
namespace largefile
{
//设定三个映射类型的大小
struct MMapSizeOption
{
int32_t max_mmap_size; //最大映射大小 8M
int32_t first_mmap_size; //第一次映射的大小 4K
int32_t per_mmap_size; //每次再映射增加的映射大小 4K
};
class MMapFile
{
public:
MMapFile();
explicit MMapFile(const int fd); //避免隐式构造(避免一个参数的,带来歧义)
MMapFile(const MMapSizeOption& mmap_size_option,const int fd);
~MMapFile();
//文件映射到内存-------------------------------------------------------------
//进行内存映射一定要fd
bool map_file(const bool write=false);//进行文件内存映射操作,同时设置访问权限
void* get_data() const;//拿到内存映射成功的这部分数据
int32_t get_size() const;//拿到映射数据的大小
bool munmap_file(); //解除映射
bool remap_file(); //重新映射
//内存映射到文件,即内存内容同步到磁盘-------------------------------------------
bool sync_file(); //同步文件
private:
bool ensure_file_size(const int32_t size);//磁盘扩容
//内存映射的属性
private:
int32_t size_;
int fd_;
void* data_;
struct MMapSizeOption mmapfile_size_option;
};
}
}
#endif
mmap_file.cpp
#include"mmap_file.h"
#include<stdio.h>
static int debug = 1; //如果日志信息太多会影响性能,所以设置开关
namespace program
{
namespace largefile
{
//初始化与析构----------------------------------------------------
MMapFile::MMapFile():
size_(0),fd_(-1),data_(NULL)
{
}
MMapFile::MMapFile(const int fd):
size_(0),fd_(fd),data_(NULL)
{
}
MMapFile::MMapFile(const MMapSizeOption& mmap_size_option,const int fd):
size_(0),fd_(fd),data_(NULL)
{
mmapfile_size_option.max_mmap_size = mmap_size_option.max_mmap_size;
mmapfile_size_option.first_mmap_size = mmap_size_option.first_mmap_size;
mmapfile_size_option.per_mmap_size = mmap_size_option.per_mmap_size;
}
MMapFile::~MMapFile()
{
//如果还有数据
if(data_)
{
if(debug) printf("mmap_file destruct,fd:%d,data:%p,maped_size:%d\n",fd_,data_,size_);
msync(data_,size_,MS_SYNC); //内存与磁盘同步
munmap(data_,size_); //解除映射
size_ = 0;
data_ = NULL;
fd_ = -1;
mmapfile_size_option.max_mmap_size = 0;
mmapfile_size_option.first_mmap_size = 0;
mmapfile_size_option.per_mmap_size = 0;
}
}
//内存映射到文件(异步)是否成功--------------------------------------------------
bool MMapFile::sync_file()
{
if(NULL!=data_&&size_>0)
{
return msync(data_,size_,MS_ASYNC)==0; //同步成功否
}
return true; //没有数据需要同步,也算同步成功
}
//文件映射到内存----------------------------------------------------------------
bool MMapFile::map_file(const bool write)//进行文件内存映射操作,同时设置访问权限
{
int flags = PROT_READ;
if(write)
{
flags |=PROT_WRITE;
}
if((fd_ < 0) && (0 == mmapfile_size_option.max_mmap_size))
{
return false;
}
//对于内存映射的大小设定,这里涉及到硬盘文件与内存大小的同步
if(size_ < mmapfile_size_option.max_mmap_size)
{
size_ = mmapfile_size_option.first_mmap_size;
}
else
{
size_ = mmapfile_size_option.max_mmap_size;
}
//磁盘扩容
if(ensure_file_size(size_) == 0)
{
fprintf(stderr,"ensure file size failed in map_file,size:%d",size_);
return false;
}
//如果成功映射,映射到的内存首地址返回给指针
data_ = mmap(0,size_,flags,MAP_SHARED,fd_,0);
if(MAP_FAILED == data_)
{
fprintf(stderr,"map file failed: %s\n",strerror(errno)); //失败会返回错误编号errno,通过stderror拿到错误编号
size_= 0;
fd_= -1;
data_ = NULL;
return false;
}
if(debug) printf("mmap file successed,fd:%d maped size: %d,data:%p\n",fd_,size_,data_);
return true;
}
//拿到内存映射成功的这部分数据
void* MMapFile::get_data() const
{
return data_;
}
//拿到映射数据的大小
int32_t MMapFile::get_size() const
{
return size_;
}
//解除映射
bool MMapFile::munmap_file()
{
if(munmap(data_,size_) == 0)
{
return true;
}
else
{
return false;
}
}
//重新映射(扩大/缩小现有内存映射)
bool MMapFile::remap_file()
{
//防御性编程
if(fd_ < 0 || data_ == NULL)
{
fprintf(stderr,"mremap not yet\n");
return false;
}
if(size_ == mmapfile_size_option.max_mmap_size)
{
fprintf(stderr,"already mapped max size:%d,now size:%d\n",size_,mmapfile_size_option.max_mmap_size);
return false;
}
int32_t newsize = size_ + mmapfile_size_option.per_mmap_size;
//如果内存空间达到最大值,那么就不能再大了,这里涉及到硬盘文件与内存大小的同步
if(size_ > mmapfile_size_option.max_mmap_size)
{
newsize = mmapfile_size_option.max_mmap_size;
}
//要记得给磁盘也扩容
if(ensure_file_size(newsize) == 0)
{
fprintf(stderr,"ensure file size failed in map_file,size:%d",size_);
return false;
}
if(debug) printf("mremap start fd:%d,now size: %d,new size:%d, old data:%p\n",fd_,size_,newsize,data_);
//重新映射,指向一个新的地址,进行扩容,看flags,反之就是在原来的地方扩容,阔不了就是失败
void* new_map_data = mremap(data_,size_,newsize,MREMAP_MAYMOVE);
//if(MAP_FAILED == mremap(data_,size_,newsize,MREMAP_MAYMOVE))
if(MAP_FAILED == new_map_data)
{
fprintf(stderr,"mremap failed,fd:%d,new size:%d,error desc: %s\n",fd_,newsize,strerror(errno));
return false;
}
else
{
if(debug)
printf("mremap success. fd:%d,now size: %d,new size:%d, old data:%p, new data:%p\n",fd_,size_,newsize,data_,new_map_data);
}
data_ = new_map_data;
size_ = newsize;
return true;
}
//磁盘扩容
bool MMapFile::ensure_file_size(const int32_t size)
{
struct stat s; //文件的状态
//拿到文件的状态
if(fstat(fd_,&s) < 0)
{
fprintf(stderr,"fstat error,error desc: %s\n",strerror(errno));
return false;
}
//获取文件大小,如果磁盘文件小于内存,则磁盘扩容
if(s.st_size < size)
{
//调整文件的大小
if(ftruncate(fd_,size) < 0)
{
fprintf(stderr,"fstat error,size:%ld,error desc: %s\n",s.st_size,strerror(errno));
return false;
}
}
return true;
}
}
}
main.cpp测试内存映射
#include"common.h"
#include"mmap_file.h"
using namespace std;
using namespace program;
static const mode_t OPEN_MODE = 0644; //无符号整数,打开权限参数
static const largefile::MMapSizeOption mmapfile_size_option = {10240000,4096,4096};//内存映射大小参数 10M 4K 4K
int open_file(string file_name,int open_flags)
{
int fd = open(file_name.c_str(),open_flags,OPEN_MODE); //打开方式参数open_flags
if(fd < 0)
{
return -errno; //返回负数
}
return fd;
}
int main()
{
const char* filename = "./mapfile_test.txt";
//1.打开/创建一个文件,取得文件的句柄open
int fd = open_file(filename,O_RDWR|O_CREAT|O_LARGEFILE);
if(fd < 0)
{
//这里不用errno的原因:怕被覆盖,比如这里前面还有个read,出错则会重置errno
fprintf(stderr,"open file failed. filename:%s,error desc:%s\n",filename,strerror(-fd));//负负地正
return -1;
}
puts("创建fd成功");
//进行内存初始化
largefile::MMapFile* mmapfile = new largefile::MMapFile(mmapfile_size_option,fd);
puts("内存初始化成功");
//进行内存映射,设为可写
bool is_mapped = mmapfile->map_file(true);
puts("内存映射成功");
//映射成功
if(is_mapped)
{
puts("准备去分配内存空间");
//测试重新映射
mmapfile->remap_file(); //本来4K变成8K
//分配空间
memset(mmapfile->get_data(),'8',mmapfile->get_size());
//同步内容
mmapfile->sync_file();
//解除映射
mmapfile->munmap_file();
}
else
{
fprintf(stderr,"map file failed\n");
}
puts("成功");
close(fd);
return 0;
}
common.h公共头文件
为了健壮性更好,设置公共头文件
#ifndef _COMMON_H
#define _COMMON_H
#include<iostream>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h> //msync
#include<string>
#include<string.h>
#include<stdint.h> //int32_t
#include<errno.h> //errno stderr strerror
#include<stdio.h> //fprintf
#endif /*_COMMON_H*/
内存映射效果
从图中可以看到,初始分配的内存空间大小是4096即4K,起始地址是0x7fd95db5c000,
测试重新分配时,内存大小增大4K,重新到另外一个起始地址映射,而不是在原来的起始位置继续追加,根据mremap(data_,size_,newsize,MREMAP_MAYMOVE)参数设定。
最后文件mapfile_test.txt存在设定的内容。
文件操作FileOperation类
本类主要做的事情和流程:
指定一个文件名,进行文件操作对象的初始化,利用open_file函数中的open系统函数根据文件名,拿到文件句柄fd,接下来创建一个大小65字节buf,不断用buf来测试读写操作。先将buf除了结束符的64字节全置为6,写到文件的1024位置,然后将buf置空,从文件的1024位置读64字节到buf中,此时打印buf的内容应该都是6,然后再将buf全置为9,直接写入文件,会将文件的头64个字节都置为9。
file_op.h
文件操作初始化构造函数与析构函数:
- FileOperation(const std::string &filename,const int open_flags = O_RDWR|O_LARGEFILE); //记得参数的LARGEFILE
- ~FileOperation();
打开关闭文件:
- int open_file();
- void close_file();
保存删除文件:
- int flush_file(); //write方法操作系统会把文件写入缓存到内存,所以该方法将文件直接写到磁盘
- int unlink_file();
读写文件:
- virtual int pread_file(char* buf, const int32_t nbytes,const int64_t offset); //大文件,所以64位比较好。 从offset位置读nbytes到buf中
- virtual int pwrite_file(const char* buf,const int32_t nbytes,const int64_t offset); //从buf中读nbytes到offset位置
- int write_file(const char* buf,const int32_t nbytes);//在当前位置直接开始写
拿到文件大小:
- int64_t get_file_size();
截取文件:
- int ftruncate_file(const int64_t length);
文件内容定位:
- int seek_file(const int64_t offset);
拿到文件句柄:
- int get_fd() const;
查看文件是否打开,未打开则偷偷打开(比如拿到文件大小)(protected):
- int FileOperation::check_file()
文件操作的属性
- int fd_; //拿到文件描述句柄
- int open_flags_;//文件打开方式(读写、不存在则创建等)
- char* file_name_; //文件名
- static const mode_t OPEN_MODE = 0644; //文件打开方式用户权限
- static const int MAX_DISK_TIMES = 5; //读取文件失败,尝试5次就不读了(可能负载过高也可能磁盘出问题等)
#ifndef LARGE_FILE_OP_H
#define LARGE_FILE_OP_H
#include"common.h"
namespace program
{
namespace largefile
{
class FileOperation
{
public:
FileOperation(const std::string &filename,const int open_flags = O_RDWR|O_LARGEFILE);
~FileOperation();
//打开关闭
int open_file();
void close_file();
//保存删除
int flush_file(); //write方法操作系统会把文件写入缓存到内存,所以该方法将文件直接写到磁盘
int unlink_file();
//读写
virtual int pread_file(char* buf, const int32_t nbytes,const int64_t offset); //大文件,所以64位比较好
virtual int pwrite_file(const char* buf,const int32_t nbytes,const int64_t offset); //seek
int write_file(const char* buf,const int32_t nbytes);//在当前位置直接开始写
//拿到文件大小
int64_t get_file_size();
//截取文件
int ftruncate_file(const int64_t length);
//文件内容定位
int seek_file(const int64_t offset);
int get_fd() const
{
return fd_;
}
protected:
int check_file();
int fd_; //拿到文件描述句柄
int open_flags_;//文件打开方式(读写、不存在则创建等)
char* file_name_; //文件名
static const mode_t OPEN_MODE = 0644; //文件打开方式用户权限
static const int MAX_DISK_TIMES = 5; //读取文件失败,尝试5次就不读了(可能负载过高也可能磁盘出问题等)
};
}
}
#endif
file_op.cpp
#include "file_op.h"
#include "common.h"
static int debug = 1;
namespace program
{
namespace largefile
{
FileOperation::FileOperation(const std::string &filename,const int open_flags):
fd_(-1),open_flags_(open_flags)
{
//注意有分配内存
file_name_ = strdup(filename.c_str());//字符串复制,相当于重新分配一块内存,把内容复制过去
}
//关闭文件句柄,清空分配的内存
FileOperation::~FileOperation()
{
if(fd_>0)
{
::close(fd_); //说明是全局的不属于当前作用域,直接调用库文件中的函数
}
if(NULL!=file_name_)
{
free(file_name_);
file_name_ = NULL;
}
}
//打开关闭------------------------------------------------------------------------
int FileOperation::open_file()
{
//如果已经打开就先关掉再打开
if(fd_>0)
{
close(fd_);
fd_=-1;
}
fd_ = ::open(file_name_,open_flags_,OPEN_MODE); //注意这一句和下一句的顺序不能颠倒
if(fd_<0)
{
return -errno;
}
return fd_;
}
void FileOperation::close_file()
{
if(fd_<0)
{
return;
}
::close(fd_);
fd_ = -1;
}
//保存删除------------------------------------------------------------------------
int FileOperation::flush_file()
{
//O_SYNC立即同步到磁盘,成功才返回
if(open_flags_ & O_SYNC)
{
return 0;
}
//偷偷打开文件
int fd_ = check_file();
if(fd_ < 0)
{
return fd_;
}
return fsync(fd_); //内存数据写回磁盘
}
int FileOperation::unlink_file()
{
close_file();
return ::unlink(file_name_);
}
//读写------------------------------------------------------------------------
int FileOperation::pread_file(char* buf, const int32_t nbytes,const int64_t offset)
{
//方法一:read函数参数不匹配,所以还需要lseek
//方法二:直接用pread64函数(大文件),其不会自动移动文件指针(注意参数类型一定要匹配!!)
//有offset有可能不能一次性读完要多次读取!并且因为可能会出现服务器暂时无响应的情况,要多尝试几次。
int32_t left = nbytes; //剩余字节数
int64_t read_offset = offset; //起始位置偏移量
int32_t read_len = 0; //已读的长度
char* p_tmp = buf; //读到的数据位置是buf的起始位置
int i = 0;
while(left > 0)
{
++i;
if(i >= MAX_DISK_TIMES)
{
break;
}
//偷偷打开文件
if(check_file() < 0)
{
return -errno;
}
//读取
read_len = ::pread64(fd_, p_tmp, left, read_offset);
//读的大小有问题或读完了
if(read_len < 0)
{
read_len = -errno; //出错的原因保存到read_len
//这里为什么不用errno?因为errno是全局变量,多线程环境下,都可以对errno进行改变
//EINTR系统中断(繁忙)即临时出错 或 系统要求重新尝试
if(-read_len == EINTR || EAGAIN == -read_len)
{
continue;
}
//EBADF文件描述符已经坏了
else if(EBADF == -read_len)
{
fd_ = -1;
//return read_len;
continue;
}
else
{
return read_len;
}
}
else if(0 == read_len)
{
break;
}
left -= read_len;
p_tmp += read_len;
read_offset += read_len;
}
if(0 != left)
{
return EXIT_DISK_OPEN_INCOMPLETE;
}
return TFS_SUCCESS;
//return pread64(fd_, buf, size_t nbytes, offset);
}
int FileOperation::pwrite_file(const char* buf,const int32_t nbytes,const int64_t offset)
{
int32_t left = nbytes; //剩余字节数
int64_t write_offset = offset; //起始位置偏移量
int32_t written_len = 0; //已写的长度
const char* p_tmp = buf; //写到的数据位置是buf的起始位置
int i = 0;
//最多写五次
while(left > 0)
{
++i;
if(i >= MAX_DISK_TIMES)
{
break;
}
if(check_file() < 0)
{
return -errno;
}
//写
written_len = ::pwrite64(fd_, p_tmp, left, write_offset);
//写的大小有问题或读完了
if(written_len < 0)
{
written_len = -errno; //出错的原因保存到read_len
//这里为什么不用errno?因为errno是全局变量,多线程环境下,都可以对errno进行改变
//EINTR系统中断(繁忙)即临时出错 或 系统要求重新尝试
if(-written_len == EINTR || EAGAIN == -written_len)
{
continue;
}
//EBADF文件描述符已经坏了
else if(EBADF == -written_len)
{
fd_ = -1;
continue;
}
else
{
return written_len;
}
}
left -= written_len;
p_tmp += written_len;
write_offset += written_len;
}
if(0 != left)
{
return EXIT_DISK_OPEN_INCOMPLETE;
}
return TFS_SUCCESS;
}
int FileOperation::write_file(const char* buf,const int32_t nbytes)
{
//write方法是根据文件指针走的
int32_t left = nbytes; //剩余字节数
int32_t written_len = 0; //已写的长度
const char* p_tmp = buf; //写到的数据位置是buf的起始位置
int i = 0;
while(left > 0)
{
++i;
if(i >= MAX_DISK_TIMES)
{
break;
}
if(check_file() < 0)
{
return -errno;
}
//写
written_len = ::write(fd_, p_tmp, left);
//写的大小有问题或读完了
if(written_len < 0)
{
written_len = -errno; //出错的原因保存到read_len
//这里为什么不用errno?因为errno是全局变量,多线程环境下,都可以对errno进行改变
//EINTR系统中断(繁忙)即临时出错 或 系统要求重新尝试
if(-written_len == EINTR || EAGAIN == -written_len)
{
continue;
}
//EBADF文件描述符已经坏了
else if(EBADF == -written_len)
{
fd_ = -1;
continue;
}
else
{
return written_len;
}
}
left -= written_len;
p_tmp += written_len;
}
if(0 != left)
{
return EXIT_DISK_OPEN_INCOMPLETE;
}
return TFS_SUCCESS;
}
//偷偷打开文件
int FileOperation::check_file()
{
if(fd_<0)
{
fd_ = open_file();
}
return fd_;
}
//拿到文件大小
int64_t FileOperation::get_file_size()
{
//不打开文件(明面)也可以拿到文件大小
int fd = check_file();
if(fd_<0)
{
return fd_;
}
//拿到文件的状态
struct stat buf;
if(fstat(fd_,&buf) < 0)
{
fprintf(stderr,"file_op fstat error,error desc: %s\n",strerror(errno));
return -1;
}
return buf.st_size;
}
//截取文件(缩小文件)
int FileOperation::ftruncate_file(const int64_t length)
{
//偷偷打开文件
int fd = check_file();
if(fd_<0)
{
return fd_;
}
return ftruncate(fd_, length);
}
//文件内容定位
int FileOperation::seek_file(const int64_t offset)
{
//偷偷打开文件
int fd = check_file();
if(fd_<0)
{
return fd_;
}
return lseek(fd_,offset,SEEK_SET); //相对于文件头部移动
}
}
}
file_op_test.cpp测试文件操作类
#include "file_op.h"
#include "common.h"
using namespace std;
using namespace program;
int main()
{
const char* filename = "file_op.txt\n";
largefile::FileOperation* fileOP = new largefile::FileOperation(filename,O_CREAT|O_RDWR|O_LARGEFILE);
printf("ready to open file");
int fd = fileOP->open_file();
if(fd < 0)
{
fprintf(stderr,"open file failed. filename:%s,error desc:%s\n",filename,strerror(-fd));//负负得正
exit(-1);
}
printf("ready to pwrite_file,write 64bytes at 1024 position\n");
char buf[65];
memset(buf,'6',64); //将buf用6填满
int ret = fileOP->pwrite_file(buf,64,1024);//将buf的64的字节写到1024的位置
if(ret < 0)
{
if(ret == largefile::EXIT_DISK_OPEN_INCOMPLETE)
{
fprintf(stderr,"read or write length is less than required\n");
}
else
{
fprintf(stderr,"pwrite file %s failed. error desc:%s\n",filename,strerror(-ret));
}
}
printf("ready to pread_file,read 64bytes at 1024 position into buf\n");
memset(buf,0,64); //将buf置空
ret = fileOP->pread_file(buf,64,1024); //从1024的位置读64个字节到buf中
if(ret < 0)
{
if(ret == largefile::EXIT_DISK_OPEN_INCOMPLETE)
{
fprintf(stderr,"read or write length is less than required\n");
}
else
{
fprintf(stderr,"pread file %s failed. error desc:%s\n",filename,strerror(-ret));
}
}
else
{
buf[64] = '\0';
printf("read:%s\n",buf); //打印buf
}
printf("ready to write_file,write 64bytes in head\n");
memset(buf,'9',64);
ret = fileOP->write_file(buf,64); //覆盖,将buf的64个字节写到文件
if(ret < 0)
{
if(ret == largefile::EXIT_DISK_OPEN_INCOMPLETE)
{
fprintf(stderr,"read or write length is less than required\n");
}
else
{
fprintf(stderr,"write_file %s failed. error desc:%s\n",filename,strerror(-ret));
}
}
printf("close_file\n");
fileOP->close_file();
return 0;
}
文件操作效果
打开文件可以看到
文件映射操作MMapFileOperation类
本类的名字顾名思义重在操作,所以继承自文件操作类,很多操作是一样的,但是也离不开内存映射类,所以使用聚合方式,用指针形式拿到内存映射对象。
本类主要做的事情和流程:
首先还是根据给定的文件名进行初始化,然后利用父类的open_file拿到文件句柄,再调用重写的mmap_file方法,内部利用内存映射对象进行内存映射。然后定义129字节的buf,全部置为6,将buf写到内存的8偏移位置,再给buf置空,又从内存写回buf,读出buf数据,最后内存同步磁盘文件,关闭内存映射和文件句柄。
mmap_file_op.h
文件操作初始化构造函数与析构函数:
-
MMapFileOperation(const std::string &filename,const int open_flags = O_CREAT|O_RDWR|O_LARGEFILE):FileOperation(filename,open_flags),map_file_(NULL),is_mapped_(false){}//因为父类没有默认构造函数,所以要显式调用
-
~MMapFileOperation()
映射内存:
- int mmap_file(const MMapSizeOption& mmap_size_option);
- int munmap_file();
重写读写:
- int pread_file(char* buf,const int32_t size,const int64_t offset);
- int pwrite_file(const char* buf,const int32_t size,const int64_t offset);
拿到映射地址;写入磁盘
- void* get_map_data() const;
- int flush_file();
内存映射操作的属性
- MMapFile* map_file_; //要操作的文件映射对象
- bool is_mapped_; //是否已经映射,因为初始化时不一定初始化文件映射对象
#ifndef LARGEFILE_MMAPFILE_OP_H
#define LARGEFILE_MMAPFILE_OP_H
#include"common.h"
#include"file_op.h"
#include"mmap_file.h"
namespace program
{
namespace largefile
{
class MMapFileOperation:public FileOperation
{
public:
MMapFileOperation(const std::string &filename,const int open_flags = O_CREAT|O_RDWR|O_LARGEFILE):
FileOperation(filename,open_flags),map_file_(NULL),is_mapped_(false){}//因为父类没有默认构造函数,所以要显式调用
~MMapFileOperation()
{
if(map_file_)
{
delete(map_file_);
map_file_=NULL;
}
}
//映射内存
int mmap_file(const MMapSizeOption& mmap_size_option);
int munmap_file();
//重写读写
int pread_file(char* buf,const int32_t size,const int64_t offset);
int pwrite_file(const char* buf,const int32_t size,const int64_t offset);
//拿到映射地址;写入磁盘
void* get_map_data() const;
int flush_file();
private:
MMapFile* map_file_; //要操作的文件映射对象
bool is_mapped_; //是否已经映射,因为初始化时不一定初始化文件映射对象
};
}
}
#endif
mmap_file_op.cpp
#include"mmap_file_op.h"
#include"common.h"
static int debug = 1;
namespace program
{
namespace largefile
{
int MMapFileOperation::mmap_file(const MMapSizeOption& mmap_size_option)
{
//最大映射大小有问题
if(mmap_size_option.max_mmap_size < mmap_size_option.first_mmap_size)
{
return TFS_ERROR;
}
if(mmap_size_option.max_mmap_size <= 0)
{
return TFS_ERROR;
}
//偷偷打开文件失败
int fd = check_file();
if(fd < 0)
{
fprintf(stderr,"MMapFileOperation::mmap_file -- check_file failed");
return TFS_ERROR;
}
//如果没有映射,那么就映射一下
if(!is_mapped_)
{
//如果有映射对象,那么先删除后重新映射
if(map_file_)
{
delete(map_file_);
}
map_file_ = new MMapFile(mmap_size_option,fd);
is_mapped_ = map_file_->map_file(true);
}
//看映射是否成功
if(is_mapped_)
{
return TFS_SUCCESS;
}
else
{
return TFS_ERROR;
}
}
//解除映射
int MMapFileOperation::munmap_file()
{
//如果之前映射成功才需要解除映射
if(is_mapped_ && map_file_!=NULL)
{
delete(map_file_); //删除时会析构~MMapFile()
map_file_ = NULL;
}
}
void* MMapFileOperation::get_map_data() const
{
//映射了才可以拿到映射块的指针
if(is_mapped_)
{
return map_file_->get_data();
}
return NULL;
}
//重写--读写
int MMapFileOperation::pread_file(char* buf,const int32_t size,const int64_t offset)
{
//情况1.内存已经映射,要读取的数据大于映射内存空间(内存空间不够)
//注意顺序不要颠倒,尝试追加一次内存,如果还不够就算了(根据需求来,可以用while追加内存直到空间足够,不过要注意不要是死循环,比如永远满足不了)
if(is_mapped_ && (offset + size) > map_file_->get_size())
{
if(debug) fprintf(stdout, "mmap file pread, size: %d, offset: %" __PRI64_PREFIX"d, map file size: %d. need remap\n",
size, offset, map_file_->get_size());
map_file_->remap_file(); //追加内存
}
//情况1.内存已经映射,要读取的数据小于映射内存空间(内存空间够)
if(is_mapped_ && (offset + size) <= map_file_->get_size())
{
//copy到buf中
memcpy(buf,(char*)map_file_->get_data()+offset,size);
return TFS_SUCCESS;
}
//情况2.内存没有映射 或 要读取的数据映射不全
return FileOperation::pread_file(buf,size,offset);
}
int MMapFileOperation::pwrite_file(const char* buf,const int32_t size,const int64_t offset)
{
//情况1.内存已经映射,要写的数据小于映射内存空间(内存空间不够)
if(is_mapped_ && (offset + size) > map_file_->get_size())
{
if(debug) fprintf(stdout, "mmap file pwrite, size: %d, offset: %" __PRI64_PREFIX"d, map file size: %d. need remap",
size, offset, map_file_->get_size());
map_file_->remap_file(); //追加内存
}
//情况1.内存已经映射,要写的数据小于映射内存空间(内存空间够)
if(is_mapped_ && (offset + size) <= map_file_->get_size())
{
//注意这里是copy buf到内存去
memcpy((char*)map_file_->get_data()+offset,buf,size);
return TFS_SUCCESS;
}
//情况2.内存没有映射 或 要写入的数据映射不全
return FileOperation::pwrite_file(buf,size,offset);
}
int MMapFileOperation::flush_file()
{
//映射了才需要同步
if(is_mapped_)
{
if(map_file_->sync_file())
{
return TFS_SUCCESS;
}
else
{
return TFS_ERROR;
}
}
//写入到磁盘
return FileOperation::flush_file();
}
}
}
mmap_file_op_test.cpp测试文件映射操作
#include"mmap_file_op.h"
#include"common.h"
using namespace std;
using namespace program;
const static largefile::MMapSizeOption mmap_size_option = {10240000,4096,4096};
int main()
{
int ret=0;
const char* filename = "mmap_file_op.txt";
//largefile::MMapFileOperation* mmfo = new largefile::MMapFileOperation(filename);
largefile::MMapFileOperation mmfo(filename);
printf("ready to open file\n");
int fd = mmfo.open_file();
if(fd < 0)
{
fprintf(stderr,"open file failed. filename:%s,error desc:%s\n",filename,strerror(-fd));//负负得正
exit(-1);
}
printf("ready to mmap_file\n");
ret = mmfo.mmap_file(mmap_size_option);
if(ret == largefile::TFS_ERROR)
{
fprintf(stderr,"mmap_file failed. reason:%s\n",strerror(errno));
mmfo.close_file();
exit(-2);
}
printf("ready to pwrite_file\n");
char buf[128+1];
memset(buf,'6',128);
ret = mmfo.pwrite_file(buf,128,8);
if(ret < 0)
{
if(ret == largefile::EXIT_DISK_OPEN_INCOMPLETE)
{
fprintf(stderr,"read or write length is less than required\n");
}
else
{
fprintf(stderr,"pwrite file %s failed. error desc:%s\n",filename,strerror(-ret));
}
}
printf("ready to pread_file,read 128bytes,offset 8bytes\n");
memset(buf,0,128);
ret = mmfo.pread_file(buf,128,8);
if(ret < 0)
{
if(ret == largefile::EXIT_DISK_OPEN_INCOMPLETE)
{
fprintf(stderr,"read or write length is less than required\n");
}
else
{
fprintf(stderr,"pread file %s failed. error desc:%s\n",filename,strerror(-ret));
}
}
else
{
buf[128] = '\0';
printf("read:%s\n",buf);
}
printf("ready to flush_file\n");
ret = mmfo.flush_file();
if(ret == largefile::TFS_ERROR)
{
fprintf(stderr,"flush file failed.reason:%s\n",strerror(errno));
}
mmfo.munmap_file();
mmfo.close_file();
return 0;
}
文件映射操作效果
mmap_file_op.txt中的内容
索引处理IndexHandle类
本类是实现存储引擎的核心类,本类实现小文件的文件编号去通过映射在内存中的索引文件查找其在主块中的位置,然后进行处理,比如写小文件,读小文件,删除小文件。
在实现本类的过程中,三个结构必须要定义出来,分别就是IndexHeader索引文件的头部,索引文件的文件哈希索引块MetaInfo,和大文件块结构BlockInfo。这三个结构的属性在上文中已经说到,那么在此处再单独看看它们的结构。
IndexHeader索引文件的头部:
struct IndexHeader
{
//初始化索引文件信息
IndexHeader()
{
memset(this,0,sizeof(IndexHeader));
}
BlockInfo block_info_; //块信息
int32_t bucket_size_; //哈希桶大小
int32_t data_file_size_; //未使用数据起始的偏移
int32_t index_file_offset_; //索引文件当前偏移(索引文件大小)
int32_t reuse_head_offset_; //可重用的链表节点
};
文件哈希索引块MetaInfo:
这个结构中的方法不详细写了,具体看common.h
struct MetaInfo
{
MetaInfo() { init(); }
MetaInfo(const uint64_t file_id,const int32_t in_offset,const int32_t file_size,const int32_t next_meta_offset)
{
file_id_ = file_id;
location_.inner_offset_ = in_offset;
location_.size_ = file_size;
next_meta_offset_ = next_meta_offset;
}
//拷贝构造
MetaInfo(const MetaInfo& meta_info);
//赋值拷贝
MetaInfo& operator=(const MetaInfo& meta_info);
//克隆
MetaInfo& clone(const MetaInfo& meta_info);
//小文件是否相等
bool operator==(const MetaInfo& rhs) const;
uint64_t get_key() const;
void set_key(const uint64_t key);
uint64_t get_file_id() const;
void set_file_id(const uint64_t file_id);
int32_t get_offset() const;
void set_offset(const int32_t offset);
int32_t get_size() const;
void set_size(const int32_t file_size);
int32_t get_next_meta_offset_() const;
void set_next_meta_offset_(const int32_t offset);
private:
//小文件的一些信息
uint64_t file_id_;
//小文件在大文件中的位置和大小
struct
{
int32_t inner_offset_;
int32_t size_;
}location_;
int32_t next_meta_offset_;
private:
void init()//所有成员清零
};
}
大文件块结构BlockInfo
//大文件块结构
struct BlockInfo
{
uint32_t block_id_;
int32_t version_;
int32_t file_count_;
int32_t size_;
int32_t del_file_count_;
int32_t del_size_;
uint32_t seq_no_;
BlockInfo()
{
memset(this,0,sizeof(BlockInfo)); //所有成员清零
}
//块是否相等
inline bool operator==(const BlockInfo& rhs) const;
知道了三个结构后,我们要如何进行这些结构的操作更新,那么就用到了IndexHandle索引处理类。
index_handle.h
索引处理类的构造函数与析构函数:
- IndexHandle(const std::string& base_path,const uint32_t main_block_id);
- ~IndexHandle();
在使用构造函数初始化时,会根据给的路径信息传给文件映射操作类生成对象赋值给成员变量mmap_file_op_操作。
创建索引文件与加载索引文件:
创建索引文件需要:逻辑块的id、哈希桶的大小、内存映射的选项(因为创建时会执行内存映射)。
这一方法内部会首先将索引文件读到内存,然后同步到磁盘文件,然后进行从磁盘文件进行内存映射。
- int create(const uint32_t logic_block_id,const int32_t bucket_size,
const MMapSizeOption mmap_size_option);
加载索引文件到内存,和创建的差不多,一般创建一次后,后面都会用加载这个方法。
- int load(const uint32_t logic_block_id, const int32_t _bucket_size,
const MMapSizeOption mmap_size_option);
删除索引文件(解除映射,删除文件)
- int remove(const uint32_t logic_block_id);
同步磁盘
- int flush();
更新块信息(在新增小文件删除小文件时)
- int update_block_info(const OpType op_type,const uint32_t
modify_size);
小文件对索引文件产生影响的方法(读写删)
- int32_t write_segment_meta(const uint64_t key, MetaInfo& meta);
- int32_t read_segment_meta(const uint64_t key, MetaInfo& meta);
- int32_t delete_segment_meta(const uint64_t key);
哈希方法
哈希比较
- bool hash_compare(const uint64_t left_key, const uint64_t right_key)
{
return (left_key == right_key);
}
哈希查找
- int32_t hash_find(const uint64_t key, int32_t& current_offset,
int32_t& previous_offset);
增加哈希索引
- int32_t hash_insert(const uint64_t key, int32_t previous_offset,
MetaInfo& meta);
一些帮助性方法
具体看下文
#ifndef LARGEFILE_INDEX_HANDLE_H
#define LARGEFILE_INDEX_HANDLE_H
#include"common.h"
#include"mmap_file_op.h"
namespace program
{
namespace largefile
{
struct IndexHeader
{
//初始化索引文件信息
IndexHeader()
{
memset(this,0,sizeof(IndexHeader));
}
BlockInfo block_info_; //块信息
int32_t bucket_size_; //哈希桶大小
int32_t data_file_size_; //未使用数据起始的偏移
int32_t index_file_offset_; //索引文件当前偏移(索引文件大小)
int32_t reuse_head_offset_; //可重用的链表节点
};
class IndexHandle
{
public:
//拿到文件映射操作对象
IndexHandle(const std::string& base_path,const uint32_t main_block_id);
~IndexHandle();
//创建索引文件需要:逻辑块的id、哈希桶的大小、内存映射的选项(因为创建时会执行内存映射)
//将索引文件读到内存,然后同步到磁盘,然后进行内存映射
int create(const uint32_t logic_block_id,const int32_t bucket_size, const MMapSizeOption mmap_size_option);
//加载索引文件到内存
int load(const uint32_t logic_block_id, const int32_t _bucket_size, const MMapSizeOption mmap_size_option);
//删除索引文件(解除映射,删除文件)
int remove(const uint32_t logic_block_id);
//同步磁盘
int flush();
//拿到哈希桶大小
int32_t bucket_size() const
{
return (reinterpret_cast<IndexHeader*> (mmap_file_op_->get_map_data()))->bucket_size_;
}
//拿到映射到内存(起始地址)的索引文件的索引
IndexHeader* index_header()
{
//强制类型转换
return reinterpret_cast<IndexHeader*>(mmap_file_op_->get_map_data());
}
//拿到大文件块的地址
BlockInfo* block_info()
{
return reinterpret_cast<BlockInfo*>(mmap_file_op_ ->get_map_data());
}
//拿到未使用数据的起始偏移(块数据偏移)
int32_t get_block_data_offset() const
{
//索引头部中拿到(索引处理中拿不到的)
return reinterpret_cast<IndexHeader*>(mmap_file_op_->get_map_data())->data_file_size_;
}
//拿到可重用链表位置
int32_t reuse_head_offset() const
{
//索引头部中拿到(索引处理中拿不到的)
return reinterpret_cast<IndexHeader*>(mmap_file_op_->get_map_data())->reuse_head_offset_;
}
//拿到哈希桶在块索引中的位置(文件保存后会直接映射在内存中)(先变成1字节然后转为4字节的)
int32_t* bucket_slot()
{
return reinterpret_cast<int32_t*> (reinterpret_cast<char*> (mmap_file_op_->get_map_data()) + sizeof(IndexHeader));
}
//更新未使用数据起始的偏移
void commit_block_data_offset(const int file_size)
{
reinterpret_cast<IndexHeader*> (mmap_file_op_->get_map_data())->data_file_size_ += file_size;
}
//更新块信息
int update_block_info(const OpType op_type,const uint32_t modify_size);
//拿到文件映射操作对象
MMapFileOperation* get_file_op_()
{
return mmap_file_op_;
}
//哈希写到哈希文件中去,传递键值和哈希索引块
int32_t write_segment_meta(const uint64_t key, MetaInfo& meta);
int32_t read_segment_meta(const uint64_t key, MetaInfo& meta);
int32_t delete_segment_meta(const uint64_t key);
private:
//哈希比较
bool hash_compare(const uint64_t left_key, const uint64_t right_key)
{
return (left_key == right_key);
}
//哈希查找
int32_t hash_find(const uint64_t key, int32_t& current_offset, int32_t& previous_offset);
//增加哈希索引
int32_t hash_insert(const uint64_t key, int32_t previous_offset, MetaInfo& meta);
private:
MMapFileOperation* mmap_file_op_;//文件映射操作对象
bool is_load_; //索引文件是否被加载
};
}
}
#endif
index_handle.cpp
#include"common.h"
#include"index_handle.h"
#include<sstream>
static int debug = 1;
namespace program
{
namespace largefile
{
IndexHandle::IndexHandle(const std::string& base_path,const uint32_t main_block_id)
{
//进行路径信息的初始化
std::stringstream tmp_stream;
tmp_stream << base_path << INDEX_DIR_PREFIX << main_block_id; // /root/user/index/1
std::string index_path;
tmp_stream >> index_path;
//用路径信息来进行文件映射(初始化句柄等)
mmap_file_op_ = new MMapFileOperation(index_path, O_CREAT|O_RDWR|O_LARGEFILE);
is_load_ = false;
}
IndexHandle::~IndexHandle()
{
if(mmap_file_op_)
{
delete mmap_file_op_;
mmap_file_op_ = NULL;
}
}
//创建索引文件(映射到内存)
int IndexHandle::create(const uint32_t logic_block_id,const int32_t bucket_size, const MMapSizeOption mmap_size_option)
{
int ret = TFS_SUCCESS;
if(debug) printf("create index, block id:%u, bucket size:%d, max_mmap_size:%d ,first_mmap_size:%d , per_mmap_size:%d\n",
logic_block_id, bucket_size, mmap_size_option.max_mmap_size, mmap_size_option.first_mmap_size,mmap_size_option.per_mmap_size);
//如果索引文件已经加载
if(is_load_)
{
return EXIT_INDEX_ALREADY_LOADED_ERROR;
}
//如果索引文件不存在
int64_t file_size = mmap_file_op_->get_file_size();
if(file_size < 0)
{
return TFS_ERROR;
}
else if(file_size == 0)
{
//创建索引头部,并分配文件哈希索引块内存
IndexHeader i_header;
i_header.block_info_.block_id_ = logic_block_id;
i_header.block_info_.seq_no_ = 1;
i_header.bucket_size_ = bucket_size;
//索引文件当前偏移(文件大小) = 索引头部大小 + 文件哈希索引块大小
i_header.index_file_offset_ = sizeof(IndexHeader) + bucket_size * sizeof(int32_t);
//先将索引文件写入内存
char* init_data = new char[i_header.index_file_offset_];
memcpy(init_data, &i_header, sizeof(IndexHeader)); //索引头部写到init_data
memset(init_data + sizeof(IndexHeader), 0, i_header.index_file_offset_ - sizeof(IndexHeader));//哈希索引块写到init_data中的头部后面
ret = mmap_file_op_->pwrite_file(init_data,i_header.index_file_offset_,0);//写到内存
delete[] init_data; //写入内存后,记得要释放临时空间
init_data = NULL;
if(ret!=TFS_SUCCESS)
{
return ret;
}
//将内存中索引文件写入磁盘
ret = mmap_file_op_->flush_file();
if(ret!=TFS_SUCCESS)
{
return ret;
}
}
else //file size > 0, index already exist
{
return EXIT_META_UNEXPECTED_FOUND_ERROR;
}
//索引文件初始化成功后,映射到内存
ret = mmap_file_op_-> mmap_file(mmap_size_option);
if(ret != TFS_SUCCESS)
{
return ret;
}
is_load_ = true;
//因为映射到内存中去了,所以不用这里的i_header
if(debug) printf("init index success, block id:%u, \
data file size:%d, index file size:%d ,\
bucket file size:%d , free head offset:%d \
seqno:%d, size:%d, file count:%d, del size:%d, \
del file count:%d, version:%d\n",
logic_block_id, index_header()->data_file_size_,
index_header()->index_file_offset_,index_header()->bucket_size_,
index_header()->reuse_head_offset_,block_info()->seq_no_,
block_info()->size_,block_info()->file_count_,block_info()->del_size_,
block_info()->del_file_count_,block_info()->version_);
return TFS_SUCCESS;
}
//加载索引文件(映射入内存)
int IndexHandle::load(const uint32_t logic_block_id, const int32_t _bucket_size, const MMapSizeOption mmap_size_option)
{
//如果已经加载
if (is_load_)
{
return EXIT_INDEX_ALREADY_LOADED_ERROR;
}
//如果索引文件大小不存在(根据拿到文件大小)
int ret = TFS_SUCCESS;
int file_size = mmap_file_op_->get_file_size();
if (file_size < 0)
{
return file_size;
}
else if (file_size == 0) // empty file
{
return EXIT_INDEX_CORRUPT_ERROR;
}
//如果索引文件大小小于最大映射内存但是比首次映射内存要大,那么重新设置首次大小
largefile::MMapSizeOption tmp_map_option = mmap_size_option;
if (file_size > tmp_map_option.first_mmap_size&& file_size <= tmp_map_option.max_mmap_size)
{
tmp_map_option.first_mmap_size = file_size;
}
//将索引文件映射入内存
ret = mmap_file_op_->mmap_file(tmp_map_option);
if (TFS_SUCCESS != ret)
return ret;
if(debug) printf("IndexHandle::load. - bucket_size():%d, bucket_size_:%d, block_id:%d\n",bucket_size(),_bucket_size,block_info()->block_id_);
//检查主块文件id和哈希桶大小是否合法
if (0 == bucket_size() || 0 == block_info()->block_id_)
{
fprintf(stderr,"Index corrupt error. blockid: %u, bucket size: %d\n", block_info()->block_id_, bucket_size());
return EXIT_INDEX_CORRUPT_ERROR;
}
//检查实际索引文件大小和拿到的文件大小是否一样
int32_t index_file_offset_ = sizeof(IndexHeader) + bucket_size() * sizeof(int);
if (file_size < index_file_offset_) //映射内存不够
{
fprintf(stderr, "Index corrupt error. blockid: %u, bucket size: %d, file size: %d, index file size: %d\n",
block_info()->block_id_, bucket_size(), file_size, index_file_offset_);
return EXIT_INDEX_CORRUPT_ERROR;
}
//检查哈希桶大小是否对的上
if (_bucket_size != bucket_size())
{
fprintf(stderr, "Index configure error. old bucket size: %d, new bucket size: %d", bucket_size(), _bucket_size);
return EXIT_BUCKET_CONFIGURE_ERROR;
}
//检查文件id,看是否冲突
if (logic_block_id != block_info()->block_id_)
{
fprintf(stderr, "block id conflict. blockid: %u, index blockid: %u", logic_block_id, block_info()->block_id_);
return EXIT_BLOCKID_CONFLICT_ERROR;
}
is_load_ = true;
//因为映射到内存中去了,所以不用这里的i_header
if(debug) printf("init index success, block id:%u, \
data file size:%d, index file size:%d ,\
bucket file size:%d , free head offset:%d \
seqno:%d, size:%d, file count:%d, del size:%d, \
del file count:%d, version:%d\n",
logic_block_id, index_header()->data_file_size_,
index_header()->index_file_offset_,index_header()->bucket_size_,
index_header()->reuse_head_offset_,block_info()->seq_no_,
block_info()->size_,block_info()->file_count_,block_info()->del_size_,
block_info()->del_file_count_,block_info()->version_);
return TFS_SUCCESS;
}
//移除索引文件(解除映射,删除文件)
int IndexHandle::remove(const uint32_t logic_block_id)
{
//如果已经加载
if(is_load_)
{
if(logic_block_id!= block_info()->block_id_)
{
fprintf(stderr,"block id conflict.blockid:%d,index blockid:%d\n",logic_block_id,block_info()->block_id_);
return EXIT_BLOCKID_CONFLICT_ERROR;
}
}
//这里为什么可以这样调用,因为MapFileOperation* mmap_file_op_;//文件映射操作对象
int ret = mmap_file_op_->munmap_file();
if(TFS_SUCCESS!=ret)
{
return ret;
}
ret = mmap_file_op_->unlink_file();
return ret;
}
//同步磁盘
int IndexHandle::flush()
{
int ret = mmap_file_op_->flush_file();
if(TFS_SUCCESS!=ret)
{
fprintf(stderr,"index flush fail. ret:%d error desc:%s\n",ret,strerror(errno));
}
return ret;
}
//哈希写到哈希文件中去,传递键值和哈希索引块
int32_t IndexHandle::write_segment_meta(const uint64_t key, MetaInfo& meta)
{
int32_t current_offset = 0, previous_offset = 0;
//思考? key存在吗? 存在如何处理、不存在如何处理?
//1.从文件哈希表中查找key是否存在 hash_find(key,current_offset,previous_offset)
int ret = hash_find(key,current_offset,previous_offset);
if(TFS_SUCCESS == ret)
{
return EXIT_META_UNEXPECTED_FOUND_ERROR;
}
else if(EXIT_META_NOT_FOUND_ERROR!=ret)
{
return ret;
}
//2.不存在就写入mata到文件哈希表中 hash_insert(meta,slot,previous_offset)
//hash_insert(key,previous_offset,meta)
ret = hash_insert(key,previous_offset,meta);
return ret;
}
//哈希查找
int32_t IndexHandle::hash_find(const uint64_t key, int32_t& current_offset, int32_t& previous_offset)
{
int ret = TFS_SUCCESS;
MetaInfo meta_info;
current_offset = 0;
previous_offset = 0;
//1.确定key存放的桶slot的位置
int32_t slot = static_cast<uint32_t>(key) % bucket_size();
//2.读取桶首节点存储的第一个节点的偏移量,如果偏移量为0,直接返回EXIT_META_NOT_FOUND_ERROR
//3.根据偏移量读取存储的metainfo
//4.与key进行比较,相等则设置current_offset和previous_offset并返回success,否则继续执行
//5.从metainfo中取得下一个节点在文件中的偏移量如果偏移量为0,直接返回EXIT_META_NOT_FOUND_ERROR,否则跳转继续执行3
int32_t pos = bucket_slot()[slot];
for(;pos!=0;)
{
//读节点位置
ret = mmap_file_op_->pread_file(reinterpret_cast<char*>(&meta_info),sizeof(MetaInfo),pos);
if(TFS_SUCCESS!=ret)
{
return ret;
}
//比较读出来的是否和想要的key是一样的
if(hash_compare(key,meta_info.get_key()))
{
current_offset = pos;
return TFS_SUCCESS;
}
//更新偏移位置
previous_offset = pos;
pos = meta_info.get_next_meta_offset_();
}
return EXIT_META_NOT_FOUND_ERROR;
}
//增加哈希索引
int32_t IndexHandle::hash_insert(const uint64_t key, int32_t previous_offset, MetaInfo& meta)
{
int ret = TFS_SUCCESS;
MetaInfo tmp_meta_info;
int32_t current_offset = 0;
//1.确定key存放的桶slot的位置
int32_t slot = static_cast<uint32_t>(key) % bucket_size();
//2.确定meta节点存储在文件中的偏移量
if(reuse_head_offset()!=0) //看可重用链表中有没有合适的
{
ret = mmap_file_op_->pread_file(reinterpret_cast<char*>(&tmp_meta_info),sizeof(MetaInfo),reuse_head_offset());
if(TFS_SUCCESS!=ret)
{
return ret;
}
//剥离得到的节点
current_offset = index_header()->reuse_head_offset_;
if(debug) printf("reuse meta_info,current offset:%d\n",current_offset);
index_header()->reuse_head_offset_ = tmp_meta_info.get_next_meta_offset_();//设置下一个可重用节点
}
else
{
current_offset = index_header()->index_file_offset_;
index_header()->index_file_offset_+=sizeof(MetaInfo);
}
//3.将meta节点写入索引文件中
meta.set_next_meta_offset_(0);
ret = mmap_file_op_->pwrite_file(reinterpret_cast<const char*>(&meta),sizeof(MetaInfo),current_offset);
if(TFS_SUCCESS!=ret) //如果写入不成功
{
index_header()->index_file_offset_-=sizeof(MetaInfo);
return ret;
}
//4.如果写入成功,那么meta节点插入到哈希链表中
if(0!=previous_offset) //当前一个节点已经存在
{
//先读前一个节点到tmp_meta_info
ret = mmap_file_op_->pread_file(reinterpret_cast<char*>(&tmp_meta_info),sizeof(MetaInfo),previous_offset);
if(TFS_SUCCESS!=ret)
{
index_header()->index_file_offset_-=sizeof(MetaInfo);
return ret;
}
//然后设置前一个节点tmp_meta_info的下一个偏移位置应该在current_offset
tmp_meta_info.set_next_meta_offset_(current_offset);
ret = mmap_file_op_->pwrite_file(reinterpret_cast<const char*>(&tmp_meta_info),sizeof(MetaInfo),previous_offset);
if(TFS_SUCCESS!=ret)
{
index_header()->index_file_offset_-=sizeof(MetaInfo);
return ret;
}
}
else //当前一个节点不存在,说明哈希桶只有一个节点
{
bucket_slot()[slot] = current_offset;
}
return TFS_SUCCESS;
}
//从索引文件中读块信息
int32_t IndexHandle::read_segment_meta(const uint64_t key, MetaInfo& meta)
{
int32_t current_offset = 0;
int32_t previous_offset = 0;
//1.确定key存放桶的位置
//int32_t slot = static_cast<uint32_t>(key) % bucket_size();
//2.利用哈希查找key
int32_t ret = hash_find(key,current_offset,previous_offset);
if(TFS_SUCCESS == ret)
{
ret = mmap_file_op_->pread_file(reinterpret_cast<char*>(&meta),sizeof(MetaInfo),current_offset);
return ret;
}
else
{
return ret;
}
}
//从索引文件中删除块信息
int32_t IndexHandle::delete_segment_meta(const uint64_t key)
{
//1.首先看块到底在不在
int32_t current_offset = 0;
int32_t previous_offset = 0;
//利用哈希查找key
int32_t ret = hash_find(key,current_offset,previous_offset);
if(ret != TFS_SUCCESS)
{
return ret;
}
MetaInfo meta_info;
ret = mmap_file_op_->pread_file(reinterpret_cast<char*>(&meta_info),sizeof(MetaInfo),current_offset);
if(TFS_SUCCESS != ret)
{
return ret;
}
//拿到下一个位置,让上一个节点指向下一个节点
int32_t next_pos = meta_info.get_next_meta_offset_();
//如果只有一个节点
if(previous_offset == 0)
{
int32_t slot = static_cast<uint32_t>(key)%bucket_size();
bucket_slot()[slot] = next_pos; //直接更新槽位数据
}
//如果不止一个节点
else
{
MetaInfo pre_meta_info;
ret = mmap_file_op_->pread_file(reinterpret_cast<char*>(&pre_meta_info),sizeof(MetaInfo),previous_offset);
if(TFS_SUCCESS!=ret)
{
return ret;
}
//设置下一个节点
pre_meta_info.set_next_meta_offset_(next_pos); //将前一个节点的数据覆盖,这个节点暂时成为僵尸节点
ret = mmap_file_op_->pwrite_file(reinterpret_cast<char*>(&pre_meta_info),sizeof(MetaInfo),previous_offset);
if(TFS_SUCCESS!=ret)
{
return ret;
}
}
//把删除节点加入可重用节点链表
meta_info.set_next_meta_offset_(reuse_head_offset());
ret = mmap_file_op_->pwrite_file(reinterpret_cast<const char*>(&meta_info),sizeof(MetaInfo),current_offset);
if(TFS_SUCCESS!=ret)
{
return ret;
}
index_header->reuse_head_offset_ = current_offset;
if(debug) printf("delete_segment_meta -- reuse meta_info,current offset:%d\n",current_offset);
//更新索引信息
update_block_info(C_OP_DELETE,meta_info.get_size());
return TFS_SUCCESS;
}
//更新块信息
int IndexHandle::update_block_info(const OpType op_type,const uint32_t modify_size)
{
if(block_info()->block_id_ == 0)
{
return EXIT_BLOCKID_ZERO_ERROR;
}
if(op_type == C_OP_INSERT)
{
++block_info()->version_;
++block_info()->file_count_;
++block_info()->seq_no_;
block_info()->size_ += modify_size;
}
else if(op_type == C_OP_DELETE)
{
++block_info()->version_;
--block_info()->file_count_;
++block_info()->seq_no_;
block_info()->size_ -= modify_size;
++block_info()->del_file_count_;
block_info()->del_size_+=modify_size; //已删除的文件大小
}
if(debug) printf("init index success, block id:%u,data file size:%d, index file size:%d ,bucket file size:%d , free head offset:%d, seqno:%d, size:%d, file count:%d, del size:%d, del file count:%d, version:%d\n",
block_info()->block_id_, index_header()->data_file_size_,
index_header()->index_file_offset_,index_header()->bucket_size_,
index_header()->reuse_head_offset_,block_info()->seq_no_,
block_info()->size_,block_info()->file_count_,block_info()->del_size_,
block_info()->del_file_count_,block_info()->version_);
return TFS_SUCCESS;
}
}
}
common.h
之前的几个类其实已经更新了公共头文件,但是在本类中进行大幅更新,加入了最主要的BlockInfo主块结构和MetaInfo文件哈希索引块结构。
#ifndef _COMMON_H
#define _COMMON_H
#include<iostream>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h> //msync
#include<string>
#include<string.h>
#include<stdint.h> //int32_t
#include<errno.h> //errno stderr strerror
#include<stdio.h> //fprintf
#include<unistd.h>
#include<stdlib.h> //free
#include<inttypes.h> //__PRI64_PREFIX
#include<assert.h>
//加一个标识
namespace program
{
namespace largefile
{
const int32_t EXIT_DISK_OPEN_INCOMPLETE = -8012;//read or write length is less than required
const int32_t TFS_SUCCESS = 0;
const int32_t TFS_ERROR = -1;
const int32_t EXIT_INDEX_ALREADY_LOADED_ERROR = -8013; //index is loaded when create or load.
const int32_t EXIT_META_UNEXPECTED_FOUND_ERROR = -8014; //meta found in index when insert.
const int32_t EXIT_INDEX_CORRUPT_ERROR = -8015; //index file is corrupt.
const int32_t EXIT_BLOCKID_CONFLICT_ERROR = -8016; //block id conflict.
const int32_t EXIT_BUCKET_CONFIGURE_ERROR = -8017; //bucket size error.
const int32_t EXIT_META_NOT_FOUND_ERROR = -8018; //META_NOT_FOUND.
const int32_t EXIT_BLOCKID_ZERO_ERROR = -8019; //BLOCKID_ZERO.
//基础路径信息
static const std::string MAINBLOCK_DIR_PREFIX = "/mainblock/";
static const std::string INDEX_DIR_PREFIX = "/index/";
static const mode_t DIR_MODE = 0755;
//定义索引处理的一些类型(更新块信息这里需要用到)
enum OpType
{
C_OP_INSERT = 1,
C_OP_DELETE
};
//设定三个映射类型的大小
struct MMapSizeOption
{
int32_t max_mmap_size;
int32_t first_mmap_size;
int32_t per_mmap_size;
};
//大文件块结构
struct BlockInfo
{
uint32_t block_id_;
int32_t version_;
int32_t file_count_;
int32_t size_;
int32_t del_file_count_;
int32_t del_size_;
uint32_t seq_no_;
BlockInfo()
{
memset(this,0,sizeof(BlockInfo)); //所有成员清零
}
//块是否相等
inline bool operator==(const BlockInfo& rhs) const
{
return block_id_ == rhs.block_id_&&version_ == rhs.version_&&file_count_ == rhs.file_count_
&&size_ == rhs.size_&&del_file_count_ == rhs.del_file_count_&&del_size_ == rhs.del_size_
&&seq_no_ == rhs.seq_no_;
}
};
//文件哈希索引块
struct MetaInfo
{
MetaInfo() { init(); }
MetaInfo(const uint64_t file_id,const int32_t in_offset,const int32_t file_size,const int32_t next_meta_offset)
{
file_id_ = file_id;
location_.inner_offset_ = in_offset;
location_.size_ = file_size;
next_meta_offset_ = next_meta_offset;
}
//拷贝构造
MetaInfo(const MetaInfo& meta_info)
{
memcpy(this,&meta_info,sizeof(MetaInfo));
}
//赋值拷贝
MetaInfo& operator=(const MetaInfo& meta_info)
{
if(this == &meta_info)
{
return *this;
}
file_id_ = meta_info.file_id_;
location_.inner_offset_ = meta_info.location_.inner_offset_;
location_.size_ = meta_info.location_.size_;
next_meta_offset_ = meta_info.next_meta_offset_;
}
//克隆
MetaInfo& clone(const MetaInfo& meta_info)
{
assert(this!=&meta_info);
file_id_ = meta_info.file_id_;
location_.inner_offset_ = meta_info.location_.inner_offset_;
location_.size_ = meta_info.location_.size_;
next_meta_offset_ = meta_info.next_meta_offset_;
return *this;
}
//小文件是否相等
bool operator==(const MetaInfo& rhs) const
{
return file_id_ == rhs.file_id_&&location_.inner_offset_ == rhs.location_.inner_offset_
&&location_.size_ == rhs.location_.size_&&next_meta_offset_ == rhs.next_meta_offset_;
}
uint64_t get_key() const
{
return file_id_;
}
void set_key(const uint64_t key)
{
file_id_ = key;
}
uint64_t get_file_id() const
{
return file_id_;
}
void set_file_id(const uint64_t file_id)
{
file_id_ = file_id;
}
int32_t get_offset() const
{
return location_.inner_offset_;
}
void set_offset(const int32_t offset)
{
location_.inner_offset_ = offset;
}
int32_t get_size() const
{
return location_.size_;
}
void set_size(const int32_t file_size)
{
location_.size_ = file_size;
}
int32_t get_next_meta_offset_() const
{
return next_meta_offset_;
}
void set_next_meta_offset_(const int32_t offset)
{
next_meta_offset_ = offset;
}
private:
//小文件的一些信息
uint64_t file_id_;
//小文件在大文件中的位置和大小
struct
{
int32_t inner_offset_;
int32_t size_;
}location_;
int32_t next_meta_offset_;
private:
void init()
{
memset(this,0,sizeof(MetaInfo)); //所有成员清零
}
};
}
}
#endif /*_COMMON_H*/
block_init_test.cpp测试生成索引文件
利用create方法分别在对应的索引文件夹和主块文件夹中生成一个索引文件和主块文件,索引文件内部初始化生成索引头部+容量为1000的哈希桶。
#include"common.h"
#include"file_op.h"
#include"index_handle.h"
#include<sstream>
using namespace program;
using namespace std;
//内存映射参数
const static largefile::MMapSizeOption mmap_option = {1024000,4096,4096}; //索引文件1M、4K、4K
const static uint32_t main_blocksize = 1024*1024*64; //主块文件的大小64M
const static uint32_t bucket_size = 1000; //哈希桶的大小
static int32_t block_id = 1; //默认块id为1
static int debug = 1;
int main(int argc, char** argv)
{
std::string mainblock_path;
std::string index_path;
int32_t ret = largefile::TFS_SUCCESS;
cout<<"Type your block_id:"<<endl;
cin >> block_id;
if(block_id<1)
{
cerr<<"Invalid blockid,exit."<<endl;
exit(-1);
}
//生成主块文件、生成索引文件并初始化
//1.创建索引文件(内部自动进行文件映射)
largefile::IndexHandle* index_handle = new largefile::IndexHandle(".",block_id); //索引文件句柄
if(debug) printf("init index...\n");
ret = index_handle->create(block_id,bucket_size,mmap_option);
if(ret!=largefile::TFS_SUCCESS)
{
fprintf(stderr, "create index %d failed.\n", block_id);
//delete mainblock; //删除主块指针
delete index_handle; //删除索引指针
exit(-3);
}
//2.生成主块文件
std::stringstream tmp_stream;
tmp_stream<<"."<<largefile::MAINBLOCK_DIR_PREFIX<<block_id;
tmp_stream>>mainblock_path;
//初始化文件路径和打开方式
largefile::FileOperation* mainblock = new largefile::FileOperation(mainblock_path,O_RDWR|O_LARGEFILE|O_CREAT);
//初始化文件大小
ret = mainblock->ftruncate_file(main_blocksize);
if(ret!=0)
{
fprintf(stderr, "create main block %s failed. reason:%s\n", mainblock_path.c_str(), strerror(errno));
delete mainblock;
index_handle->remove(block_id); //主块文件如果出错,那么之前生成的索引文件也要删除
exit(-2);
}
mainblock->close_file();
index_handle->flush();
delete mainblock;
delete index_handle;
return 0;
}
看到效果图可以看到,生成了一个id为10的块,首次映射大小呢是4KB,索引文件大小是4044B,此时还没有小文件。
block_write_test.cpp测试小文件写入大文件
首先给出要写入的大文件的块id,加载索引文件,在这个块中的主块中写入小文件,大小为4KB,主块文件写入成功后将信息写到MetaInfo中,更新索引信息。
#include"common.h"
#include"file_op.h"
#include"index_handle.h"
#include<sstream>
using namespace program;
using namespace std;
//内存映射参数
const static largefile::MMapSizeOption mmap_option = {1024000,4096,4096}; //索引文件1M、4K、4K
const static uint32_t main_blocksize = 1024*1024*64; //主块文件的大小64M
const static uint32_t bucket_size = 1000; //哈希桶的大小
static int32_t block_id = 1; //默认块id为1
static int debug = 1;
int main(int argc, char** argv)
{
std::string mainblock_path;
std::string index_path;
int32_t ret = largefile::TFS_SUCCESS;
cout<<"Type your block_id:"<<endl;
cin >> block_id;
if(block_id<1)
{
cerr<<"Invalid blockid,exit."<<endl;
exit(-1);
}
//加载索引文件,写入文件到主块文件
//1.加载索引文件(内部自动进行文件映射)
largefile::IndexHandle* index_handle = new largefile::IndexHandle(".",block_id); //索引文件句柄
if(debug) printf("load index...\n");
ret = index_handle->load(block_id,bucket_size,mmap_option);
if(ret!=largefile::TFS_SUCCESS)
{
fprintf(stderr, "load index %d failed.\n", block_id);
//delete mainblock; //删除主块指针
delete index_handle; //删除索引指针
exit(-2);
}
//2.写入文件到主块文件
std::stringstream tmp_stream;
tmp_stream<<"."<<largefile::MAINBLOCK_DIR_PREFIX<<block_id;
tmp_stream>>mainblock_path;
//初始化文件路径和打开方式
largefile::FileOperation* mainblock = new largefile::FileOperation(mainblock_path,O_RDWR|O_LARGEFILE|O_CREAT);
//写文件
char buf[4096];
memset(buf,'6',sizeof(buf));
int32_t data_offset = index_handle->get_block_data_offset(); //拿到块偏移
uint32_t file_no = index_handle->block_info()->seq_no_; //拿到下一个可分配的文件编号
//如果写入失败
if((ret = mainblock->pwrite_file(buf,sizeof(buf),data_offset))!=largefile::TFS_SUCCESS)
{
fprintf(stderr,"write to main block failed.ret:%d,reason:%s\n",ret,strerror(errno));
mainblock->close_file();
delete mainblock;
delete index_handle;
exit(-3);
}
//3.将主块文件信息写入MetaInfo.成功,则需要更新未使用数据起始的偏移和文件哈希索引块MetaInfo
largefile::MetaInfo meta;
meta.set_file_id(file_no); //下一个可分配的文件编号
meta.set_offset(data_offset); //文件在块内部的偏移量
meta.set_size(sizeof(buf)); //主块文件大小
//4.MetaInfo写到哈希链表
ret = index_handle->write_segment_meta(meta.get_key(),meta);
if(ret == largefile::TFS_SUCCESS)
{
//写成功后
//1.更新索引头部信息
index_handle->commit_block_data_offset(sizeof(buf));
//2.更新块信息,比如更新文件大小,块数据偏移等
index_handle->update_block_info(largefile::C_OP_INSERT,sizeof(buf));
//写到磁盘
ret = index_handle->flush();
if(ret!=largefile::TFS_SUCCESS)
{
fprintf(stderr,"flush mainblock %d failed. file no: %u\n",block_id,file_no);
}
}
else
{
fprintf(stderr,"write_segment_meta mainblock %d failed. file no: %u\n",block_id,file_no);
}
if(ret != largefile::TFS_SUCCESS)
{
fprintf(stderr,"write to mainblock %d failed. file no: %u\n",block_id,file_no);
}
else
{
if(debug) printf("write successfully. file no:%u, block_id:%d\n",file_no,block_id);
}
mainblock->close_file();
delete mainblock;
delete index_handle;
return 0;
}
可以看到数据刚好偏移了4KB个位置,并且索引文件增大了1,文件数量存了1一个了
block_read_test.cpp测试从大文件中读小文件
根据块id先加载索引文件,再根据文件id去索引文件中拿到相应的MetaInfo,拿到要读取的位置,把内容从主块中读取到buffer中,再打印buffer。
#include"common.h"
#include"file_op.h"
#include"index_handle.h"
#include<sstream>
using namespace program;
using namespace std;
//内存映射参数
const static largefile::MMapSizeOption mmap_option = {1024000,4096,4096}; //索引文件1M、4K、4K
const static uint32_t main_blocksize = 1024*1024*64; //主块文件的大小64M
const static uint32_t bucket_size = 1000; //哈希桶的大小
static int32_t block_id = 1; //默认块id为1
static int debug = 1;
int main(int argc, char** argv)
{
std::string mainblock_path; //主块文件路径
std::string index_path; //索引文件路径
int32_t ret = largefile::TFS_SUCCESS;
cout<<"Type block id:"<<endl;
cin>>block_id;
//如果blockid不合法
if(block_id<1)
{
cerr<<"invalid block_id,exit."<<endl;
exit(-1);
}
//1.如果合法,那么就初始化索引操作对象,准备载入索引文件
largefile::IndexHandle* index_handle = new largefile::IndexHandle(".",block_id);
if(debug) printf("load index...\n");
ret = index_handle->load(block_id,bucket_size,mmap_option);
if(ret!=largefile::TFS_SUCCESS)
{
fprintf(stderr, "load index %d failed.\n", block_id);
delete index_handle; //删除索引指针
exit(-2);
}
//2.读取文件的meta info.
uint64_t file_id = 0;
cout<<"Type file id:"<<endl;
cin>>file_id;
//如果blockid不合法
if(file_id<1)
{
cerr<<"invalid file_id,exit."<<endl;
exit(-3);
}
largefile::MetaInfo meta;
ret = index_handle->read_segment_meta(file_id,meta);
if(ret != largefile::TFS_SUCCESS)
{
fprintf(stderr,"read_segment_meta error. file_id:%ld, ret:%d\n",file_id,ret);
exit(-4);
}
//3.根据meta info读取文件,首先要得到主块文件才行
std::stringstream tmp_stream;
tmp_stream<<"."<<largefile::MAINBLOCK_DIR_PREFIX<<block_id;
tmp_stream>>mainblock_path;
//打开主块文件,进行读
largefile::FileOperation* mainblock = new largefile::FileOperation(mainblock_path,O_RDWR);
//char buffer[4096];
char* buffer = new char[meta.get_size()+1]; //使用动态内存分配(这里其实可以做一下处理,比如太大了,就每次读4096,直到被读完)
ret = mainblock->pread_file(buffer,meta.get_size(),meta.get_offset());
if(ret != largefile::TFS_SUCCESS)
{
fprintf(stderr,"read from main block failed. ret:%d, reason:%s\n",ret,strerror(errno));
mainblock->close_file();
delete mainblock;
delete index_handle;
exit(-5);
}
//4.打印输出结果
buffer[meta.get_size()] = '\0';
printf("read:%s, read size:%d\n",buffer,meta.get_size());
mainblock->close_file();
delete mainblock;
delete index_handle;
return 0;
}
可以看到成功读取了文件id为1的文件内存。
block_delete_test.cpp测试删除小文件
根据块id载入索引文件,根据文件id删除索引文件中的meta_info节点,块文件中的内容不用真的删除,只做一个标记,为之后统一删除准备。
#include"common.h"
#include"file_op.h"
#include"index_handle.h"
#include<sstream>
using namespace program;
using namespace std;
//内存映射参数
const static largefile::MMapSizeOption mmap_option = {1024000,4096,4096}; //索引文件1M、4K、4K
const static uint32_t main_blocksize = 1024*1024*64; //主块文件的大小64M
const static uint32_t bucket_size = 1000; //哈希桶的大小
static int32_t block_id = 1; //默认块id为1
static int debug = 1;
int main(int argc, char** argv)
{
std::string mainblock_path; //主块文件路径
std::string index_path; //索引文件路径
int32_t ret = largefile::TFS_SUCCESS;
cout<<"Type block id:"<<endl;
cin>>block_id;
//如果blockid不合法
if(block_id<1)
{
cerr<<"invalid block_id,exit."<<endl;
exit(-1);
}
//1.如果合法,那么就初始化索引操作对象,准备载入索引文件
largefile::IndexHandle* index_handle = new largefile::IndexHandle(".",block_id);
if(debug) printf("load index...\n");
ret = index_handle->load(block_id,bucket_size,mmap_option);
if(ret!=largefile::TFS_SUCCESS)
{
fprintf(stderr, "load index %d failed.\n", block_id);
delete index_handle; //删除索引指针
exit(-2);
}
//2.删除指定文件的meta info.
uint64_t file_id = 0;
cout<<"want to delete file's id:"<<endl;
cin>>file_id;
//如果blockid不合法
if(file_id<1)
{
cerr<<"invalid file_id,exit."<<endl;
exit(-3);
}
//删除
ret = index_handle->delete_segment_meta(file_id); //内部已经对索引信息进行了更新
if(ret != largefile::TFS_SUCCESS)
{
fprintf(stderr,"delete_segment_meta error. file_id:%ld, ret:%d\n",file_id,ret);
exit(-4);
}
//3.注意索引文件meta info删除文件,主块文件中不会真的删除,而是先打个标记不管,等到一定条件了再一次性删除
//4.更新到磁盘文件
ret = index_handle->flush();
if(ret != largefile::TFS_SUCCESS)
{
fprintf(stderr,"flush index error.block_id:%d, file_id:%ld, ret:%d\n",block_id,file_id,ret);
exit(-5);
}
printf("delete successfully\n");
delete index_handle;
return 0;
}
之前对10号块已经写入了1次,再写入两次,如下:
可以看到10号块成功写入了三个文件,并且索引文件由于大小不够了重新映射了内存。
那么执行删除操作,删除1号块:
可以看到确实删除了1号文件,可重用的起始位置由0变成了4044的位置
再删除3号文件,可重用的起始位置变成了4092的位置,刚好跨越了三个节点
那么如果再次插入,就会从重用位置开始插入了