項目需要用到全文檢索。
之前接觸的都是java中lucene + 分詞庫的解決方案?,F(xiàn)在公司使用的都是coreseek,且項目使用mongodb作為數(shù)據(jù)庫,團(tuán)長讓我研究一下解決的方案,所以記錄一下學(xué)習(xí)與使用過程。
Coreseek
Coreseek是一個開源的中文全文檢索引擎,基于Sphinx研發(fā)并獨(dú)立發(fā)布。在Sphinx在基礎(chǔ)上增加了LibMMSeg中文分詞包,實現(xiàn)了對中文的分詞與檢索。
Sphinx是由俄羅斯人開發(fā)的高性能全文檢索引擎。
全文檢索引擎本身的原理是類似的,網(wǎng)上有很多文章介紹的挺好,這里直接引用一下:
Sphinx的一大特點是與mysql數(shù)據(jù)庫結(jié)合良好,只需在配置文件中寫好SQL查詢語句就能定義索引數(shù)據(jù)庫源。
但此次項目使用mongodb,Sphinx不能直接支持。
配置索引數(shù)據(jù)源
按照官方手冊配置索引數(shù)據(jù)源,例如一個mysql數(shù)據(jù)源的示例配置:
#源定義
source mysql
{
type = mysql #表示使用mysql數(shù)據(jù)源
sql_host = localhost #表示數(shù)據(jù)庫服務(wù)器的鏈接地址
sql_user = root #表示數(shù)據(jù)庫的用戶名
sql_pass = 123456 #表示數(shù)據(jù)庫的密碼
sql_db = test #表示數(shù)據(jù)庫的名稱
sql_port = 3306 #表示數(shù)據(jù)庫的端口
sql_query_pre = SET NAMES utf8
#從數(shù)據(jù)庫之中讀取數(shù)據(jù)的SQL語句設(shè)置
sql_query = SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content FROM documents
#sql_query第一列id需為整數(shù),且被系統(tǒng)使用,無需再設(shè)置sql_attr_uint
#使用sql_attr設(shè)置的字段,只能作為屬性,使用SphinxClient::SetFilter()進(jìn)行過濾;未被設(shè)置的字段,自動作為全文檢索的字段,使用SphinxClient::Query("搜索字符串")進(jìn)行全文搜索;
#title、content作為字符串/文本字段,被全文索引
sql_attr_uint = group_id #從SQL讀取到的值必須為整數(shù);sql_attr_uint表示該字段是數(shù)值屬性
sql_attr_timestamp = date_added #從SQL讀取到的值必須為整數(shù),作為時間屬性;sql_attr_timestamp表示該字段是時間屬性;可以不用該配置
sql_query_info_pre = SET NAMES utf8 #命令行查詢時,設(shè)置正確的字符集,3.2.14開始支持
sql_query_info = SELECT * FROM documents WHERE id=$id #命令行查詢時,從數(shù)據(jù)庫讀取原始數(shù)據(jù)信息
}
#index定義
index mysql
{
source = mysql #對應(yīng)的source名稱
path = var/data/mysql #索引存放的位置,路徑為var/data
docinfo = extern
mlock = 0
morphology = none
min_word_len = 1
html_strip = 0
#charset_dictpath = /usr/local/mmseg3/etc/ #BSD、Linux環(huán)境下設(shè)置,/符號結(jié)尾
charset_dictpath = etc/ #Windows環(huán)境下設(shè)置,/符號結(jié)尾
charset_type = zh_cn.utf-8
}
建立索引數(shù)據(jù)
執(zhí)行indexer建立索引數(shù)據(jù),命令格式為:
indexer -c 配置文件的路徑 index名稱
例如:
/usr/local/coreseek/bin/indexer -c etc/csft.conf questions
啟動檢索服務(wù)
檢索服務(wù)需要配置守護(hù)進(jìn)程的相關(guān)選項,示例為:
#定義searchd守護(hù)進(jìn)程的相關(guān)選項
searchd
{
#定義監(jiān)聽的IP和端口
#listen = 127.0.0.1
#listen =172.16.88.100:3312
listen = 3312
listen = /var/run/searchd.sock
#定義log的位置
log =/usr/local/coreseek/var/log/searchd.log
#定義查詢log的位置
query_log =/usr/local/coreseek/var/log/query.log
#定義網(wǎng)絡(luò)客戶端請求的讀超時時間
read_timeout = 5
#定義子進(jìn)程的最大數(shù)量
max_children = 300
#設(shè)置searchd進(jìn)程pid文件名
pid_file =/usr/local/coreseek/var/log/searchd.pid
#定義守護(hù)進(jìn)程在內(nèi)存中為每個索引所保持并返回給客戶端的匹配數(shù)目的最大值
max_matches = 100000
#啟用無縫seamless輪轉(zhuǎn),防止searchd輪轉(zhuǎn)在需要預(yù)取大量數(shù)據(jù)的索引時停止響應(yīng)
#也就是說在任何時刻查詢都可用,或者使用舊索引,或者使用新索引
seamless_rotate = 1
#配置在啟動時強(qiáng)制重新打開所有索引文件
preopen_indexes = 1
#設(shè)置索引輪轉(zhuǎn)成功以后刪除以.old為擴(kuò)展名的索引拷貝
unlink_old = 1
# MVA更新池大小,這個參數(shù)不太明白
mva_updates_pool = 1M
#最大允許的包大小
max_packet_size = 32M
#最大允許的過濾器數(shù)
max_filters = 256
#每個過濾器最大允許的值的個數(shù)
max_filter_values = 4096
}
配置完畢后,執(zhí)行searchd啟動或關(guān)閉檢索服務(wù),命令格式為:
searchd -c 配置文件的路徑
searchd -c 配置文件的路徑 --stop
例如:
/usr/local/coreseek/bin/searchd -c etc/csft.conf
/usr/local/coreseek/bin/searchd -c etc/csft.conf --stop
程序中api的調(diào)用
官方的安裝內(nèi)提供了Php, Python, Java, C, Ruby等語言的api,這里列出一個項目中使用的Java版api調(diào)用示例:
SphinxClient cl = new SphinxClient();
cl.SetServer("192.168.19.135", 9314); //sphinx服務(wù)器與端口
cl.SetMatchMode(SphinxClient.SPH_MATCH_ANY); //匹配任意
cl.SetSortMode(SphinxClient.SPH_SORT_RELEVANCE, ""); //按匹配程序排序
cl.SetLimit(0, 20); //分頁參數(shù)
int[] users = {0, 839017};
cl.SetFilter("check_user", users, false); //根據(jù)條件過濾
SphinxResult res = cl.Query("一元一次方程", "faq_question"); //指定哪個索引集中檢索
System.out.println("Query retrieved " + res.total + " of " + res.totalFound + " matches in " + res.time + " sec.");
System.out.println("Query stats:");
for(int i=0; i<res.words.length; i++){
SphinxWordInfo wordInfo = res.words[i]; //words返回分詞結(jié)果
System.out.println("\t'" + wordInfo.word + "' found " + wordInfo.hits + " times in " + wordInfo.docs + " documents");
}
for(int i=0; i<res.matches.length; i++){
SphinxMatch info = res.matches[i]; //matches返回搜索結(jié)果
System.out.println((i + 1) + ". id=" + info.docId + ", weight=" + info.weight);
}
詳細(xì)的api參考。
在Coreseek/Sphinx中支持Mongodb數(shù)據(jù)源
在Sphinx中支持Mongodb主要有兩種方式,xmlpipe與python數(shù)據(jù)源。
xmlpipe數(shù)據(jù)源
編寫一個自己的服務(wù),將Mongodb數(shù)據(jù)轉(zhuǎn)換成Sphinx能夠識別的xml文本數(shù)據(jù)源,再調(diào)用api生成索引數(shù)據(jù)。
xml數(shù)據(jù)示例:
<?xml version="1.0" encoding="utf-8"?>
<sphinx:docset>
<sphinx:schema>
<sphinx:field name="subject"/>
<sphinx:field name="content"/>
<sphinx:attr name="published" type="timestamp"/>
<sphinx:attr name="author_id" type="int" bits="16" default="1"/>
</sphinx:schema>
<sphinx:document id="1">
<subject>愚人節(jié)最佳蠱惑爆料 谷歌300億美元收購百度</subject>
<published>1270131607</published>
<content>據(jù)國外媒體報道,谷歌將巨資收購百度,涉及金額高達(dá)300億美元。谷歌借此重返大陸市場。......
</content>
<author_id>1</author_id>
</sphinx:document>
<sphinx:document id="2">
<subject>Twitter主頁改版 推普通用戶消息增加趨勢話題</subject>
<published>1270135548</published>
<content>4月1日消息,據(jù)國外媒體報道,Twitter本周二推出新版主頁,目的很簡單:幫助新用戶了解Twitter和增加用戶黏稠度。......
</content>
<author_id>1</author_id>
</sphinx:document>
<sphinx:document id="3">
<subject>死都要上!Opera Mini 體驗版搶先試用</subject>
<published>1270094460</published>
<content>Opera一直都被認(rèn)為是瀏覽速度飛快,同時在移動平臺上更是占有不少的份額。......
</content>
<author_id>2</author_id>
</sphinx:document>
</sphinx:docset>
Sphinx的配置:
#源定義
source xml
{
type = xmlpipe2
xmlpipe_command = bin\cat var/test/test.xml #此處也可使用其他可執(zhí)行程序輸出xml數(shù)據(jù)
}
參考:
python數(shù)據(jù)源
Python數(shù)據(jù)源也稱為萬能數(shù)據(jù)源,借助Python強(qiáng)大的靈活性與活躍度,Sphinx做了一層橋接,此方式幾乎可以讀取所有形式的數(shù)據(jù)。
官網(wǎng)的代碼例子:
# -*- coding:utf-8 -*-
# coreseek3.2 python source演示操作mssql數(shù)據(jù)庫
# author: HonestQiao
# date: 2010-06-01 10:05
from os import path
import os
import sys
import pymssql
import datetime
class MainSource(object):
def __init__(self, conf):
self.conf = conf
self.idx = 0
self.data = []
self.conn = None
self.cur = None
def GetScheme(self): #獲取結(jié)構(gòu),docid、文本、整數(shù)
return [
('threadid' , {'docid':True, } ),
('title', { 'type':'text'} ),
('context', { 'type':'text'} ),
('date', {'type':'integer'} ),
]
def GetFieldOrder(self): #字段的優(yōu)先順序
return [('title', 'context')]
def Connected(self): #如果是數(shù)據(jù)庫,則在此處做數(shù)據(jù)庫連接
if self.conn==None:
self.conn = pymssql.connect(host='127.0.0.1', user='root', password='123456', database='bbs', as_dict=True)
self.cur = self.conn.cursor()
sql = 'SELECT threadid,title,content,date FROM ss_bbs_topic'
self.cur.execute(sql)
self.data = [ row for row in self.cur]
pass
def NextDocument(self): #取得每一個文檔記錄的調(diào)用
if self.idx < len(self.data):
item = self.data[self.idx]
self.docid = self.threadid = item['threadid'] #'docid':True
self.title = item['title'].encode('utf-8')
self.context = item['context'].encode('utf-8')
self.date = item['date']
self.idx += 1
return True
else:
return False
if __name__ == "__main__": #直接訪問演示部分
conf = {}
source = MainSource(conf)
source.Connected()
while source.NextDocument():
print "id=%d, subject=%s" % (source.docid, source.title)
pass
#eof
其中核心的概念是利用python將目標(biāo)數(shù)據(jù)按規(guī)定的模型讀取至python虛擬機(jī)中,Sphinx再從虛擬機(jī)里讀出索引源數(shù)據(jù)。
相關(guān)配置文件:
#python路徑定義
python
{
path = /usr/local/coreseek/etc/pysource #BSD、Linux環(huán)境下設(shè)置
path = /usr/local/coreseek/etc/pysource/csft_demo #BSD、Linux環(huán)境下設(shè)置
#path = etc/pysource #Windows環(huán)境下設(shè)置,最好給出絕對路徑
#path = etc/pysource/csft_demo #Windows環(huán)境下設(shè)置,最好給出絕對路徑
}
#源定義
source python
{
type = python
name = csft_demo.MainSource #對應(yīng)etc/pysource/csft_demo/__init__.py中的MainSource
}
參考:
項目服務(wù)架構(gòu)
項目使用xmlpipe的方案支持mongodb數(shù)據(jù),實現(xiàn)后的架構(gòu)如圖:

