coreseek使用记录

项目需要用到全文检索。
之前接触的都是java中lucene + 分词库的解决方案。现在公司使用的都是coreseek,且项目使用mongodb作为数据库,团长让我研究一下解决的方案,所以记录一下学习与使用过程。

Coreseek

Coreseek是一个开源的中文全文检索引擎,基于Sphinx研发并独立发布。在Sphinx在基础上增加了LibMMSeg中文分词包,实现了对中文的分词与检索。

Sphinx是由俄罗斯人开发的高性能全文检索引擎。
全文检索引擎本身的原理是类似的,网上有很多文章介绍的挺好,这里直接引用一下:

Sphinx的一大特点是与mysql数据库结合良好,只需在配置文件中写好SQL查询语句就能定义索引数据库源。
但此次项目使用mongodbSphinx不能直接支持。

配置索引数据源

按照官方手册配置索引数据源,例如一个mysql数据源的示例配置:

#源定义
source mysql
{
    type                = mysql    #表示使用mysql数据源

    sql_host                = localhost    #表示数据库服务器的链接地址
    sql_user                = root          #表示数据库的用户名
    sql_pass                = 123456      #表示数据库的密码
    sql_db              = test          #表示数据库的名称
    sql_port                = 3306         #表示数据库的端口
    sql_query_pre           = SET NAMES utf8

    #从数据库之中读取数据的SQL语句设置
    sql_query               = SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content FROM documents
                                        #sql_query第一列id需为整数,且被系统使用,无需再设置sql_attr_uint

    #使用sql_attr设置的字段,只能作为属性,使用SphinxClient::SetFilter()进行过滤;未被设置的字段,自动作为全文检索的字段,使用SphinxClient::Query("搜索字符串")进行全文搜索;
    #title、content作为字符串/文本字段,被全文索引

    sql_attr_uint           = group_id                 #从SQL读取到的值必须为整数;sql_attr_uint表示该字段是数值属性
    sql_attr_timestamp      = date_added      #从SQL读取到的值必须为整数,作为时间属性;sql_attr_timestamp表示该字段是时间属性;可以不用该配置

    sql_query_info_pre      = SET NAMES utf8                                        #命令行查询时,设置正确的字符集,3.2.14开始支持
    sql_query_info          = SELECT * FROM documents WHERE id=$id  #命令行查询时,从数据库读取原始数据信息
}

#index定义
index mysql
{
    source            = mysql             #对应的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环境下设置,/符号结尾
    charset_dictpath = etc/ #Windows环境下设置,/符号结尾
    charset_type        = zh_cn.utf-8

}

建立索引数据

执行indexer建立索引数据,命令格式为:

indexer -c 配置文件的路径 index名称

例如:

/usr/local/coreseek/bin/indexer -c etc/csft.conf questions

启动检索服务

检索服务需要配置守护进程的相关选项,示例为:

#定义searchd守护进程的相关选项
searchd
{
           #定义监听的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
           #定义网络客户端请求的读超时时间
   read_timeout       = 5
           #定义子进程的最大数量
   max_children       = 300
           #设置searchd进程pid文件名
   pid_file           =/usr/local/coreseek/var/log/searchd.pid
           #定义守护进程在内存中为每个索引所保持并返回给客户端的匹配数目的最大值
   max_matches        = 100000
           #启用无缝seamless轮转,防止searchd轮转在需要预取大量数据的索引时停止响应
    #也就是说在任何时刻查询都可用,或者使用旧索引,或者使用新索引
   seamless_rotate    = 1
           #配置在启动时强制重新打开所有索引文件
   preopen_indexes    = 1
           #设置索引轮转成功以后删除以.old为扩展名的索引拷贝
   unlink_old         = 1
           # MVA更新池大小,这个参数不太明白
   mva_updates_pool   = 1M
           #最大允许的包大小
   max_packet_size    = 32M
           #最大允许的过滤器数
   max_filters        = 256
           #每个过滤器最大允许的值的个数
   max_filter_values  = 4096
}

配置完毕后,执行searchd启动或关闭检索服务,命令格式为:

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的调用

官方的安装内提供了Php, Python, Java, C, Ruby等语言的api,这里列出一个项目中使用的Java版api调用示例:

SphinxClient cl = new SphinxClient();
cl.SetServer("192.168.19.135", 9314);  //sphinx服务器与端口
cl.SetMatchMode(SphinxClient.SPH_MATCH_ANY);    //匹配任意
cl.SetSortMode(SphinxClient.SPH_SORT_RELEVANCE, "");  //按匹配程序排序
cl.SetLimit(0, 20);                     //分页参数
int[] users = {0, 839017};
cl.SetFilter("check_user", users, false);  //根据条件过滤

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返回分词结果
    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返回搜索结果
    System.out.println((i + 1) + ". id=" + info.docId + ", weight=" + info.weight);
}

详细的api参考

在Coreseek/Sphinx中支持Mongodb数据源

Sphinx中支持Mongodb主要有两种方式,xmlpipepython数据源。

xmlpipe数据源

编写一个自己的服务,将Mongodb数据转换成Sphinx能够识别的xml文本数据源,再调用api生成索引数据。
xml数据示例:

<?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>愚人节最佳蛊惑爆料 谷歌300亿美元收购百度</subject>
        <published>1270131607</published>
        <content>据国外媒体报道,谷歌将巨资收购百度,涉及金额高达300亿美元。谷歌借此重返大陆市场。......
        </content>
        <author_id>1</author_id>
    </sphinx:document>
    <sphinx:document id="2">
        <subject>Twitter主页改版 推普通用户消息增加趋势话题</subject>
        <published>1270135548</published>
        <content>4月1日消息,据国外媒体报道,Twitter本周二推出新版主页,目的很简单:帮助新用户了解Twitter和增加用户黏稠度。......
        </content>
        <author_id>1</author_id>
    </sphinx:document>
    <sphinx:document id="3">
        <subject>死都要上!Opera Mini 体验版抢先试用</subject>
        <published>1270094460</published>
        <content>Opera一直都被认为是浏览速度飞快,同时在移动平台上更是占有不少的份额。......
        </content>
        <author_id>2</author_id>
    </sphinx:document>
</sphinx:docset>

Sphinx的配置:

#源定义
source xml
{
    type            = xmlpipe2
    xmlpipe_command = bin\cat var/test/test.xml     #此处也可使用其他可执行程序输出xml数据
}

参考:

python数据源

Python数据源也称为万能数据源,借助Python强大的灵活性与活跃度,Sphinx做了一层桥接,此方式几乎可以读取所有形式的数据。
官网的代码例子:

# -*- coding:utf-8 -*-
# coreseek3.2 python source演示操作mssql数据库
# 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):  #获取结构,docid、文本、整数
        return [
            ('threadid' , {'docid':True, } ),
            ('title', { 'type':'text'} ),
            ('context', { 'type':'text'} ),
            ('date', {'type':'integer'} ),
        ]

    def GetFieldOrder(self): #字段的优先顺序
        return [('title', 'context')]

    def Connected(self):   #如果是数据库,则在此处做数据库连接
        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):   #取得每一个文档记录的调用
        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将目标数据按规定的模型读取至python虚拟机中,Sphinx再从虚拟机里读出索引源数据。
相关配置文件:

#python路径定义
python
{
    path = /usr/local/coreseek/etc/pysource         #BSD、Linux环境下设置
    path = /usr/local/coreseek/etc/pysource/csft_demo   #BSD、Linux环境下设置
    #path = etc/pysource            #Windows环境下设置,最好给出绝对路径
    #path = etc/pysource/csft_demo  #Windows环境下设置,最好给出绝对路径
}

#源定义
source python
{
    type = python
    name = csft_demo.MainSource #对应etc/pysource/csft_demo/__init__.py中的MainSource
}

参考:

项目服务架构

项目使用xmlpipe的方案支持mongodb数据,实现后的架构如图:

coreseek1

  • 索引数据源服务定时生成xml,调用coreseek命令生成索引
  • 客户端请求Restful接口,服务端组装搜索条件,调用coreseekapi返回数据主键,
  • mongodb中获得业务数据,最后组装Json结果返回给客户端

中文分词

Sphinx自带LibMMSeg库实现中文分词,创建索引的过程中会将field字段内容进行分词。
但是在实际应用中会发现,一般情况下的分词无法对单字进行检索,例如一个短语:

学习汉语拼音

默认的分词结果是

学习_汉语拼音

此时用单字“学”,“拼音”等就无法从索引中检索出数据。
虽然我并不认为单字搜索是一个合理的全文检索需求,但有时项目需要,也不得不去寻求解决的方案。

同义词库

在分词组件中启用同义词功能,并完善同义词库,例如建立一个同义词条目:

汉语拼音->拼音

这样检索“拼音”就能索引到“汉语拼音”的数据。
参考资料:

但此方案也无法解决单字检索的问题。

一元分词

一元分词的方案是,不使用词库,把每个单字做为一个分词建立索引数据,检索时也对每个字进行拆分。
这种方法索引数据会变得巨大,查询开销也会变大,而且享受不了词库所带来的精准性。
coreseek核心配置:

#charset_dictpath=/usr/local/mmseg3/etc/ 此行需要注释掉,从而关闭可以提升性能和精确度的中文分词功能!
charset_type=utf-8 #表示启用使用utf-8字符集,来处理中文字符。
ngram_len=1 #表示使用一元字符切分模式,从而得以对单个中文字符进行索引;
ngram_chars=U+4E00..U+9FBF, ......  #表示要进行一元字符切分模式的字符集;
charset_table=U+FF10..U+FF19->0..9, 0..9,...... #表示可被一元字符切分模式认可的有效字符集;

参考资料:

中缀索引

中缀索引是除了对关键字本身还会对所有可能的中缀(即子字符串)做索引。
举个例子:

汉语拼音

这个词将会被切分为

汉,汉语,汉语拼,语,语拼,语拼音,拼音,音

这些子字符串,并创建索引。
这种方法索引数据将会更为庞大,但相对一元分词精准性相对较高,所以我们的项目最终采用这个方案。
coreseek核心配置:

enable_star=0 #不使用通配符,默认不启用,可以不写
min_infix_len=1 #使用中缀索引,并且最小索引为1,关于该项作用不知者可以查询手册
infix_fields=字段1,字段2 #因为中缀索引会使索引量急剧膨胀,所以最好选择你认为最主要的少量几个字段做中缀索引。

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容