Sphinx使用及近實時索引實現


Sphinx介紹 

  Sphinx是由一個開源的全文檢索引擎,功能類似Lucune,用C++編寫,可爲其他應用提供高速、低空間佔用、高結果相關度的全文搜索功能。Sphinx可以非常容易的與SQL數據庫和腳本語言集成,當前系統內置MySQL和PostgreSQL數據庫數據源的支持,也支持從標準輸入讀取特定格式的XML數據,通過修改源代碼,用戶可以自行增加新的數據源(例如:其他類型的DBMS的原生支持) 

Sphinx特性 

  • 1:Sphinx支持高速建立索引(可達10MB/秒,而Lucene建立索引的速度是1.8MB/秒) 
    2:高性能的搜索(在2--4GB的文本數據上,平均每次檢索響應時間小於0.1秒) 
    3:高擴展性(實測最高可對100GB的文本建立索引,單一索引可包含1億條記錄) 
    4:提供了優秀的相關度算法,基於短語相似度和統計(BM25)的複合Ranking方法 
    5:支持分佈式搜索 
    6:支持短語搜索 
    7:可作爲MySQL的存儲引擎提供搜索服務 
    8:支持布爾、短語、詞語相似度等多種檢索模式 
    9:文檔支持多個全文檢索字段 


系統組成 
整個Sphinx系統由多個可執行程序和一套api組成,這裏引用Coreseek(一個基於Sphinx的開源檢索引擎,提供了良好的中文支持)的一個結構圖做個示例 

 

可執行程序 

  • 1:索引建立和維護程序(索引程序indexer) 
    2:查詢服務程序(後臺服務程序searchd) 
    3:輔助工具程序(search, spelldump等) 

api 

  • 1:應用程序api(包括ruby,C/C++, Python, php, java的程序api) 
    2:Mysql的SphinxSE引擎接口 
    3:SphinxQL支持 

工作流程  
安裝好Sphinx後,首先需要根據想要檢索的場景來建立對應的配置文件,Sphinx是以sphinx.conf爲配置文件,索引與搜索均以這個文件爲依據進行,要進行全文檢索,首先就要配置好sphinx.conf,告訴sphinx哪些字段需要進行索引,哪些字段需要在where,orderby,groupby中用到。 
該文件的結構大致如下: 
Java代碼  收藏代碼
  1. <![CDATA[source 源名稱1{  
  2. …  
  3. }  
  4. index 索引名稱1{  
  5. source=源名稱1  
  6. …  
  7. }  
  8. source 源名稱2{  
  9. …  
  10. }  
  11. index 索引名稱2{  
  12. source = 源名稱2  
  13. …  
  14. }  
  15. indexer{  
  16. …  
  17. }  
  18. searchd{  
  19. …  
  20. }  
  21. ]]>  

從配置文件的組成中我們可以發現Sphinx可以定義多個索引與數據源,不同的索引與數據源可以應用到不同表或不同應用的全文檢索方式。 
source 

以MySQL爲例,示範如何配置全量索引的數據源 
Java代碼  收藏代碼
  1. source poi_name  
  2. {  
  3.     type            = mysql          ######數據源類型  
  4.   
  5.     sql_host        = localhost           ######mysql主機  
  6.     sql_user        = root     ######mysql用戶名  
  7.     sql_pass        = ************   ######mysql密碼  
  8.     sql_db          = ***     ######mysql數據庫名  
  9.     sql_port        = 3306           ######mysql端口  
  10.   
  11.     sql_query_pre       = SET NAMES utf8   ###mysql檢索編碼,特別要注意這點,很多人中文檢索不到是數據庫的編碼是GBK或其他非UTF8  
  12.   
  13.     sql_query       = \  
  14.         SELECT id, poi_name, poi_name as name, branch_name, city_id, district_id, biz_area_id, type_id, level, latitude/1000000 latitude, longitude/1000000 longitude, complain_status, creator_id, create_time, check_status, modify_time, deleted, link_status \  
  15.         FROM poi        ####### 獲取數據的sql,這裏可以指定條件查詢進行過濾  
  16.   
  17.     #####以下是用來過濾或條件查詢的屬性,這裏列出的字段將可以進行條件查詢,同時不參與全文檢索############  
  18.     sql_attr_uint       = city_id  
  19.     sql_attr_uint       = district_id  
  20.     sql_attr_uint       = biz_area_id  
  21.     sql_attr_uint       = type_id  
  22.     sql_attr_uint       = level  
  23.     sql_attr_uint       = complain_status  
  24.     sql_attr_uint       = creator_id  
  25.     sql_attr_uint       = create_time  
  26.     sql_attr_uint       = check_status  
  27.     sql_attr_uint       = deleted  
  28.     sql_attr_uint       = modify_time  
  29.     sql_attr_uint       = link_status  
  30.     sql_attr_float      = latitude  
  31.     sql_attr_float      = longitude  
  32.     sql_attr_string     = poi_name       ####### poi_name字段將不參與全文檢索  
  33. }  

  增量索引的配置與之類似,只不過需要根據增量條件對獲取數據進行過濾,這裏以時間戳爲例(也可以通過對id設置更新記錄表等其它方式來設置增量條件) 
