1. 程式人生 > >高效能搜尋引擎sphinx原始碼解析之搜尋過程和評分公式

高效能搜尋引擎sphinx原始碼解析之搜尋過程和評分公式

技術交流qq群: 659201069


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;