天天看點

[搜尋引擎]Sphinx的介紹和原理探索

當然,有人會說資料庫的索引也可以做到sphinx索引,隻是資料結構不一樣而已,但是,最大的不同是sphinx就像一張沒有任何關系查詢支援的單表資料庫。而且,索引主要用在搜尋功能的實作而不是主要的資料來源。是以,你的資料庫也許是符合第三範式的,但索引會完全被非規範化而且主要包含需要被搜尋的資料。

另外一點,大部分資料庫都會遭遇一個内部碎片的問題,它們需要在一個大請求裡遭遇太多的半随機I/O任務。那就是說,考慮一個在資料庫的索引中,查詢指向索引,索引指向資料,如果資料因為碎片問題被分開在不同的磁盤中,那麼此次查詢将占用很長的時間。

What/Sphinx是什麼

定義

Sphinx是一個全文檢索引擎。

特性

  • 索引和性能優異
  • 易于內建SQL和XML資料源,并可使用SphinxAPI、SphinxQL或者SphinxSE搜尋接口
  • 易于通過分布式搜尋進行擴充
  • 高速的索引建立(在當代CPU上,峰值性能可達到10 ~ 15MB/秒)
  • 高性能的搜尋 (在1.2G文本,100萬條文檔上進行搜尋,支援高達每秒150~250次查詢)

Why/為什麼使用Sphinx

遇到的使用場景

遇到一個類似這樣的需求:使用者可以通過文章标題和文章搜尋到一片文章的内容,而文章的标題和文章的内容分别儲存在不同的庫,而且是跨機房的。

可選方案

A、直接在資料庫實作跨庫LIKE查詢

優點:簡單操作

缺點:效率較低,會造成較大的網絡開銷

B、結合Sphinx中文分詞搜尋引擎

優點:效率較高,具有較高的擴充性

缺點:不負責資料存儲

使用Sphinx搜尋引擎對資料做索引,資料一次性加載進來,然後做了是以之後儲存在記憶體。這樣使用者進行搜尋的時候就隻需要在Sphinx伺服器上檢索資料即可。而且,Sphinx沒有MySQL的伴随機磁盤I/O的缺陷,性能更佳。

其他典型使用場景

1、快速、高效、可擴充和核心的全文檢索

  • 資料量大的時候,比MyISAM和InnoDB都要快。
  • 能對多個源表的混合資料建立索引,不限于單個表上的字段。
  • 能将來自多個索引的搜尋結果進行整合。
  • 能根據屬性上的附加條件對全文搜尋進行優化。 

2、高效地使用WHERE子句和LIMIT字句

當在多個WHERE條件做SELECT查詢時,索引選擇性較差或者根本沒有索引支援的字段,性能較差。sphinx可以對關鍵字做索引。差別是,MySQL中,是内部引擎決定使用索引還是全掃描,而sphinx是讓你自己選擇使用哪一種通路方法。因為sphinx是把資料儲存到RAM中,是以sphinx不會做太多的I/O操作。而mysql有一種叫半随機I/O磁盤讀,把記錄一行一行地讀到排序緩沖區裡,然後再進行排序,最後丢棄其中的絕大多數行。是以sphinx使用了更少的記憶體和磁盤I/O。

3、優化GROUP BY查詢

在sphinx中的排序和分組都是用固定的記憶體,它的效率比類似資料集全部可以放在RAM的MySQL查詢要稍微高些。

4、并行地産生結果集

sphinx可以讓你從相同資料中同時産生幾份結果,同樣是使用固定量的記憶體。作為對比,傳統SQL方法要麼運作兩個查詢,要麼對每個搜尋結果集建立一個臨時表。而sphinx用一個multi-query機制來完成這項任務。不是一個接一個地發起查詢,而是把幾個查詢做成一個批處理,然後在一個請求裡送出。

5、向上擴充和向外擴充

  • 向上擴充:增加CPU/核心、擴充磁盤I/O
  • 向外擴充:多個機器,即分布式sphinx

