Laravel 中使用 sphinx 搜索引擎

在开发项目的时候有个文本搜索的模块,最初数据量在十万以内用 sql 的 like 语句匹配查询时间在零点几秒感觉也能勉强满足,后来随着数据量的递增主表数据达到一百多万左右并且其他连表数据也同时增加后,再使用sql查询需要二三十秒左右,完全无法满足项目要求,便有了sphinx搜索引擎的使用。

1. sphinx简介

1.1 sphinx是什么

Sphinx是由俄罗斯人Andrew Aksyonoff开发的一个全文检索引擎。意图为其他应用提供高速、低空间占用、高结果 相关度的全文搜索功能。Sphinx可以非常容易的与SQL数据库和脚本语言集成。当前系统内置MySQL和PostgreSQL 数据库数据源的支持,也支持从标准输入读取特定格式 的XML数据。通过修改源代码,用户可以自行增加新的数据源(例如:其他类型的DBMS 的原生支持)

1.2 sphinx的特性

高速的建立索引(在当代CPU上,峰值性能可达到10 MB/秒);
高性能的搜索(在2 – 4GB 的文本数据上,平均每次检索响应时间小于0.1秒);
可处理海量数据(目前已知可以处理超过100 GB的文本数据, 在单一CPU的系统上可 处理100 M 文档);
提供了优秀的相关度算法,基于短语相似度和统计(BM25)的复合Ranking方法;
支持分布式搜索;
支持短语搜索
提供文档摘要生成
可作为MySQL的存储引擎提供搜索服务;
支持布尔、短语、词语相似度等多种检索模式;
文档支持多个全文检索字段(最大不超过32个);
文档支持多个额外的属性信息(例如:分组信息,时间戳等);
支持断词;

2. sphinx安装

安装方式可以使用源码安装,也可以直接下载编译好了的二进制文件。

2.1 下载

这里选择后者直接在 sphinx 官网上下载编译好的二进制文件选择 Linux x64 binaries glibc 2.12 (Centos 6 etc)。

2.1 拷贝解压

下载完成把它拷贝到 服务器 /usr/local/sphinx 目录下解压出来
里面包含 api bin doc etc misc src var 这几个目录,后面主要用到api 头文件和测试文件, bin 可执行程序目录,etc 配置文件目录,var 生成的数据及日志目录。

3. sphinx配置

3.1 拷贝一个例子配置文件并重命名
cd  /usr/local/sphinx/etc
cp  sphinx-min.conf.dist  sphinx.conf 
3.2 在/usr/local/sphinx/var 下面建两个目录 data, log

data 存放搜索引擎从数据库加载的数据
log 存放日志文件

3.3 修改配置文件
source src1
{
    type            = mysql   #数据库类型
    sql_host        = 192.168.1.11 #数据库所在服务器ip地址
    sql_user        = root #数据库用户名
    sql_pass        = pwd #数据库密码
    sql_db          = redwine #数据库名
    sql_port        = 3306  # mysql 默认端口3306
    sql_query_pre   = set names utf8   #数据库编码
    #获取数据的sql语句
    sql_query       = SELECT \
                        v.VintageId, \
                        v.`Name` as VintageName, \
                        IFNULL(v.`Year`, '') as Year, \
                        w.WineId, \
                        w.`Name` as WineName, \
                        w.OriginCountry, \
                        w.Type, \
                        s.Id as StyleId, \
                        s.`Name` as StyleName, \
                        r.Id as RegionId, \
                        r.`Name` as RegionName, \
                        e.Id as WineryId, \
                        e.`Name` as WineryName \
                    FROM \
                        Vintage v \
                        LEFT JOIN Wine w ON v.WineId = w.WineId \
                        LEFT JOIN Style s ON w.StyleId = s.Id \
                        LEFT JOIN Region r ON w.RegionId = r.Id \
                        LEFT JOIN Winery e ON w.WineryId = e.Id \
                    WHERE \
                        v.`Year` IS NULL \

    #sql_attr_uint      = VintageId  #作为key的字段不需要写出否则报错
    sql_attr_string     = Year
    sql_attr_uint       = WineId
    sql_attr_string     = WineName
    sql_attr_string     = OriginCountry
    sql_attr_uint       = Type
    sql_attr_uint       = StyleId
    sql_attr_uint       = RegionId
    sql_attr_uint       = WineryId
    # 下面的字段是搜索时需要去匹配的字段
    sql_field_string    = VintageName
    sql_field_string    = StyleName
    sql_field_string    = RegionName
    sql_field_string    = WineryName
}

