高性能搜索引擎sphinx源码解析之搜索过程和评分公式

来源:互联网 发布:mac显示路径命令 编辑:程序博客网 时间:2024/05/16 19:08

sphinx搜索(select)逻辑

用输入的查询词在索引文件中挨个进行比较,找到满足关系的文档的过程,并读出文档,给每个文件打分,最后打分完成后进行排序,随后获取到排序后的文档列表的过程。

sphinx搜索过程包括以下步骤

1)        搜收用户输入,并存储,存储格式CsphString,字符串形式,例如select id,weight() ,list_namefrom LISTING wherematch('金');

2)        解析用户输入,解析完成后每条语句以SqlStmt_t格式存储

下面对SqlStmt_t作出简要分析

3)        SqlStmt_t结构成员CsphQuery,此类存储着查询所需的所有信息

示意图如下,全部成员见类图,下图只给出主成员


4)        根据SqlStmt_t结构中命令动词,来匹配不同的程序分支,这里只分析select流程

5)        pLocalSorter =sphCreateQueue( tQueueSettings );创建优先级队列,用于对结果排序,默认以weight排序,weight越大在队列中的优先级越高,按照优先级依次出队就完成了排序

6)        得到ram chunk 和disk chunk的指针,创建分词器

7)        对搜索字符串进行分词,分词结束后语法树逻辑结构如下


8)        创建ranker

9)        通过查找hash表,获得分词和DocID关系结构图


分词和DocId的结构图


获得每个分词DocID所占连续内存入口结构图

在hash表ExtQwordsHash_t中ExtQword_t是Key与入口地址是一对一的关系

获得DocID过程

a)  由分词搜索ram chunk中的hash表,得结构ExtQword_t

b)  结构中存储着每个分词DocID的数目

c) 通ExtQword_t可获得每个分词DocID内存所对应的入口地址

d)  分词的DocID在内存中占着连续的内存空间,这样就可以从内存中直接读出所有的DocID   即可

e)    过滤掉KILLlist中的DocID

f)      search RAM chunk,给每个DocID安装DocInfo信息,给每条DocID打分

下面是如何查找属性信息,这里只是找到这条文档的属性在RtSegment_t中的指针,用于属性过滤,以下是docinfo的内存存储结构


struct RtSegment_t : ISphNoncopyable{     CSphTightVector<CSphRowitem>       m_dRows;      ///<row data    CSphTightVector<BYTE>       m_dStrings;       ///< strings storage    CSphTightVector<DWORD>      m_dMvas;      ///<MVAs storage}

通过二分查找,找到这条文档在m_dRows的位置,就可获得这条文件档属性在m_dStrings和m_dMvas的指针和位移,然后进行属性过滤

10)        把结果存储在ISphMatchSorter中,在multiQuery函中完成对Ram chunk 、Disk chunk的搜索

11)        把结果存储到SearchHandler_c的成员,m_dResults中,同时释放ISphMatchSorter结构内存,在RunLocalSearches中完成结果集的转存

搜索Diskchunk的过程

搜索Disk chunk过程与内存大致相同,多了一步读文件,关于索引文件(spa,spd,…)的处理方法在第三部分讲解


权重算法详解

权重因子

1)Hits


举例说明