Java代碼  收藏代碼
  1. source poi_name_incr : poi_name  
  2. {  
  3.     sql_query       = \  
  4.         SELECT id, poi_name, poi_name as name, branch_name, city_id, district_id, biz_area_id, type_id, level, latitude/1000000 latitude, longitude/1000000 longitude, complain_status, creator_id, create_time, check_status, modify_time, deleted, link_status \  
  5.         FROM poi where create_time > unix_timestamp() - 360  
  6.     ...  
  7. }  

實時索引不需要設置數據源,直接在index裏配置爲rt即可 

index 
全量索引的index配置如下,這裏沒有配置採用外置的分詞插件如mmseg等 

Java代碼  收藏代碼
  1. index poi_name  
  2. {  
  3.     source          = poi_name     #### 聲明索引數據源  
  4.     path            = /opt/***/mtpoi/indexfiles/poi_name   #######索引文件存放路徑  
  5.     docinfo         = extern       #### 文檔信息存儲方式  
  6.     mlock           = 0            #### 緩存數據內存鎖定  
  7.     morphology      = none         #### 形態學(對中文無效)  
  8.     min_word_len        = 1        #### 索引的詞最小長度  
  9.     charset_type        = utf-8    #### 數據編碼  
  10.     ngram_len       = 1            #### 對於非字母型數據的長度切割  
  11.     ngram_chars     = U+3000..U+2FA1F  #加上這個選項,則會對每個中文,英文字詞進行分割  
  12.     charset_table   = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F    ##### 字符表,如使用這種方式,則Sphinx會對中文進行單字切分  
  13.     html_strip      = 0  
  14. }  

增量索引的index配置與之類似,只是將數據源及path設置爲增量索引的即可 

Java代碼  收藏代碼
  1. index poi_name_incr  
  2. {  
  3.     source          = poi_name_incr  
  4.     path            = /opt/***/mtpoi/indexfiles/poi_name_incr  
  5.   
  6.     ....  
  7. }  

實時索引由於不需要設置數據源,配置有些不同 
Java代碼  收藏代碼
  1. index poi_rt  
  2. {  
  3.     type               = rt       #### 聲明爲實時索引  
  4.     rt_mem_limit       = 512M  
  5.     path               = /opt/***/mtpoi/indexfiles/poi_rt  
  6.     charset_type       = utf-8  
  7.     charset_table        = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F  
  8.     ngram_chars        = U+3000..U+2FA1F  
  9.   
  10.     #### 實時索引的條件查詢字段 ####  
  11.     rt_attr_uint       = city_id  
  12.     rt_attr_uint       = district_id  
  13.     rt_attr_uint       = biz_area_id  
  14.     rt_attr_uint       = type_id  
  15.     rt_attr_uint       = level  
  16.     rt_attr_uint       = complain_status  
  17.     rt_attr_uint       = creator_id  
  18.     rt_attr_uint       = create_time  
  19.     rt_attr_uint       = check_status  
  20.     rt_attr_uint       = deleted  
  21.     rt_attr_uint       = modify_time  
  22.     rt_attr_uint       = link_status  
  23.     rt_attr_float      = latitude  
  24.     rt_attr_float      = longitude  
  25.     rt_attr_string     = poi_name  
  26.     #### 參與全文檢索的屬性 ####  
  27.     rt_field           = poi_name  
  28.     rt_field           = branch_name  
  29. }  


indexer 
indexer的配置比較簡單,一般來說不需要改動,配置完畢後執行indexer工具重建索引即可 

Java代碼  收藏代碼
  1. # 重建配置裏的全部索引,必須關閉searchd  
  2. /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***mtpoi/conf/sphinx.conf.incr --all 
  3. # 重建部分索引(poi_name_incr),可指定多個 
  4. /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr poi_name_incr  
  5. # searchd運行過程中更新索引,添加--ratate參數  
  6. /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr --rotate poi_name  