6、聚合分片資料

适合用在将資料分布在不同實體MySQL伺服器間的情況。

例子:有一個1TB大小的表,其中有10億篇文章,通過使用者ID分片到10個MySQL伺服器上,在單個使用者的查詢下當然很快,如果需要實作一個歸檔分頁功能,展示某個使用者的所有朋友發表的文章。那麼就要同僚通路多台MySQL伺服器了。這樣會很慢。而sphinx隻需要建立幾個執行個體,在每個表裡映射出經常通路的文章屬性,然後就可以進行分頁查詢了,總共就三行代碼的配置。

介紹了Sphinx的工作原理,關于如何安裝的文章在網上有很多,筆者就不再複述了,現在繼續講解Sphinx的配置檔案,讓Sphinx工作起來。

How/如何使用Sphinx

Sphinx工作流程圖

[搜尋引擎]Sphinx的介紹和原理探索

流程圖解釋

Database:資料源,是Sphinx做索引的資料來源。因為Sphinx是無關存儲引擎、資料庫的,是以資料源可以是MySQL、PostgreSQL、XML等資料。

Indexer:索引程式,從資料源中擷取資料,并将資料生成全文索引。可以根據需求,定期運作Indexer達到定時更新索引的需求。

Searchd:Searchd直接與用戶端程式進行對話,并使用Indexer程式建構好的索引來快速地處理搜尋查詢。

APP:用戶端程式。接收來自使用者輸入的搜尋字元串,發送查詢給Searchd程式并顯示傳回結果。

Sphinx的工作原理

Sphinx的整個工作流程就是Indexer程式到資料庫裡面提取資料,對資料進行分詞,然後根據生成的分詞生成單個或多個索引,并将它們傳遞給searchd程式。然後用戶端可以通過API調用進行搜尋。

介紹了Sphinx工作原理以及Sphinx的配置之後,繼續介紹在Sphinx中,負責做索引的程式Indexer是如何做索引的。

sphinx使用配置檔案從資料庫讀出資料之後,就将資料傳遞給Indexer程式,然後Indexer就會逐條讀取記錄,根據分詞算法對每條記錄建立索引,分詞算法可以是一進制分詞/mmseg分詞。下面先介紹Indexer做索引時使用的資料結構和算法。

資料源配置

先來看一份資料源的配置檔案示例:

1 source test
 2  {
 3      type                    = mysql
 4  
 5      sql_host                = 127.0.0.1
 6      sql_user                = root
 7      sql_pass                = root
 8      sql_db                  = test
 9      sql_port                = 3306    # optional, default is 3306
10  
11      sql_query_pre           = SET NAMES utf8
12      sql_query            = SELECT id, name, add_time FROM tbl_test
13  
14      sql_attr_timestamp      = add_time
15  
16    sql_query_info_pre      = SET NAMES utf8
17      sql_query_info          = SELECT * FROM tbl_test WHERE id=$id
18  }      

其中

source後面跟着的是資料源的名字,後面做索引的時候會用到;

type:資料源類型,可以為MySQL,PostreSQL,Oracle等等;

sql_host、sql_user、sql_pass、sql_db、sql_port是連接配接資料庫的認證資訊;

sql_query_pre:定義查詢時的編碼

sql_query:資料源配置核心語句,sphinx使用此語句從資料庫中拉取資料;

sql_attr_*:索引屬性,附加在每個文檔上的額外的資訊(值),可以在搜尋的時候用于過濾和排序。設定了屬性之後,在調用Sphinx搜尋API時,Sphinx會傳回已設定了的屬性;

sql_query_info_pre:設定查詢編碼,如果在指令行下調試出現問号亂碼時,可以設定此項;

sql_query_info:設定指令行下傳回的資訊。

索引配置

1 index test_index
 2 {
 3     source                    = test
 4     path                      = /usr/local/coreseek/var/data/test
 5     docinfo                   = extern
 6     charset_dictpath          = /usr/local/mmseg3/etc/
 7     charset_type              = zh_cn.utf-8
 8     ngram_len                 = 1
 9     ngram_chars               = U+3000..U+2FA1F 
10 }      