insert into LISTING(id,rtf_list_name) values(-99, '金龙鱼金龙鱼特香纯正花生油5L'');

insert into LISTING(id,rtf_list_name, rtf_channel) values(-98, '金龙鱼金龙鱼特香纯正花生油5L',’金龙鱼大小龙鱼');

用如下 selecl id,weight from LISTING where (‘龙鱼’);show meta;

此时,

龙鱼,hits:6

-99,uMatchHits:2

-98,uMatchHits:4

 

Fields, 代表每个分词所对应的field的字段,索引建立时确定,举例说明,假设索引字段编码如下,sphinx用一个32无符号整数对每个索引字段编码


insert into LISTING(id,rtf_list_name, rtf_standard_channel) values(-98, ‘金龙鱼特香纯正花生油5L’, ‘金龙鱼特香纯正花生油5L’)    

这条文档的m_uDocFields为6,对应分词,龙鱼

pDoc->m_uDocFields

 tDoc.m_uDocFields =m_pQword->m_dQwordFields.GetMask32() & m_dQueriedFields.GetMask32();

 

m_iWeight = m_iWeight + uRank*SPH_BM25_SCALE其中,m_iWeight是BM25算法得到,uRank相似度


1、用户可以为每个field指定weight,格式optionfield_weights=(rtf_list_name=10),默认为1,这样可以加大或减小每个field所占的权重比例

2、每个分词的IDF计算方法, IDF是指在整个文档集中的反向文档频率。常见词(如“the” or “to”等)的IDF值小,罕见词的IDF值大,当一个关键词只在一个文档中出现时,达到峰值IDF=1,

而当关键词在每个索引文档都出现时,IDF=-1

float fLogTotal = logf (float ( 1+iTotalClamped ) );

fIDF = logf (float (iTotalClamped-iTermDocs+1 ) / float ( iTermDocs ) ) /( 2*fLogTotal );

fIDF /= iQwords;

参数说明:

1)   iTotalClamped索引中总的DOcID数目,就是select count(*) 的输出结果

2)   iTermDocs每个分词对应的DocID数目,对金,为docs[0],对 龙鱼,为docs[1]

3)   iQwords,一个查询语句中,分词的个数,对下面的例子是2

举例:

select id,weight() ,list_name from LISTING where match('金龙鱼') limit 44;show meta;

输出如下:


每条文档的uRank,,相似度计算方法:  

struct ExtDoc_t{    SphDocID_t    m_uDocid;    CSphRowitem * m_pDocinfo;          ///< for inline storage only    SphOffset_t       m_uHitlistOffset;    DWORD         m_uDocFields;    float         m_fTFIDF;};     for ( inti=0; i<iWeights; i++ )    if( pDoc->m_uDocFields & (1<<i) )    {      uRank += m_pWeights[i];} 

参数说明:

1)   iWeights总的field数目

2)   m_pWeights[i]用户给每个field指定的权重值,默认是1

3)   pDoc->m_uDocFields

  tDoc.m_uDocFields =m_pQword->m_dQwordFields.GetMask32() & m_dQueriedFields.GetMask32();

dQueriedFields,查询时指定的字段,例如match(‘@rtf_list_name 金龙鱼’),值是4,如果没有指定field字段32位全是1

BM25参数,tDoc.m_fTFIDF每个分词对应多个文档,每个文档对应一个此参数

 tDoc.m_fTFIDF= float(m_pQword->m_uMatchHits)/ float(m_pQword->m_uMatchHits+SPH_BM25_K1) * m_fIDF;

参数说明:

   m_pQword->m_uMatchHits每个文档对应hit数目

   SPH_BM25_K1=1.2f

   m_fIDF为上面计算出的值


 

m_iWeight,为临时变量,m_iWeight = (int)((m_fTFIDF+0.5f)*SPH_BM25_SCALE )

参数说明:SPH_BM25_SCALE=1000

最终权重

m_iWeight = m_iWeight + uRank*SPH_BM25_SCALE

uRank最小为1,由此可能权重一定大于1000


多关键词处理方法

AND:将两个孩子节点获取到的doc合并起来,过滤掉docId不相同的,权重相加,代码如下

tDoc.m_uDocFields = pCur0->m_uDocFields| pCur1->m_uDocFields;

tDoc.m_fTFIDF = pCur0->m_fTFIDF + pCur1->m_fTFIDF;

OR:依次取前两个结点,如果DocID相同,权重相加,代码如下

m_dDocs[iDoc].m_uDocFields =pCur0->m_uDocFields | pCur1->m_uDocFields;m_dDocs[iDoc].m_fTFIDF = pCur0->m_fTFIDF+ pCur1->m_fTFIDF;

 


原创粉丝点击