搜索引擎之文件系统(一):磁盘连续存储
我们在设计文件系统时要考虑系统的性能,海量存储,容错能力,大规模分布式数据处理,可扩展分布式管理等.性能之前也用了很多
的方法来提升,比如反向索引数据,内存映射读文件等,这里就不多说了,文件系统另一个重要的功能是存储能力,之前我说过当网络蜘蛛将大
量的数据下载到硬盘时,我们要用Zlib压缩存储它来实现更小的空间,但这远远不能符合容错能力,分布式管理等其它基本需求,而且我们是否
可以考虑是否有更能利用磁盘空间的方法?当文件被OS管理时,文件会被分散的保存在这个磁盘的不同位置,形成不了连续的簇,在应用读到这
个文件时,磁盘也要去找到该文件所有的必须文件,读入磁盘Cache和内存再处理,为了拥有更快的速度需要提高磁盘平均寻道时间,如果能将
文件放到磁盘的连续存储空间内,磁头搜索的时间必将减少很多.
这里我们要做的就是将磁盘中的大量文件进行连续空间存储来提升性能和节省空间,另外也要适合分布式管理和备份,而且还要能存
储大量的诸如图片,PDF等这样异构信息,所以方法我就只想到了一个,(呵呵,这里如果有人想到了其它的记得告诉我),那就是将文件按指定的
大小切割,到达最后一个文件的时候,概率上说会产生剩余空间,比如最后文件剩5M,而我们是按50M来作为文件块切割的,那么下一个文件就先
读出45M放到里面,依此类推,最后形成的是一个按照50M/块的一个连续文件群存储,其中每一块都可以单独做备份,而任意部分又都可以存在
不同的机器上,其中靠一个文件描述表来维系每个文件在文件集群里所处的位置,当其它应用用到该文件时,通过文件描述表找到该文件位置,
将其中内容提取出来,合并还原.
这里设计的重点就是文件分解与合并了,很多网上也有类似的软件,但都没有实现连续存储来达到空间的节省,这里我来贡献代码.
先看看文件描述表:
[Config]
ID=42
LastFile=tmp.42
LastFilePos=2217
[c:/vs/first.exe]
StartFile=tmp.0
EndFile=tmp.21
tmp.21=2016 //该文件末尾所在的文件和所占空间
[c:/vs/second.exe]
StartFile=tmp.21
EndFile=tmp.42
tmp.42=2217
这里我定义了一个BUF_SIZ是8000,也就是8000个Char的分配方案,根据上面的描述表,second.exe文件所在的位置应该是从tmp.21的
2017到tmp.42的2217的位置.
代码:
//All right revsered by yoki2009
//mailto:imj040144@tom.com
//Welcome to my blog: http://blog.csdn.net/yoki2009
#include <cctype>
#include <cstddef>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <map>
#include "DefException.h"
using namespace std;
#define BUF_SIZ 8096
namespace FILESPLIT
{
static int cnt = 0;
int initConfig()
{
WritePrivateProfileString("Config","ID",
"0","./docid.pos");
return TRUE;
}
map<int , string> filelist;
static int fileid = 1;
int splitGreatToSlice(const string &srcName)
{
filelist[fileid] = srcName;
fileid++;
ifstream infile(srcName.c_str(),ios_base::binary);
if (!infile)
{
throw err::RunException("Open File Error.");
return FALSE;
}
bool isEnd = FALSE;
//check if it is the first time to splite the file
char recordID[16];
GetPrivateProfileString("Config","ID","0",recordID,16,"./docid.pos");
char bstartFile[64];
char bendpos[4];
if (!strcmp(recordID,"0"))
{
cnt = 0;
}else
{
cnt = atoi(recordID);
GetPrivateProfileString("Config","LastFile","",
bstartFile,64,"./docid.pos");
GetPrivateProfileString("Config","LastFilePos","0",
bendpos,4,"./docid.pos");
}
ostringstream oss;
oss<<"tmp."<<cnt;
WritePrivateProfileString(srcName.c_str(),"StartFile",
oss.str().c_str(),"./docid.pos");
while(!isEnd)
{
oss.str("");
oss<<"tmp."<<cnt;
cnt++;
ofstream outfile(oss.str().c_str(),ios_base::binary|ios_base::app);
if (!outfile)
{
throw err::RunException("Create tmp File Error.");
return FALSE;
}
for (size_t i=0;i<BUF_SIZ;i+=infile.gcount())
{
char buf[BUF_SIZ];
//read content from new file
//Fill the content when there is also have space in the last file
if (0 != atoi(bendpos))
{
if (infile.read(buf,(BUF_SIZ - atoi(bendpos))))
{
outfile.write(buf,(BUF_SIZ - atoi(bendpos)));
strcpy(bendpos,"0");
break;
}
}
if (infile.read(buf,BUF_SIZ))
{
outfile.write(buf,BUF_SIZ);
}else
{
//buf[infile.gcount()]='/0';
outfile.write(buf,infile.gcount());
isEnd = TRUE;
//record FILE start and end pos.
//ostringstream startpos;
ostringstream endpos;
//startpos<<;
endpos<<infile.gcount();
WritePrivateProfileString(srcName.c_str(),"EndFile",
oss.str().c_str(),"./docid.pos");
WritePrivateProfileString(srcName.c_str(),oss.str().c_str(),
endpos.str().c_str(),"./docid.pos");
//record last file information.
WritePrivateProfileString("Config","LastFile",
oss.str().c_str(),"./docid.pos");
WritePrivateProfileString("Config","LastFilePos",
endpos.str().c_str(),"./docid.pos");
//To save one variable,use endpos to store cnt.
endpos.str("");
endpos<<(--cnt);
WritePrivateProfileString("Config","ID",endpos.str().c_str(),"./docid.pos");
outfile.close();
break;
}
outfile.close();
}
}
infile.close();
return TRUE;
}
int mergeSliceToNormal(const string &srcName)
{
SetCurrentDirectory("./");
ostringstream newFile;
newFile<<srcName.c_str()<<".new";
ofstream outfile(newFile.str().c_str(),ios_base::binary);
char startFile[64];
char endFile[64];
char endpos[4];
char currentendpos[8];
bool firstVisit = FALSE;
GetPrivateProfileString(srcName.c_str(),"StartFile","",startFile,64,"./docid.pos");
GetPrivateProfileString(srcName.c_str(),"EndFile","",endFile,64,"./docid.pos");
char tmpEndFile[64];
GetPrivateProfileString((filelist[filelist.size()-1]).c_str(),"EndFile","",tmpEndFile,64,"./docid.pos");
GetPrivateProfileString((filelist[filelist.size()-1]).c_str(),tmpEndFile,"",endpos,4,"./docid.pos");
if (!strcmp(startFile,"tmp.0"))
{
firstVisit = TRUE;
}
char tmp[] = "tmp.";
char *startFilePos = NULL;
char *endFilePos = NULL;
startFilePos = strtok(startFile,tmp);
endFilePos = strtok(endFile,tmp);
for (int i = atoi(startFilePos);i<=atoi(endFilePos);i++)
{
ostringstream oss;
char buf[BUF_SIZ];
oss<<"tmp."<<i;
ifstream infile(oss.str().c_str(),ios_base::binary);
//if it was the first visit,you should seek the the begin pos in
// the last file.
if (!firstVisit)
{
infile.seekg(atoi(endpos));
firstVisit = TRUE;
if (infile.read(buf,(BUF_SIZ - atoi(endpos))))
{
outfile.write(buf,(BUF_SIZ - atoi(endpos)));
}else
{
outfile.write(buf,infile.gcount());
}
continue;
}
//if reach the last file, you should only read the length of buf
//which belonged this file.
if (i == atoi(endFilePos))
{
GetPrivateProfileString(srcName.c_str(),oss.str().c_str
(),"",currentendpos,8,"./docid.pos");
if (infile.read(buf,atoi(currentendpos)))
outfile.write(buf,atoi(currentendpos));
break;
}
if (infile.read(buf,BUF_SIZ))
{
outfile.write(buf,BUF_SIZ);
}else
{
outfile.write(buf,infile.gcount());
}
infile.close();
}
outfile.close();
return TRUE;
}
}
测试:
FILESPLIT::splitGreatToSlice("c://vs//first.exe");
FILESPLIT::splitGreatToSlice("c://vs//second.exe");
FILESPLIT::mergeSliceToNormal("c://vs//first.exe");
FILESPLIT::mergeSliceToNormal("c://vs//second.exe");
system("PAUSE");