#索引(根据需求索引可以建多个)
index redwine
{
    source          = src1 #声明索引源
    path            = /usr/local/sphinx/var/data/redwine  #索引文件存放路径及索引的文件名
    mlock           = 0 # searchd会将spa和spi预读取到内存中。但是如果这部分内存数据长时间没有访问,则它会被交换到磁盘上。
                        #设置了mlock就不会出现这个问题,这部分数据会一直存放在内存中的。
    min_word_len    = 1 # 索引的词最小长度
    min_prefix_len  = 1 #最小前缀 
    min_infix_len   = 1 #最小中缀
    expand_keywords = 1 # 是否尽可能展开关键字的精确格式或者型号形式
    ngram_len       = 1 # 对于非字母型数据的长度切割
    ngram_chars     = U+3000..U+2FA1F  #  对于非字母型数据的长度切割,N-Gram是指不按照词典,而是按照字长来分词
    charset_table   = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F, U+226..U+252  # 字符表和大小写转换规则
}

# 索引器配置
indexer
{
    mem_limit       = 128M # 内存限制
}

# sphinx 服务进程
searchd
{
    listen          = 9312 # 监听端口,官方在IANA获得正式授权的9312端口
    listen          = 9306:mysql41
    log             = /usr/local/sphinx/var/log/searchd.log # 服务进程日志
    query_log       = /usr/local/sphinx/var/log/query.log # 客户端查询日志
    read_timeout    = 5 # 请求超时
    max_children    = 30 # 同时可执行的最大searchd 进程数
    pid_file        = /usr/local/sphinx/var/log/searchd.pid #进程ID文件
    seamless_rotate = 1 # 是否支持无缝切换,做增量索引时通常需要
    preopen_indexes = 1 # 索引预开启,是否强制重新打开所有索引文件
    unlink_old      = 1 # 索引轮换成功之后,是否删除以.old为扩展名的索引拷贝
    workers         = threads  # for RT to work 多处理模式
    binlog_path     = /usr/local/sphinx/var/data #二进制日志路径
}
3.4 建立索引
cd /usr/local/sphinx/bin
./indexer -c ../etc/sphinx.conf redwine   

redwine为刚才在配置文件中建立的索引名称