- 索引數(shù)據(jù)源服務(wù)定時生成
xml,調(diào)用coreseek命令生成索引 - 客戶端請求
Restful接口,服務(wù)端組裝搜索條件,調(diào)用coreseek的api返回數(shù)據(jù)主鍵, - 從
mongodb中獲得業(yè)務(wù)數(shù)據(jù),最后組裝Json結(jié)果返回給客戶端
中文分詞
Sphinx自帶LibMMSeg庫實現(xiàn)中文分詞,創(chuàng)建索引的過程中會將field字段內(nèi)容進(jìn)行分詞。
但是在實際應(yīng)用中會發(fā)現(xiàn),一般情況下的分詞無法對單字進(jìn)行檢索,例如一個短語:
學(xué)習(xí)漢語拼音
默認(rèn)的分詞結(jié)果是
學(xué)習(xí)_漢語拼音
此時用單字“學(xué)”,“拼音”等就無法從索引中檢索出數(shù)據(jù)。
雖然我并不認(rèn)為單字搜索是一個合理的全文檢索需求,但有時項目需要,也不得不去尋求解決的方案。
同義詞庫
在分詞組件中啟用同義詞功能,并完善同義詞庫,例如建立一個同義詞條目:
漢語拼音->拼音
這樣檢索“拼音”就能索引到“漢語拼音”的數(shù)據(jù)。
參考資料:
但此方案也無法解決單字檢索的問題。
一元分詞
一元分詞的方案是,不使用詞庫,把每個單字做為一個分詞建立索引數(shù)據(jù),檢索時也對每個字進(jìn)行拆分。
這種方法索引數(shù)據(jù)會變得巨大,查詢開銷也會變大,而且享受不了詞庫所帶來的精準(zhǔn)性。
coreseek核心配置:
#charset_dictpath=/usr/local/mmseg3/etc/ 此行需要注釋掉,從而關(guān)閉可以提升性能和精確度的中文分詞功能!
charset_type=utf-8 #表示啟用使用utf-8字符集,來處理中文字符。
ngram_len=1 #表示使用一元字符切分模式,從而得以對單個中文字符進(jìn)行索引;
ngram_chars=U+4E00..U+9FBF, ...... #表示要進(jìn)行一元字符切分模式的字符集;
charset_table=U+FF10..U+FF19->0..9, 0..9,...... #表示可被一元字符切分模式認(rèn)可的有效字符集;
參考資料:
中綴索引
中綴索引是除了對關(guān)鍵字本身還會對所有可能的中綴(即子字符串)做索引。
舉個例子:
漢語拼音
這個詞將會被切分為
漢,漢語,漢語拼,語,語拼,語拼音,拼音,音
這些子字符串,并創(chuàng)建索引。
這種方法索引數(shù)據(jù)將會更為龐大,但相對一元分詞精準(zhǔn)性相對較高,所以我們的項目最終采用這個方案。
coreseek核心配置:
enable_star=0 #不使用通配符,默認(rèn)不啟用,可以不寫
min_infix_len=1 #使用中綴索引,并且最小索引為1,關(guān)于該項作用不知者可以查詢手冊
infix_fields=字段1,字段2 #因為中綴索引會使索引量急劇膨脹,所以最好選擇你認(rèn)為最主要的少量幾個字段做中綴索引。
參考資料: