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 创建索引命令,再次用 â 去搜索就有搜索结果了。