系列文章
2.分布式文件快速搜索的设计与实现(开源/分布式计算/并行)
3.分布式文件快速搜索-技术细节分析(开源/并行)
前言
在上一篇文章中,对分布式文件快速搜索的设计与实现进行了说明。今天,将对具体的实现细节进行分析。
文件的检索
-
文件获取
1). 一般地,用Directory.GetDirectories()加上SearchOption.AllDirectories来获取某个的目录下的所有文件(包括任意层子文件)。但在这里会采用自行递归获取,每获取一层目录的文件,都会预先根据SearchTypes条件(如文件大小、文件名、修改时间、属性等)来碰撞。具体参看WorkV7FindFileRunner.FindLocal方法。
2). 相比递归获取文件列表,我们可以直接读取物理磁盘的MFT(Master File Table)主文件表,这样不需要逐个目录递归,直接顺序获取所有文件夹和文件记录,速度可以比递归快10倍以上。在Windows NT便引入的NTFS文件系统,拥有USN Journal(文件日志),每个文件的变化(添加、修改、删除、更名等)都会被记录,在通过MFT获取所有文件后,每次运行,只需要判断一下系统的变化并记录便可,无需重新扫描整个磁盘。
-
哈希获取
在使用SearchTypes条件过滤完成后,使用MD5CryptoServiceProvider来获取MD5哈希值。当然,你可以使用SHA1CryptoServiceProvider计算哈希值,如果你觉得MD5不可靠,具体参看WorkUtils.HashMD5File。
实际的哈希获取,会使用缓存。缓存的实现使用快速二进制序列化,原理是判断缓存数据库是否存在一样的文件信息(文件名、大小和修改时间),如果匹配,则返回已经存在的哈希值,否则就获取新的哈希值。具体参看LocalHashStorage。
文件的匹配/比较
文件的匹配有3种方式:完全一致、包含以及全文索引。
-
完全一致
完全一致,直接使用哈希值比较。
-
包含
适用于运行时搜索,判断文本文件中包含的内容。目前直接使用File.ReadAllText(file).IndexOf(keyword, StringComparison.InvariantCultureIgnoreCase)来判断,可能遇到的问题应该是文件太大导致内存溢出。
-
全文索引
可索引文件的全文内容会自动缓存,支持自定义扩展接口IFileContentIndex,目前内置了微软的IFilter实现。具体参看LocalFileContentIndexStorage。
并行计算的处理
实现
因为不是.NET3.5/4,没有PPL,只能模拟并行,来源参看分布式文件快速搜索v7.0(多计算机并行/多种算法)。原理是使用ThreadPool.QueueUserWorkItem各个任务,使用ManualResetEvent记住每个任务的状态,并用WaitHandle.WaitAll等待所有任务完成。具体参看ParallelProcessor.ExecuteParallel。
{
if (methods.Length > 0)
{
// Initialize the reset events to keep track of completed threads
ManualResetEvent[] resetEvents = new ManualResetEvent[methods.Length];
// Launch each method in it's own thread
for (int i = 0; i < methods.Length; i++)
{
resetEvents[i] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object index)
{
int methodIndex = (int)index;
// Execute the method
methods[methodIndex].Run();
// Tell the calling thread that we're done
resetEvents[methodIndex].Set();
}), i);
}
// Wait for all threads to execute
WaitHandle.WaitAll(resetEvents);
}
}
线程安全
在多线程的环境中,最常见的问题是线程安全。.NET 2.0中,Dictionary是不安全的,我用网上封装的SynchronizedDictionary,当然,我们还可以使用折腾箱子的HashTable。在.NET 4.0中,你可以使用ConcurrentDictionary。
除了集合,在访问FileStream等对象的情况,你都必须注意保证线程安全,否则数据会跟实际预想不一致。
任务切分
并行计算最关键是如何切分任务。在上一篇文章中我已经简略地说明,实际的逻辑比较复杂。首先,根据要搜索的文件组和最大任务数进行切分,在每个任务中,判断文件夹是否为远程文件夹,如果是,则使用分布式搜索,否则本地搜索,具体参看WorkV7FindFileRunner。
在上述所有文件夹搜索任务都完成后,聚合搜索结果,再根据网络/本地切分任务。对于本地文件夹,则判断是否为同一个物理磁盘,如果是,则动态使用并行计算以实现加速。具体参看WorkV7.Find()。
具体参看上一篇文章的流程图、WorkV7FindFileRunner(第一次根据文件大小、名称等过滤)和WorkV7FindResultRunner(根据文件属性过滤后再根据匹配方式搜索)。
分布式的实现
这是本程序的最大特点。分布式的原理就是在各个节点部署一个监听程序,多个节点组合成一个grid,在监听的同时,也可以作为客户端发送请求。
数据的传输
本程序没有使用XML作为数据载体,而是使用了自定义的格式:文件头+数据体(如文件内容等),文件头包括了命令等信息。整个内容可选压缩算法,每个数据包在最后自动添加结束标记,以便在TCP中识别。
协议
分布式文件快速搜索自定义了协议接口,内置了对HTTP、TCP和FTP的支持,你可以自由添加各种协议。每种网络连接,都会使用异步处理,避免堵塞请求。
譬如:
c:\temp\abc.txt
\\lancomputer\temp\abc.txtt
http://1.2.3.4:88/foo/abc.txt
tcp://1.2.3.4:55/foo/abc.txt
ftp://1.2.3.4/foo/abc.txt
pop3://1.2.3.4:101/foo/abc.txt
skydrive://foo/abc.txt
dropbox://foo/abc.txt
AmazonS3://foo/abc.txt
flicker://foo/abc.jpg
facebook://foo/abc.jpg
-
HTTP
每个数据包都使用POST方式,在可选压缩后把数据写入Request.GetRequestStream,具体参看HTTPFileWorkProvider。在服务器端,使用HttpListener监听,在获得每个HttpListenerRequest后,调用基类(BaseManager)的ProcessRequest处理Request.InputStream,具体参看WorkV7HTTPManager
HttpListener有点诡异,使用fooListener.Prefixes.Add()来定义监听的路径。在调用HttpListener之前,你需要使用HttpListener.IsSupported判断一下你的操作系统是否支持:必须XP SP2或以上、Win2003、Vista、08、Win7。HttpListener本身不支持SSL,但你可以httpcfg.exe来配置,之前我参看的是一篇英文的文章,现在一下子找不到,大家就凑合用中文的吧:配置HttpListener侦听SSL连接详解。
-
TCP
每个具体的操作,会先使用AsynchronousClient进行连接,服务器使用AsynchronousSocketListener进行监听,在Received事件处理客户端发送来的请求,具体参看TCPFileWorkProvider和WorkV7TCPManager。
-
FTP
使用.NET内置的FTPWebRequest,具体参看FTPFileWorkProvider。
大数据的传输
在下一个大版本中,将会提供对文件同步的支持。文件传输,有很多现成的软件可以做参考。我的设想是:把文件动态切分成多个小块以减少内存的占用,标记之,成功就记录一个,失败/断开后下次传输,则可以断点续传。当然,一样可选压缩算法,以提高传输性能。
代码下载
点击这里下载:Filio.zip
项目地址
本项目已经在http://filio.codeplex.com/ 开源