index後面跟的test_index是索引名稱

source:資料源名稱;

path:索引檔案基本名,indexer程式會将這個路徑作為字首生成出索引檔案名。例如,屬性集會存在/usr/local/sphinx/data/test1.spa中,等等。

docinfo:索引文檔屬性值存儲模式;

charset_dictpath:中文分詞時啟用詞典檔案的目錄,該目錄下必須要有uni.lib詞典檔案存在;

charset_type:資料編碼類型;

ngram_len:分詞長度;

ngram_chars:要進行一進制字元切分模式認可的有效字元集。

中文分詞核心配置

一進制分詞

1 charset_type = utf8
2 
3 ngram_len = 1
4 
5 ngram_chars = U+3000..U+2FA1F      

mmseg分詞

1 charset_type = utf8
2 
3 charset_dictpath = /usr/local/mmseg3/etc/
4 
5 ngram_len = 0      

運作示例

資料庫資料

[搜尋引擎]Sphinx的介紹和原理探索

使用indexer程式做索引

[搜尋引擎]Sphinx的介紹和原理探索

查詢

[搜尋引擎]Sphinx的介紹和原理探索

可以看到,配置檔案中的add_time被傳回了,如上圖的1所示。而sql_query_info傳回的資訊如上圖的2所示。

Sphinx的配置不是很靈活,此處根據工作流程給出各部分的配置,更多的進階配置可以在使用時查閱文檔。

反向索引

反向索引是一種資料結構,用來存儲在全文搜尋下某個單詞在一個文檔或者一組文檔中的存儲位置的映射。它是文檔檢索系統中最常用的資料結構。

反向索引(Inverted Index):反向索引是實作“單詞-文檔矩陣”的一種具體存儲形式,通過反向索引,可以根據單詞快速擷取包含這個單詞的文檔清單。

傳統的索引是:索引ID->文檔内容,而反向索引是:文檔内容(分詞)->索引ID。可以類比正向代理和反向代理的差別來了解。正向代理把内部請求代理到外部,反向代理把外部請求代理到内部。是以應該了解為轉置索引比較合适。

反向索引主要由兩個部分組成:“單詞詞典”和“倒排檔案”。

單詞詞典是反向索引中非常重要的組成部分,它用來維護文檔集合中出現過的所有單詞的相關資訊,同時用來記載某個單詞對應的倒排清單在倒排檔案中的位置資訊。在支援搜尋時,根據使用者的查詢詞,去單詞詞典裡查詢,就能夠獲得相應的倒排清單,并以此作為後續排序的基礎。

對于一個規模很大的文檔集合來說,可能包含幾十萬甚至上百萬的不同單詞,能否快速定位某個單詞直接影響搜尋時的響應速度,是以需要高效的資料結構來對單詞詞典進行建構和查找,常用的資料結構包括哈希加連結清單結構和樹形詞典結構。

反向索引基礎知識

  • 文檔(Document):一般搜尋引擎的處理對象是網際網路網頁,而文檔這個概念要更寬泛些,代表以文本形式存在的存儲對象,相比網頁來說,涵蓋更多種形式,比如Word,PDF,html,XML等不同格式的檔案都可以稱之為文檔。再比如一封郵件,一條短信,一條微網誌也可以稱之為文檔。在本書後續内容,很多情況下會使用文檔來表征文本資訊。
  • 文檔集合(Document Collection):由若幹文檔構成的集合稱之為文檔集合。比如海量的網際網路網頁或者說大量的電子郵件都是文檔集合的具體例子。
  • 文檔編号(Document ID):在搜尋引擎内部,會将文檔集合内每個文檔賦予一個唯一的内部編号,以此編号來作為這個文檔的唯一辨別,這樣友善内部處理,每個文檔的内部編号即稱之為“文檔編号”,後文有時會用DocID來便捷地代表文檔編号。
  • 單詞編号(Word ID):與文檔編号類似,搜尋引擎内部以唯一的編号來表征某個單詞,單詞編号可以作為某個單詞的唯一表征。
 Indexer程式就是根據配置好地分詞算法,将擷取到的記錄進行分詞,然後用反向索引做資料結構儲存起來。

 分詞算法

