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中用到。
該文件的結構大致如下:
- <![CDATA[source 源名稱1{
- …
- }
- index 索引名稱1{
- source=源名稱1
- …
- }
- source 源名稱2{
- …
- }
- index 索引名稱2{
- source = 源名稱2
- …
- }
- indexer{
- …
- }
- searchd{
- …
- }
- ]]>
從配置文件的組成中我們可以發現Sphinx可以定義多個索引與數據源,不同的索引與數據源可以應用到不同表或不同應用的全文檢索方式。
source
以MySQL爲例,示範如何配置全量索引的數據源
- source poi_name
- {
- type = mysql ######數據源類型
- sql_host = localhost ######mysql主機
- sql_user = root ######mysql用戶名
- sql_pass = ************ ######mysql密碼
- sql_db = *** ######mysql數據庫名
- sql_port = 3306 ######mysql端口
- sql_query_pre = SET NAMES utf8 ###mysql檢索編碼,特別要注意這點,很多人中文檢索不到是數據庫的編碼是GBK或其他非UTF8
- sql_query = \
- 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 \
- FROM poi ####### 獲取數據的sql,這裏可以指定條件查詢進行過濾
- #####以下是用來過濾或條件查詢的屬性,這裏列出的字段將可以進行條件查詢,同時不參與全文檢索############
- sql_attr_uint = city_id
- sql_attr_uint = district_id
- sql_attr_uint = biz_area_id
- sql_attr_uint = type_id
- sql_attr_uint = level
- sql_attr_uint = complain_status
- sql_attr_uint = creator_id
- sql_attr_uint = create_time
- sql_attr_uint = check_status
- sql_attr_uint = deleted
- sql_attr_uint = modify_time
- sql_attr_uint = link_status
- sql_attr_float = latitude
- sql_attr_float = longitude
- sql_attr_string = poi_name ####### poi_name字段將不參與全文檢索
- }
增量索引的配置與之類似,只不過需要根據增量條件對獲取數據進行過濾,這裏以時間戳爲例(也可以通過對id設置更新記錄表等其它方式來設置增量條件)
- source poi_name_incr : poi_name
- {
- sql_query = \
- 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 \
- FROM poi where create_time > unix_timestamp() - 360
- ...
- }
實時索引不需要設置數據源,直接在index裏配置爲rt即可
index
全量索引的index配置如下,這裏沒有配置採用外置的分詞插件如mmseg等
- index poi_name
- {
- source = poi_name #### 聲明索引數據源
- path = /opt/***/mtpoi/indexfiles/poi_name #######索引文件存放路徑
- docinfo = extern #### 文檔信息存儲方式
- mlock = 0 #### 緩存數據內存鎖定
- morphology = none #### 形態學(對中文無效)
- min_word_len = 1 #### 索引的詞最小長度
- charset_type = utf-8 #### 數據編碼
- ngram_len = 1 #### 對於非字母型數據的長度切割
- ngram_chars = U+3000..U+2FA1F #加上這個選項,則會對每個中文,英文字詞進行分割
- charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F ##### 字符表,如使用這種方式,則Sphinx會對中文進行單字切分
- html_strip = 0
- }
增量索引的index配置與之類似,只是將數據源及path設置爲增量索引的即可
- index poi_name_incr
- {
- source = poi_name_incr
- path = /opt/***/mtpoi/indexfiles/poi_name_incr
- ....
- }
實時索引由於不需要設置數據源,配置有些不同
- index poi_rt
- {
- type = rt #### 聲明爲實時索引
- rt_mem_limit = 512M
- path = /opt/***/mtpoi/indexfiles/poi_rt
- charset_type = utf-8
- charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
- ngram_chars = U+3000..U+2FA1F
- #### 實時索引的條件查詢字段 ####
- rt_attr_uint = city_id
- rt_attr_uint = district_id
- rt_attr_uint = biz_area_id
- rt_attr_uint = type_id
- rt_attr_uint = level
- rt_attr_uint = complain_status
- rt_attr_uint = creator_id
- rt_attr_uint = create_time
- rt_attr_uint = check_status
- rt_attr_uint = deleted
- rt_attr_uint = modify_time
- rt_attr_uint = link_status
- rt_attr_float = latitude
- rt_attr_float = longitude
- rt_attr_string = poi_name
- #### 參與全文檢索的屬性 ####
- rt_field = poi_name
- rt_field = branch_name
- }
indexer
indexer的配置比較簡單,一般來說不需要改動,配置完畢後執行indexer工具重建索引即可
- # 重建配置裏的全部索引,必須關閉searchd
- /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***mtpoi/conf/sphinx.conf.incr --all
- # 重建部分索引(poi_name_incr),可指定多個
- /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr poi_name_incr
- # searchd運行過程中更新索引,添加--ratate參數
- /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr --rotate poi_name
searchd
searchd的配置項裏最主要的是監聽端口
- searchd
- {
- listen = 9346 # 監聽端口,api訪問端口
- listen = 9340:mysql41 # SphinxQL訪問端口
- log = /var/sankuai/logs/sphinx_poi_incr/sphinx-searchd.log
- query_log = /var/sankuai/logs/sphinx_poi_incr/sphinx-query.log
- max_matches = 10000 # 最大匹配結果,在某些情況下該數值會導致查詢不到結果,比如有設置分頁項時想獲取1w條之後的記錄
- query_log_format = sphinxql # 日誌查詢格式化,plain爲簡單文本格式,這裏採用sphinxql以獲取更豐富的查詢信息
- mysql_version_string = 5.5.21 # 返回給通過SphinxQL訪問的MySQL版本號,目前採用的mysql-connector-java-5.1.15需要設置該值,否則連接時會報錯
- ....
- }
執行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訪問
- $ mysql -P 9306
- Welcome to the MySQL monitor. Commands end with ; or \g.
- Your MySQL connection id is 1
- Server version: 0.9.9-dev (r1734)
- Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
- 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或者核
這裏採用前述配置的幾種索引做一個簡單的分佈式索引配置示例
- index poi_dist
- {
- type = distributed #### 設置爲分佈式搜索
- local = poi_name #### 設置查詢本地全量索引
- local = poi_name_incr #### 設置查詢本地增量索引
- local = poi_rt #### 設置查詢本地實時索引
- agent = srv24:9340:poi_name #### 也可以通過agent來進行查詢遠程全量索引
- }
更詳細的分佈式搜索的相關配置參數比如超時等參考官方文檔。
近實時索引實現
在商家數據中心的使用場景中,目前存在一些對實時性要求比較高的檢索需求,比如在CRM系統裏,對商家的審覈狀態進行審覈(0->1)後,頁面會自動刷新,此時會根據審覈狀態(1)進行查詢,如果實時性不夠的話此時會查詢不到該數據,而且使用原狀態(0)進行查詢的時候,依然能查詢到,這就要求目前的Sphinx查詢能夠儘可能的支持實時檢索。
rt
如前面介紹,現有的Sphinx是有實時索引這種類型的,但據一些文章說其在大數據量的情況下性能不太好,另外,其初始時是沒有數據的,而現有的數據庫裏已經有大約100w+的數據需要索引,全部採用rt索引看來不是一個好選擇
參考:
http://www.ivinco.com/blog/how-to-speed-up-sphinx-real-time-indexes-up-to-5-10-times/
全量+增量
結合目前MDC中商家數據的實際情況(新增,更新相對較少),可以採用對穩定數據採用全量索引,對發生更新的數據採用增量索引,然後利用distributed的特性來合併查詢
- index poi_dist
- {
- type = distributed #### 設置爲分佈式搜索
- local = poi_name #### 設置查詢本地全量索引
- local = poi_name_incr #### 設置查詢本地增量索引
- }
增量索引由於數據量少,每次重建索引時耗時不到1s,可以做到5-10s左右更新一次,然後與全量索引進行merge,把增量索引更新到進來。
- 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,代碼閱讀上相對容易