Dhtcrawler2換用sphinx搜索
dhtcrawler2最開始使用mongodb自帶的全文搜索引擎搜索資源。搜索一些短關鍵字時很容易導致erlang進程call timeout,也就是查詢時間太長。對于像avi這種關鍵字,搜索時間長達十幾秒。搜索的資源數量200萬左右。這其中大部分資源只是對root文件名進行了索引,即對于多文件資源而言沒有索引單個文件名。索引方式有部分資源是按照字符串子串的形式,沒有拆詞,非常占用存儲空間;有部分是使用了rmmseg(我編譯了rmmseg-cpp作為erlang nif庫調用 erl-rmmseg)進行了拆詞,占用空間小了很多,但由于詞庫問題很多片里的詞匯沒拆出來。
很早以前我以為搜索耗時的原因是因為數據庫太忙,想部署個mongodb集群出來。后來發現數據庫沒有任何讀寫的狀態下,查詢依然慢。終于只好放棄mongodb自帶的文本搜索。于是我改用sphinx。簡單起見,我直接下載了coreseek4.1(sphinx的一個支持中文拆詞的包裝)。
現在,已經導入了200多萬的資源進sphinx,并且索引了所有文件名,索引文件達800M。對于avi關鍵字的搜索大概消耗0.2秒的時間。搜索試試。
以下記錄下sphinx在dhtcrawler的應用
sphinx簡介
sphinx包含兩個主要的程序:indexer和searchd。indexer用于建立文本內容的索引,然后searchd基于這些索引提供文本搜索功能,而要使用該功能,可以遵循searchd的網絡協議連接searchd這個服務來使用。
indexer可以通過多種方式來獲取這些文本內容,文本內容的來源稱為數據源。sphinx內置mysql這種數據源,意思是可以直接從mysql數據庫中取得數據。sphinx還支持xmlpipe2這種數據源,其數據以xml格式提供給indexer。要導入mongodb數據庫里的內容,可以選擇使用xmlpipe2這種方式。
sphinx document
xmlpipe2數據源需要按照以下格式提交:
<sphinx:docset>
<sphinx:schema>
<sphinx:field name="subject"/>
<sphinx:field name="files"/>
<sphinx:attr name="hash1" type="int" bits="32"/>
<sphinx:attr name="hash2" type="int" bits="32"/>
</sphinx:schema>
<sphinx:document id="1">
<subject>this is the subject</subject>
<files>file content</files>
<hash1>111</hash1>
</sphinx:document>
</sphinx:docset>
該文件包含兩大部分:schema和documents,其中schema又包含兩部分:field和attr,其中由field標識的字段就會被indexer讀取并全部作為輸入文本建立索引,而attr則標識查詢結果需要附帶的信息;documents則是由一個個sphinx:document組成,即indexer真正要處理的數據。注意其中被schema引用的屬性名。
document一個很重要的屬性就是它的id。這個id對應于sphinx需要唯一,查詢結果也會包含此id。一般情況下,此id可以直接是數據庫主鍵,可用于查詢到詳細信息。searchd搜索關鍵字,其實可以看作為搜索這些document,搜索出來的結果也是這些document,搜索結果中主要包含schema中指定的attr。
增量索引
數據源的數據一般是變化的,新增的數據要加入到sphinx索引文件中,才能使得searchd搜索到新錄入的數據。要不斷地加入新數據,可以使用增量索引機制。增量索引機制中,需要一個主索引和一個次索引(delta index)。每次新增的數據都建立為次索引,然后一段時間后再合并進主索引。這個過程主要還是使用indexer和searchd程序。實際上,searchd是一個需要一直運行的服務,而indexer則是一個建立完索引就退出的工具程序。所以,這里的增量索引機制,其中涉及到的“每隔一定時間就合并”這種工作,需要自己寫程序來協調(或通過其他工具)
sphinx與mongodb
上面提到,一般sphinx document的id都是使用的數據庫主鍵,以方便查詢。但mongodb中默認情況不使用數字作為主鍵。dhtcrawler的資源數據庫使用的是資源info-hash作為主鍵,這無法作為sphinx document的id。一種解決辦法是,將該hash按位拆分,拆分成若干個sphinx document attr支持位數的整數。例如,info-hash是一個160位的id,如果使用32位的attr(高版本的sphinx支持64位的整數),那么可以把該info-hash按位拆分成5個attr。而sphinx document id則可以使用任意數字,只要保證不沖突就行。當獲得查詢結果時,取得對應的attr,組合為info-hash即可。
mongodb默認的Object id也可以按這種方式拆分。
dhtcrawler2與sphinx
dhtcrawler2中我自己寫了一個導入程序。該程序從mongodb中讀出數據,數據到一定量時,就輸出為xmlpipe2格式的xml文件,然后建立為次索引,最后合并進主索引。過程很簡單,包含兩次啟動外部進程的工作,這個可以通過erlang中os:cmd完成。
值得注意的是,在從mongodb中讀數據時,使用skip基本是不靠譜的,skip 100萬個數據需要好幾分鐘,為了不增加額外的索引字段,我只好在created_at字段上加索引,然后按時間段來讀取資源,這一切都是為了支持程序關閉重啟后,可以繼續上次工作,而不是重頭再來。200萬的數據,已經處理了好幾天了。
后頭數據建立好了,需要在前臺展示出來。erlang中似乎只有一個sphinx客戶端庫:giza。這個庫有點老,寫成的時候貌似還在使用sphinx0.9版本。其中查詢代碼包含了版本判定,已經無法在我使用的sphinx2.x版本中使用。無奈之下我只好修改了這個庫的源碼,幸運的是查詢功能居然是正常的,意味著sphinx若干個版本了也沒改動通信協議?后來,我為了取得查詢的統計信息,例如消耗時間以及總結果,我再一次修改了giza的源碼。新的版本可以在我的github上找到:my giza,看起來我沒侵犯版本協議吧?
目前dhtcrawler的搜索,先是基于sphinx搜索出hash列表,然后再去mongodb中搜索hash對應的資源。事實上,可以為sphinx的document直接附加這些資源的描述信息,就可以避免去數據庫查詢。但我想,這樣會增加sphinx索引文件的大小,擔心會影響搜索速度。實際測試時,發現數據庫查詢有時候還真的很消耗時間,盡管我做了分頁,以使得單頁僅對數據庫進行少量查詢。
xml unicode
在導入xml到sphinx的索引過程中,本身我輸出的內容都是unicode的,但有很多資源會導致indexer解析xml出錯。出錯后indexer直接停止對當前xml的處理。后來查閱資料發現是因為這些無法被indexer處理的xml內容包含unicode里的控制字符,例如 ä (U+00E4)。我的解決辦法是直接過濾掉這些控制字符。unicode的控制字符參看UTF-8 encoding table and Unicode characters。在erlang中干這個事居然不復雜:
strip_invalid_unicode(<<>>) ->
<<>>;
strip_invalid_unicode(<<C/utf8, R/binary>>) ->
case is_valid_unicode(C) of
true ->
RR = strip_invalid_unicode(R),
<<C/utf8, RR/binary>>;
false ->
strip_invalid_unicode(R)
end;
strip_invalid_unicode(<<_, R/binary>>) ->
strip_invalid_unicode(R).
is_valid_unicode(C) when C < 16#20 ->
false;
is_valid_unicode(C) when C >= 16#7f, C =< 16#ff ->
false;
is_valid_unicode(_) ->
true.
posted @ 2013-08-08 23:04 Kevin Lynx 閱讀(2769) | 評論 (0) | 編輯 收藏