searchd 
searchd的配置項裏最主要的是監聽端口 
Java代碼  收藏代碼
  1. searchd  
  2. {  
  3.     listen          = 9346           # 監聽端口,api訪問端口  
  4.     listen          = 9340:mysql41   # SphinxQL訪問端口  
  5.     log             = /var/sankuai/logs/sphinx_poi_incr/sphinx-searchd.log  
  6.     query_log       = /var/sankuai/logs/sphinx_poi_incr/sphinx-query.log  
  7.     max_matches     = 10000          # 最大匹配結果,在某些情況下該數值會導致查詢不到結果,比如有設置分頁項時想獲取1w條之後的記錄  
  8.     query_log_format   = sphinxql    # 日誌查詢格式化,plain爲簡單文本格式,這裏採用sphinxql以獲取更豐富的查詢信息  
  9.     mysql_version_string   = 5.5.21  # 返回給通過SphinxQL訪問的MySQL版本號,目前採用的mysql-connector-java-5.1.15需要設置該值,否則連接時會報錯  
  10.     ....  
  11. }  


執行indexer建好索引後,直接啓動searchd即可啓用Sphinx查詢服務 
/usr/local/sphinx-2.1.0/bin/searchd -c /opt/***/mtpoi/conf/sphinx.conf.incr 
然後通過crontab等方式調用indexer來更新索引文件 

SphinxQL 
   Sphinx的searchd守護程序從版本0.9.9-rc2開始支持MySQL二進制網絡協議,並且能夠通過標準的MySQL API訪問 

Java代碼  收藏代碼
  1. $ mysql -P 9306  
  2. Welcome to the MySQL monitor.  Commands end with ; or \g.  
  3. Your MySQL connection id is 1  
  4. Server version: 0.9.9-dev (r1734)  
  5.   
  6. Type 'help;' or '\h' for help. Type '\c' to clear the buffer.  
  7.   
  8. mysql>  

    新的訪問方法是對原生API的一種補充,原生API仍然可用。事實上,兩種訪問方法可以同時使用。另外,原生API仍舊是默認的訪問方法。MySQL協議支持需要經過額外的配置才能啓用。當然這只需要更動一行配置文件,加入一個協議爲mysql41的監聽器(listener)就可以了: 
listen = 9340:mysql41 # SphinxQL訪問端口
 
分佈式索引 
除了實時索引之外,Sphinx還支持一種特殊的索引方式------分佈式索引,分佈式檢索可以改善查詢延遲問題(即縮短查詢時間)和提高多服務器、多CPU或多核環境下的吞吐率(即每秒可以完成的查詢數)。這對於大量數據(即十億級的記錄數和TB級的文本量)上的搜索應用來說是很關鍵的。其關鍵思想是對數據進行水平分區(HP,Horizontally partition),然後並行處理: 

  • 1:在不同服務器上設置Sphinx程序集(indexer和searchd)的多個實例 
    2:讓這些實例對數據的不同部分做索引(並檢索) 
    3:在searchd的一些實例上配置一個特殊的分佈式索引然後對這個索引進行查詢 


這個特殊索引只包括對其他本地或遠程索引的引用,因此不能對它執行重新建立索引的操作,相反,如果要對這個特殊索引進行重建,要重建的是那些被這個索引被引用到的索引。 
當searchd收到一個對分佈式索引的查詢時,它做如下操作: 


  • 1:連接到遠程代理 
    2:執行查詢 :#(在遠程代理執行搜索的同時)對本地索引進行查詢; 
    3:接收來自遠程代理的搜索結果 
    4:將所有結果合併,刪除重複項 
    5:將合併後的結果返回給客戶端 


在應用程序看來,普通索引和分佈式索引完全沒有區別。也就是說,分佈式索引對應用程序而言是完全透明的,實際上也無需知道查詢使用的索引是分佈式的還是本地的。<br/> 任一個searchd實例可以同時做爲主控端(master,對搜索結果做聚合)和從屬端(只做本地搜索)。這有如下幾點好處: 


  • 1: 集羣中的每臺機器都可以做爲主控端來搜索整個集羣,搜索請求可以在主控端之間獲得負載平衡,相當於實現了一種HA(high availability,高可用性),可以應對某個節點失效的情況. 
    2: 如果在單臺多CPU或多核機器上使用,一個做爲代理對本機進行搜索的searchd實例就可以利用到全部的CPU或者核 

