此处分析用的源码为最新版本1.0 beta版的。
//search.cpp int main ( int argc, char ** argv )
让我们从程序入口点开始进行旅程。search的入口点在文件search.cpp中,打开后定位到int main ( int argc, char ** argv )开始我们的分析:
在main中开始部分进行参数检查和配置信息的load,先进行命令行参数的检查和设置,如下:
所有的查询信息都封装在CSphQuery tQuery;中,结构CSphQuery定义可以查看原文件,此处不知道也无关系影响不大,只要清楚它只是个容器的作用,用于盛放所以查询参数就ok了。
继续旅程,下面进入config的加载部分:
可以看到通过类CSphConfigParser对配置文件进行解析,最终解析结果存放在结构CSphConfig中,其实CSphConfig就是一个hashtable一样的东东,config文件中的source、index、searchd等等所有节的东西都被加载进入了CSphConfig中。
现在所有的参数信息不管是命令行传入的还是从config文件加载进入的都已经就绪,下面就该开始真正的工作了。
首先说明一下查询的流程,查询就是一个打开索引文件,用输入的查询词在索引文件中挨个进行比较,找到满足关系的文档的过程,并读出文档,给每个文件打分,最后打分完成后进行排序,随后获取到排序后的文档列表的过程。
数据库中可能存在多个索引文件,而查询一次在一个索引中进行。
pIndex->Prealloc ( false, false, sWarning )预先分配知足够内存存放cache数据
!pIndex->Preread() 预先读取所有的需要cache起来的数据,存放在Prealloc 分配的空间中
获取索引关联的数据模式const CSphSchema * pSchema = &pIndex->GetMatchSchema();
查询:
pIndex->MultiQuery ( &tQuery, pResult, 1, &pTop, NULL ) )进行最终的查询过程。
函数MultiQuery的原型为virtual bool MultiQuery ( const CSphQuery * pQuery, CSphQueryResult * pResult, int iSorters, ISphMatchSorter ** ppSorters, const CSphVector<CSphFilterSettings> * pExtraFilters, int iTag=0 ) const = 0;
在后面还将对其进行分析。
MultiQuery的查询结果由pResult和pTop返回,在pTop中存放的是docs数据,里面的数据需要进行sphFlattenQueue ( pTop, pResult, 0 )操作后才能copy到pResult中,sphFlattenQueue()的运算过程就是一个优先队列的出队操作,最后的结果就是rank从高到底的排列。pResult返回的结果是一些对全体文档都有效的数据,如字符串属性的字符串值、多值属性的值等。
查询得到结果后下面的代码只是进行简单的终端输出了,没什么好分析的了:
下面对文档排序器(ISphMatchSorter )的创建过程简单分析:
//sphinxsort.cpp ISphMatchSorter * sphCreateQueue ( const CSphQuery * pQuery, const CSphSchema & tSchema, CSphString & sError, bool bComputeItems )
此函数中大部分的代码段都是根据pQuery 中的配置对ISphMatchSorte人 进行定制的,源程序中的注释已经很详细,具体可以参考源代码。在进行了所有的需要定制的功能的设定后,最终如下面代码片段,进行实际的排序器构造:
还是按着查询的路线一直走下去,下面分析查询过程。
//sphinx.cpp bool CSphIndex_VLN::MultiQuery ( const CSphQuery * pQuery, CSphQueryResult * pResult, int iSorters, ISphMatchSorter ** ppSorters, const CSphVector<CSphFilterSettings> * pExtraFilters, int iTag ) const
//sphinx.cpp bool CSphIndex_VLN::ParsedMultiQuery ( const CSphQuery * pQuery, CSphQueryResult * pResult, int iSorters, ISphMatchSorter ** ppSorters, const XQNode_t * pRoot, CSphDict * pDict, const CSphVector<CSphFilterSettings> * pExtraFilters, CSphQueryNodeCache * pNodeCache, int iTag ) const
开头部分只是一些索引查询前的铺垫工作,为下一层接口提供需要的参数的封装:
最终的查找,并对找到的doc进行ranking
现在我们已经从index中获取到docs,此时的doc缺乏docinfo信息,下面给它们安装上docinfo:
//sphinx.cpp bool CSphIndex_VLN::MatchExtended ( CSphQueryContext * pCtx, const CSphQuery * pQuery, int iSorters, ISphMatchSorter ** ppSorters, ISphRanker * pRanker, int iTag ) const
***********************************************************************************************************************************************
一直到现在,都停留在index抽象层中,没有涉及到任何具体的磁盘IO,从下面开始我们将进入的外存空间,进行具体的读磁盘索引操作。
***********************************************************************************************************************************************
//sphinxsearch.cpp template < typename STATE > int ExtRanker_T<STATE>::GetMatches ( int iFields, const int * pWeights )
从磁盘中读取匹配到的docs块和hits块
//sphinxsearch.cpp const ExtDoc_t * ExtRanker_c::GetFilteredDocs ()
这函数没什么好说的,对查询树找到的结果进行过滤,因为有些match是不想要的
//sphinxsearch.cpp const ExtDoc_t * ExtAnd_c::GetDocsChunk ( SphDocID_t * pMaxID )
前面已经提到过查询是分解到一颗查询树上进行的,树叶子节点为原子词条,而分支节点为and、or等孩子节点的组合操作,从下面的函数可以看到这点,这里是一个and节点,它将两个孩子节点的查询结果进行集合运算中的and运算:
//sphinxsearch.cpp const ExtDoc_t * ExtTerm_c::GetDocsChunk ( SphDocID_t * pMaxID )
下面看看对于原子词条是怎么读取和它相对应的docs列表的:
//sphinx.cpp virtual const CSphMatch & GetNextDoc ( DWORD * pDocinfo )
对于从文件中读取hitslist和读docs文件差不多,这里不再给出分析过程。
分析了这么大半天终于接近尾声,从输入一个查询短语,一直到从index文件中读出相关的文档列表,走过了查询的主要过程,但之间还有什么忽略掉了的地方,一些细节的地方,但它们对弄懂search流程并不那么重要,要具体弄懂每一个细节的地方还得花大量的时间精力去分析它,但这里就不在深入下去了。