执行时若出现下面错误
sql_connect: failed to load libmysqlclient (or libmariadb)(DSN=mysql://root:***@192.168.1.11:3306/redwine).
原因:libmysqlclient 没有安装
安装 yum install mysql-community-client
安装 yum install mysql-devel

启动后台守护进程

./searchd -c ../etc/sphinx.conf

停止正在运行的searchd

./searchd -c ../etc/sphinx.conf  --stop

查看端口情况

ps aux|grep searchd
3.5 测试

启动搜索引擎后,进入到 api 目录下cd /usr/local/sphinx/api,比如想要搜索 ‘assa’

php test.php assa

输出结果:

Query 'assa ' retrieved 3 of 3 matches in 0.000 sec.
Query stats:
    'assa' found 4 times in 3 documents

Matches:
1. doc_id=5008883, weight=2787, vintagename=Vina Assa Reserva Rioja, year=, wineid=2132382, winename=Reserva Rioja, origincountry=es, type=1, styleid=0, stylename=, regionid=492, regionname=Rioja, wineryid=129667, wineryname=Vina Assa
2. doc_id=15581072, weight=1709, vintagename=Arba Wines Rosa Assa Valley, year=, wineid=3094479, winename=Rosa Assa Valley, origincountry=, type=3, styleid=0, stylename=, regionid=0, regionname=, wineryid=114888, wineryname=Arba Wines
3. doc_id=159024419, weight=1709, vintagename=Arba Wines Rosa Assa Valley, year=, wineid=6888148, winename=Rosa Assa Valley, origincountry=, type=3, styleid=0, stylename=, regionid=0, regionname=, wineryid=114888, wineryname=Arba Wines

说明 sphinx 安装和配置ok了
现在在php 代码中 require ( "sphinxapi.php" ); 就可以直接使用sphinx搜索引擎了,直接使用它的api还是比较麻烦的,php 的laravel框架中有相应的插件可以用。

3.6 定期自动更新索引

如果数据库数据不会变动,只需生成一次索引就可以了
如果数据库数据在不断的变化,则需要定期更新索引了,可以写一个shell 脚本,然后放在corntab里面自动执行。
在/usr/local/sphinx/bin下面建一个 build_index.sh 脚本,内容如下

time1=$(date '+%Y%m%d %H:%M:%S')
echo $time1 >> /usr/local/sphinx/var/log/build_index.log

/usr/local/sphinx/bin/indexer -c /usr/local/sphinx/etc/sphinx.conf redwine --rotate >> /usr/local/sphinx/var/log/build_index.log

time2=$(date '+%Y%m%d %H:%M:%S')
echo $time2 >> /usr/local/sphinx/var/log/build_index.log

在 crontab 里面增加一条,每天0点执行一次

1 0 * * * /usr/local/sphinx/bin/build_index.sh

4. laravel 框架中使用 sphinx

sphinxsearch for Laravel 5 的组件

4.1 sphinxsearch 安装

在项目的 composer.json 文件中增加配置项

"require": {
    /*** Some others packages ***/
    "sngrl/sphinxsearch": "dev-master",
},

在 git-bash 工具中进入项目目录,执行

composer require sngrl/sphinxsearch:dev-master

运行完 composer 命令后,在 config/app.php 文件中 ‘providers’ 项里增加

'providers' => array(
        /*** Some others providers ***/
        sngrl\SphinxSearch\SphinxSearchServiceProvider::class,
),
4.2 生成组件所需配置文件
php artisan vendor:publish --provider=sngrl\SphinxSearch\SphinxSearchServiceProvider --force

执行完该命令后 会生成 config/sphinxsearch.php 文件,内容大致如下

<?php
return array(
    'host'    => "127.0.0.1",
    'port'    => 9312,
    'timeout' => 30,
    'indexes' => array(
        'redwine' => false,//array('table' => 'keywords', 'column' => 'id'),
    ),
    'mysql_server' => array(
        'host' => "127.0.0.1",
        'port' => 9306
    )
);

sphinxsearch 组件会被安装到 vendor/sigrl/sphinxsearch 下面

4.3 在laravel中使用sphinxsearch
use sngrl\SphinxSearch\SphinxSearch;

try {
      $sphinx = new SphinxSearch();
      $ret = $sphinx->search($text, 'redwine')->limit($count, ($page-1) * $count)->get();
} catch(\Exception $e){
      \writeLog("error","error","textSearch errormsg:".$e->getMessage());
}

更多用法参考其api 的具体使用

5. 欧洲语言中一些特殊字符的搜索问题

数据库中有些字段里面包含一些法语或者其他欧洲语言,里面有些非英文单词的26个字符,如 Château 里面的 â,用这个单词去查询能查到结果集出来,但是自己使用â单个字符搜索却没有结果,调试发现使用 Château 搜索是的关键字变成了 Ch 和 teau 两个关键字了,搜索引擎直接把 â 给无视了。怪不得单独用 â 没有查到任何数据
原因:搜索引擎无法识别这些特殊字符
解决方法:
1.找出这些特殊字符,以及特殊字符对应的unicode编码
2.修改sphinx搜索引擎配置文件的 charset_table 配置,把这些要用到的特殊字符对应的unicode码加入添加上
一些欧洲国家的语言特殊字符:

法语
À Â Ç È É Ê Ë Î Ï Œ Ô Ù Û
à â ç é è ê ë î ï œ ô ù û
西班牙语 
Á É Í Ñ Ó Ú Ü 
á é í ñ ó ú 
意大利语
À È Ì Ò Ù 
à è ì ò ù  
葡萄牙语
Ã Ç Ò Ó Õ 
ã ç ò ó õ
匈牙利语
Á É Í Ó Ö Ő Ú Ü Ű
á é í ó ö ő ú ü ű
德语与斯堪的纳维亚诸语言
Ä Å Æ Ð Ë Ö Ø Þ Ü Ÿ
ä å æ ð ë ö ø þ ü ÿ

查出这些特殊字符对应的unicode编码,Unicode Character Search
法语
À U+00C0
 U+00C2
Ç U+00C7  
È U+00C8  
É U+00C9  
Ê U+00CA  
Ë U+00CB  
Î U+00CE  
Ï U+00CF  
Œ U+0152
Ô U+00D4 
Ù U+00D9 
Û U+00DB

à U+00E0  
â U+00E2  
ç U+00E7 
é U+00E9 
è U+00E8  
ê U+00EA 
ë U+00EB  
î U+00EE  
ï U+00EF  
œ U+0153 
ô U+00F4 
ù U+00F9 
û U+00FB

西班牙语
Á U+00C1
É U+00C9
Í U+00CD
Ñ U+00D1
Ó U+00D3
Ú U+00DA
Ü U+00DC

á U+00E1
é U+00E9
í U+00ED
ñ U+00F1
ó U+00F3
ú U+00FA
ü U+00FC

意大利语
À U+00C0
È U+00C8  
Ì U+00CC  
Ò U+00D2 
Ù U+00D9

à U+00E0  
è U+00E8 
ì U+00EC 
ò U+00F2 
ù U+00F9

葡萄牙语
à U+00C3
Ç U+00C7
Ò U+00D2
Ó U+00D3
Õ U+00D5

ã U+00E3
ç U+00E7
ò U+00F2
ó U+00F3
õ U+00F5

匈牙利语
Á U+00C1
É U+00C9
Í U+00CD
Ó U+00D3
Ö U+00D6
Ő U+0150
Ú U+00DA
Ü U+00DC
Ű U+0170

á U+00E1
é U+00E9
í U+00ED
ó U+00F3
ö U+00F6
ő U+0151
ú U+00FA
ü U+00FC
ű U+0171

德语与斯堪的纳维亚诸语言
Ä U+00C4
Å U+00C5
Æ U+00C6
Ð U+00D0
Ë U+00CB
Ö U+00D6
Ø U+00D8
Þ U+00DE
Ü U+00DC
Ÿ U+0178

ä U+00E4
å U+00E5
æ U+00E6
ð U+00F0
ë U+00EB
ö U+00F6
ø U+00F8
þ U+00FE
ü U+00FC
ÿ U+00FF

修改 charset_table 配置, 参考官网 charset_tables 说明

U+00C0->U+00E0, U+00C2->U+00E2, U+00C7->U+00E7, U+00C8->U+00E8, U+00C9->U+00E9, U+00CA->U+00EA, U+00CB->U+00EB, \
U+00CE->U+00EE, U+00CF->U+00EF, U+0152->U+0153, U+00D4->U+00F4, U+00D9->U+00F9, U+00DB->U+00FB, \
U+00E0, U+00E2, U+00E7, U+00E9, U+00E8, U+00EA, U+00EB, U+00EE, U+00EF, U+0153, U+00F4, U+00F9, U+00FB, \
U+00C1->U+00E1, U+00C9->U+00E9, U+00CD->U+00ED, U+00D1->U+00F1, U+00D3->U+00F3, U+00DA->U+00FA, U+00DC->U+00FC, \
U+00E1, U+00E9, U+00ED, U+00F1, U+00F3, U+00FA, U+00FC, \
U+00C0->U+00E0, U+00C8->U+00E8, U+00CC->U+00EC, U+00D2->U+00F2, U+00D9->U+00F9, \
U+00E0, U+00E8, U+00EC, U+00F2, U+00F9, \
U+00C3->U+00E3, U+00C7->U+00E7, U+00D2->U+00F2, U+00D3->U+00F3, U+00D5->U+00F5, \
U+00E3, U+00E7, U+00F2, U+00F3, U+00F5, \
U+00C1->U+00E1, U+00C9->U+00E9, U+00CD->U+00ED, U+00D3->U+00F3, U+00D6->U+00F6, \
U+0150->U+0151, U+00DA->U+00FA, U+00DC->U+00FC, U+0170->U+0171, \
U+00E1, U+00E9, U+00ED, U+00F3, U+00F6, U+0151, U+00FA, U+00FC, U+0171, \
U+00C4->U+00E4, U+00C5->U+00E5, U+00C6->U+00E6, U+00D0->U+00F0, U+00CB->U+00EB, \
U+00D6->U+00F6, U+00D8->U+00F8, U+00DE->U+00FE, U+00DC->U+00FC, U+0178->U+00FF, \
U+00E4, U+00E5, U+00E6, U+00F0, U+00EB, U+00F6, U+00F8, U+00FE, U+00FC, U+00FC, \

把这些内容添加到 sphinx.conf 配置文件的 charset_table配置项里面,重新执行下 /usr/local/sphinx/bin/build_index.sh 创建索引命令,再次用 â 去搜索就有搜索结果了。

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