一進制分詞的核心配置

1 charsey_type = zh_cn.utf8
2 ngram_len = 1
3 ugram_chars = U+4E00..U+9FBF      

ngram_len是分詞的長度。

ngram_chars辨別要進行一進制分詞切分模式的字元集。

原生的Sphinx支援的分詞算法是一進制分詞,這種分詞算法是對記錄的每個詞切割後做索引,這種索引的優點就是覆寫率高,保證每個記錄都能被搜尋到。缺點就是會生成很大的索引檔案,更新索引時會消耗很多的資源。是以,如果不是特殊需求,而且資料不是特别少的時候,都不建議使用一進制分詞。

國人在sphinx的基礎上開發了支援中文分詞的Coreseek。Coreseek與Sphinx唯一的不同就是Coreseek還支援mmseg分詞算法做中文分詞。

mmseg分詞算法是基于統計模型的,是以算法的規則也是來自對語料庫的分析和數學歸納,因為中文字元沒有明确的分界,會導緻大量的字元分界歧義,而且,中文裡面,詞和短語也很難界定,是以,算法除了要做統計和數學歸納之外,還要做歧義的解決。

在mmseg分詞中,有一個叫chunk的概念。

chunk,是一句話的分詞方式。包括一個詞條數組和四個規則。

如:研究所學生命,有“研究/生命”和“研究所學生/命”兩種分詞方式,這就是兩個chunk。

一個chunk有四個屬性:長度、平均長度(長度/分詞數)、方差、單字自由度(各單詞條詞頻的對數之和)。

做好分詞之後,會得到多種分詞方式,這時候就要使用一些過濾規則來完成歧義的解決,以得到最終的分詞方式。

歧義解決規則:

1、最大比對

比對最大長度的詞。如“國際化”,有“國際/化”、“國際化”兩種分詞方式,選擇後者。

2、最大平均詞長度

比對平均詞最大的chunk。如“南京市長江大橋”,有“南京市/長江大橋”、“南京/市長/江大橋”三種分詞方式,前者平均詞長度是7/2=3.5,後者是7/3=2.3,故選擇前者的分詞方式。

3、最大方差

去方差最大的chunk。如“研究所學生命科學”,有“研究所學生/命/科學”、“研究/生命/科學“兩種分詞方式,而它們的詞長都一樣是2。是以需要繼續過濾,前者方差是0.82,後者方差是0。是以選擇第一種分詞方式。

4、最大單字自由度

選擇單個字出現最高頻率的chunk。比如”主要是因為“,有”主要/是/因為“,”主/要是/因為“兩種分詞方式,它們的詞長、方差都一樣,而”是“的詞頻較高,是以選擇第一種分詞方式。

如果經過上述四個規則的過濾,剩下的chunk仍然大于一,那這個算法也無能為力了,隻能自己寫擴充完成。

最後的最後

總結

通過一個項目的實踐,發現sphinx的使用要點主要在配置檔案上,如果懂得配置了,那麼基本用法很容易掌握。如果要深入研究,比如研究其工作原理,那就得查閱更多的資料。進階特性還沒有用到,日後用到再做分享。最後,如果還想擴充sphinx,定制更強大的功能,可以直接閱讀源代碼,然後編寫擴充。使用sphinx也有弊端,如果需要保證高品質的搜尋,那麼就要經常手動維護詞庫。如果不能保持經常更新詞庫,那麼可以考慮百度搜尋之類的插件。如果可以加入機器學習的話,那麼會更好。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

如果本文對你有幫助,請點下推薦,寫文章不容易。

繼續閱讀