這裏採用前述配置的幾種索引做一個簡單的分佈式索引配置示例 
Java代碼  收藏代碼
  1. index poi_dist  
  2. {  
  3.     type        =   distributed            #### 設置爲分佈式搜索  
  4.     local       =   poi_name               #### 設置查詢本地全量索引  
  5.     local       =   poi_name_incr          #### 設置查詢本地增量索引  
  6.     local       =   poi_rt                 #### 設置查詢本地實時索引  
  7.     agent       =   srv24:9340:poi_name    #### 也可以通過agent來進行查詢遠程全量索引  
  8. }  

更詳細的分佈式搜索的相關配置參數比如超時等參考官方文檔。 


近實時索引實現 
在商家數據中心的使用場景中,目前存在一些對實時性要求比較高的檢索需求,比如在CRM系統裏,對商家的審覈狀態進行審覈(0-&gt;1)後,頁面會自動刷新,此時會根據審覈狀態(1)進行查詢,如果實時性不夠的話此時會查詢不到該數據,而且使用原狀態(0)進行查詢的時候,依然能查詢到,這就要求目前的Sphinx查詢能夠儘可能的支持實時檢索。 

rt 
如前面介紹,現有的Sphinx是有實時索引這種類型的,但據一些文章說其在大數據量的情況下性能不太好,另外,其初始時是沒有數據的,而現有的數據庫裏已經有大約100w+的數據需要索引,全部採用rt索引看來不是一個好選擇 
參考: 

http://www.ivinco.com/blog/plain-rt-and-mixed-indexes-performance-comparison/ 
http://www.ivinco.com/blog/how-to-speed-up-sphinx-real-time-indexes-up-to-5-10-times/ 


全量+增量 
結合目前MDC中商家數據的實際情況(新增,更新相對較少),可以採用對穩定數據採用全量索引,對發生更新的數據採用增量索引,然後利用distributed的特性來合併查詢 

Java代碼  收藏代碼
  1. index poi_dist  
  2. {  
  3.     type        =   distributed            #### 設置爲分佈式搜索  
  4.     local       =   poi_name               #### 設置查詢本地全量索引  
  5.     local       =   poi_name_incr          #### 設置查詢本地增量索引  
  6. }  

增量索引由於數據量少,每次重建索引時耗時不到1s,可以做到5-10s左右更新一次,然後與全量索引進行merge,把增量索引更新到進來。 
Java代碼  收藏代碼
  1. indexer --merge DSTINDEX SRCINDEX --rotate  

這種方案依然存在一些問題: 

  •     1:由於索引合併的間隔問題,如果一條記錄被修改了,在還沒有執行增量索引合併前,全量索引裏依然是修改前的值,而增量索引已更新爲修改後的值,這樣在通過distributed來進行查詢時合併後的結果集可能並不符合預期(有可能查詢到修改前的記錄)。對這個問題,可以採用API裏提供的updateAttributes方法來實時更新索引的值,但Java版本的API目前僅支持對int,long類型的屬性進行實時更新;當然也可以採用SphinxQL來進行屬性的更新,其依然存在不支持非int,long類型的屬性即時更新問題,但由於增量索引可以重建的比較頻繁,在索引重建時會將這些非int,long類型的屬性修改進行更新,這樣對這些屬性的索引大約存在5-10s左右的延遲,對int,long屬性的修改可以即時索引進來. 
         2:由於其不支持即時新增索引項,只能等待增量索引重建時進行更新,所以其對新增記錄也存在5-10s左右的延遲 


全量+rt+SphinxQL 
和全量+增量的方式類似,只不過將增量索引換成直接使用rt索引,然後類似進行merge合併,其好處是可以即時將新增或修改的記錄反映到索引中(這裏對新增索引必須採用SphinxQL,目前Java版本的API不支持新增索引記錄),但對於非int,long類型的屬性修改依然沒有什麼好辦法,只能等待執行索引更新時進行更新,但全量索引的更新相對週期比較長,所以相對延遲會比較大。 

與Lucene的簡單對比 

  • 對Lucene暫時接觸不深,簡單對比一下: 
    1:   Sphinx建索引速度非常的快;Lucene建索引相比Sphinx要差很多,同樣建1000w數據,Sphinx2分鐘以內,Lucene10分鐘多,不過搜索性能上相差不太大 
    2:   Sphinx的索引結構必須提前預定義好;Lucene的索引結構是比較自由的 
    3:   Sphinx查詢中Attribute(屬性)的概念,而且Sphinx在啓動Searchd的時候會將所有屬性加載到內存中;而Lucene則沒有,雖然Lucene也有NumericField,但是底層仍然是作爲String處理的。這點可能會導致Sphinx比Lucene查詢性能上好一些 
    4:   Lucene用Java,代碼閱讀上相對容易 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章