一、Introduction
sphinx是一个独立的搜索引擎,为其他应用提供高速、低空间占用、高结果相关度的全文搜索功能。sphinx可以非常容易的与sql数据库和脚本语言集成。
搜索api支持php、python、perl、ruby和java,并且也可以用作mysql存储引擎。
coreseek全文检索服务器2.0是在sphinx基础上开发的全文检索软件,按照GPLv2协议发行。中文版的sphinx。
sphinx的特性,高速建立索引,高性能的搜索,处理海量数据,优秀的相关度算法,支持分布式搜索,可作为mysql存储引擎提供搜索服务,支持布尔、短语、词语相似度等多种检索模式等等。
支持多数操作系统。
二、windows下使用sphinx
一般的搜索中,使用关键字搜索时一般是使用like ‘%xxx%’,这样查询在数据量大时非常慢,like是全表扫描。
到coreseek官网下载软件包解压。
1. 使用sphinx为要搜索的数据创建全文索引
制作配置文件,在解压包的etc/下,复制一份csft_mysql.conf,改名为sphinx.conf,放到解压包的根目录/下。修改配置文件。
#MySQL数据源配置,详情请查看:http://www.coreseek.cn/products-install/mysql/
#源定义,为eshopphp数据库中的es_goods表为数据源
source goods
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#主查询,要为哪些数据建索引就使用一条sql语句把这些数据取出来
#第一个字段一定要求是id
#为所有的商品的goods_name,goods_desc,attr_value这三个字段创建全文索引
sql_query = SELECTa.id,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value) attr_value FROM es_goods a left join es_goods_attr b ona.id=b.goods_id group by a.id
}
#index定义,一个数据源对应一个index
#定义索引文件
index goods
{
source = goods #对应的source名称
#生成的索引文件存放的目录\索引文件的名字,不要中文路径,下面最后的goods为索引文件的名字
path = D:\002php\shpinx\var\data\goods
docinfo = extern
mlock = 0
morphology = none
min_word_len = 1
html_strip = 0
#中文分词配置,中文分词词库文件所在目录,详情请查看:http://www.coreseek.cn/products-install/coreseek_mmseg/
#charset_dictpath = /usr/local/mmseg3/etc/#BSD、Linux环境下设置,/符号结尾
#Windows环境下设置,/符号结尾,最好给出绝对路径,例如:C:/usr/local/coreseek/etc/...
charset_dictpath = D:\002php\shpinx\etc
#shpinx只支持utf-8
charset_type = zh_cn.utf-8
}
#全局index定义
indexer
{
mem_limit = 128M
}
#searchd,sphinx服务器配置
searchd
{
listen = 9312
read_timeout = 5
max_children = 30
max_matches = 1000
seamless_rotate = 0
preopen_indexes = 0
unlink_old = 1
#配置以下三个文件存放的绝对路径,目录中不要出现中文
pid_file =D:\002php\shpinx\var\log\searchd_mysql.pid
log =D:\002php\shpinx\var\log\searchd_mysql.log
query_log = D:\002php\shpinx\var\log\query_mysql.log
}
使用sphinx为商品数据生成索引
使用bin/indexer.exe命令。
indexer.exe -c D:\002php\shpinx\sphinx.conf goods
2. 安装并启动sphinx服务器
使用bin/searchd.exe命令
searchd.exe -c D:\002php\shpinx\sphinx.conf --install
打开windows服务窗口找到sphinx并启动
执行services.msc打开服务窗口。
3. 在项目中使用php代码查询sphinx
先复制sphinx的api目录下的sphinxapi.php文件到项目
然后在关键字搜索的方法中调用sphinx的客户端类。
//接收要搜索的关键
$key = I('get.key');
//搜索sphinx
require ('./sphinxapi.php');
$sph = new \SphinxClient();
$sph->SetServer('localhost', 9312);
//第二个参数,sphinx中索引的名字默认是*,表示所有的索引
$ret =$sph->Query($key, 'goods');
sphinx的返回结果中会返回符合条件的id,不会返回关于这个商品的具体数据,然后通过数据库取出商品的详细信息。
三、sphinx的实时索引更新
如果整个数据量非常大,以至于难以经常性的重建索引,但是每次新增的记录却相对来说少。比如,一个论坛有100000000000个已经归档的帖子,但是每天只有1000个新帖子。这中情况下使用主索引+增量索引(main+delta)模式来实现近实时的索引更新。
这种方法的基本思路是设置两个数据源和两个索引,对很少更新或根本不更新的数据建立主索引,而对新增文档建立增量索引。在上述例子中,那个量大的已经归档的帖子们放在主索引中,而每天新增的1000个帖子放在新增索引中。增量索引更新的频率可以非常快,而文档可以在出现几分钟内就可以检索到。
比如商城中,后添加的商品在sphinx中无法搜索出来,必须要重新创建索引。如果数据量非常大,有10000000件商品,每天新添加100个商品,每天都要为所有的商品重新索引。
实现步骤
1. 创建一张表
用于保存已经创建好索引的商品的最大id。根据这个id找出哪些商品没有创建索引。每次创建好索引后把最大的id存进去,定期取出新的商品创建新增索引,创建完新的索引再把最大的id更新到这个表。
create tablees_sphinx_id(
id mediumint unsigned not null default'0' comment '已经创建好索引的最后一件商品的id'
)engine=innodbdefault charset=utf8 comment 'sphinx';
insert intoes_sphinx_id values(0);
2. 修改sphinx配置文件
创建好索引后把已经建好的最后的商品id更新到这个表。
#源定义,为eshopphp数据库中的es_goods表为数据源
source goods
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#主查询,要为哪些数据建索引就使用一条sql语句把这些数据取出来
#第一个字段一定要求是id
#为所有的商品的goods_name,goods_desc,attr_value这三个字段创建全文索引
sql_query = SELECT a.id,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value)attr_value FROM es_goods a left joines_goods_attr b on a.id=b.goods_id group by a.id
#在创建好索引后最后一个商品id更新到表中
sql_query_post = update es_sphinx_id set id= (selectmax(id) from es_goods where is_on_sale='是')
}
关闭sphinx,重新创建商品索引。再启动sphinx服务。
3. 修改配置文件,为后添加的没有创建索引的数据生成一个增量索引
在配置文件中为增量索引创建数据源和索引文件的配置
#后添加的还没有索引的数据的数据源
source goods_new
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#取出后添加的还没有建索引的商品
sql_query = SELECTa.id,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value) attr_value FROM es_goods a left join es_goods_attr b ona.id=b.goods_id where a.is_on_sale='是' and a.id > (select id from es_sphinx_id) group by a.id
#在创建好索引后最后一个商品id更新到表中
sql_query_post = update es_sphinx_id set id= (selectmax(id) from es_goods where is_on_sale='是')
}
#定义新创建的数据源的索引文件
index goods_new
{
source = goods_new #对应的source名称
#生成的索引文件存放的目录\索引文件的名字,不要中文路径,下面最后的goods为索引文件的名字
path =D:\002php\shpinx\var\data\goods_new
docinfo = extern
mlock = 0
morphology = none
min_word_len = 1
html_strip = 0
#中文分词配置,中文分词词库文件所在目录,详情请查看:http://www.coreseek.cn/products-install/coreseek_mmseg/
#charset_dictpath = /usr/local/mmseg3/etc/#BSD、Linux环境下设置,/符号结尾
#Windows环境下设置,/符号结尾,最好给出绝对路径,例如:C:/usr/local/coreseek/etc/...
charset_dictpath = D:\002php\shpinx\etc
#shpinx只支持utf-8
charset_type = zh_cn.utf-8
}
4. 编写脚本处理增量索引
先为增量索引生成索引文件;把新生成的增量的索引文件合并到主索引文件。
windows系统是bat脚本,*.bat。
linux系统的是shell脚本,只要有可执行的权限就可以执行。
比如在项目的根目录下创建updateNewSphinxGoods.bat
D:\002php\shpinx\bin\indexer.exe -c D:\002php\shpinx\sphinx.conf goods_new
D:\002php\shpinx\bin\indexer.exe -c D:\002php\shpinx\sphinx.conf --merge goods goods_new --rotate
--rotate是为了便于在sphinx服务器运行期间执行合并的命令,否则goods被锁无法执行。
双击脚本执行。为了便于关于观察执行过程和结果,也可以拖到cmd黑窗口执行。
添加了新的商品之后只要执行这个脚本就可以在sphinx中搜索到了。但是每次都手动执行这个脚本太麻烦了,可以配置这个脚本每隔一段时间自动执行一次。
5. 配置脚本自动执行
如果是windows系统,windows—>管理工具--> 任务计划程序-- >创建基本任务,创建完成后在右键这个任务,选择属性,点击触发器,新建,设置为一次,重复任务间隔为5分钟,持续时间为无限期。
如果是linux系统,配置crond进程。
四、修改数据库数据时更新sphinx
当修改了数据库中的数据时,sphinx中已经生成的索引并没有更新,所以使用修改前的关键字还是能搜索出可能不符合条件的商品,这就需要在修改商品时把sphinx中的索引也一起更新。
思路是,只能为修改了的商品重新创建索引并合并到主索引上,在合并之前先删除原索引。
1. 在创建索引时必须找出该修改了的数据。
修改商品表添加一个字段,is_updated,默认值是0,一旦被修改了就设置为1。然后定期让sphinx把is_updated=1的重新创建索引。
is_updated tinyint unsigned not null default '0' comment '是否被修改',
然后,修改商品模型,当修改商品时,将这个字段更新为1。
2. 修改sphinx的配置文件,为修改的商品重建索引
#后添加的还没有索引的数据或者修改过的商品的数据源
source goods_new
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#取出后添加的还没有建索引的商品
sql_query = SELECTa.id,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value) attr_value FROM es_goods a left join es_goods_attr b ona.id=b.goods_id where a.is_on_sale='是' and (a.id > (select id from es_sphinx_id) or a.is_updated=1)group by a.id
#在创建好索引后最后一个商品id更新到表中
sql_query_post = update es_sphinx_id set id= (selectmax(id) from es_goods where is_on_sale='是')
}
3. 修改搜索的php代码让sphinx先过滤掉被标记为修改的,只放行is_updated为0的
//过滤掉被标记为修改的
$sph->SetFilter('is_updated', array(0);
4. 然后在sphinx中定义is_updated为一个属性
首先要取出这个字段,然后才可定义为一个属性。同时将获取is_updated段时将这个字段固定为0。
主索引和增量索引都要添加这个属性,否则在merge主索引和增量索引时将失败。
#########################主索引的数据源与索引配置#######################
#源定义,为eshopphp数据库中的es_goods表为数据源
source goods
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#主查询,要为哪些数据建索引就使用一条sql语句把这些数据取出来
#第一个字段一定要求是id
#为所有的商品的goods_name,goods_desc,attr_value这三个字段创建全文索引
sql_query = SELECTa.id,0 is_updated,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value)attr_value FROM es_goods a left joines_goods_attr b on a.id=b.goods_id where a.is_on_sale='是' group by a.id
#在创建好索引后最后一个商品id更新到表中
sql_query_post = update es_sphinx_id set id= (selectmax(id) from es_goods where is_on_sale='是')
#把一个字段的值定义为一个属性,属性可以用来过滤、排序
sql_attr_uint = is_updated
}
#后添加的还没有索引的数据或者修改过的商品的数据源
source goods_new
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#取出后添加的还没有建索引的商品
sql_query = SELECTa.id,0 is_updated,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value)attr_value FROM es_goods a left joines_goods_attr b on a.id=b.goods_id where a.is_on_sale='是' and (a.id > (select id from es_sphinx_id) or a.is_updated=1)group by a.id
#在创建好索引后最后一个商品id更新到表中
sql_query_post = update es_sphinx_id set id= (selectmax(id) from es_goods where is_on_sale='是')
#把一个字段的值定义为一个属性,属性可以用来过滤、排序
sql_attr_uint = is_updated
}
由于修改了配置文件,所以要重建索引。
D:\002php\shpinx\bin>indexer.exe -c D:\002php\shpinx\sphinx.conf goods --rotate
5. 然后,在商品模型中,修改一件商品后要把sphinx中这件商品的is_updated属性设置成1。
//设置sphinx中的这条记录的is_updated属性为1
//搜索sphinx
require('./sphinxapi.php');
$sph = new\SphinxClient();
$sph->SetServer('localhost',9312);
//把id=$id这件商品的is_updated属性更新为1
$sph->UpdateAttributes('goods',array('is_updated'),array($id=>array(1)));
6. 删除旧的索引
旧的索引仍然在sphinx的索引文件中,应该定期的把is_updated=1的删掉。删除旧的索引时需要使用indexer.exe --merge dst src --merge-dst-range [attr] [min] [max] [attr] [min] [max]命令。这个命令的意思是合并dst索引和src索引,在合并前先过滤dst索引中属性值在min和max(包含max)之间的索引,这个过滤条件可以设置多个。由于必须需要两个索引合并才能实现对dst索引的过滤,也就是案例中对goods索引的过滤,以实现删除is_updated值为1的旧索引。所以,创建要删除的索引的数据源和索引配置 goods_del。为了保证合并时不报错,goods_new索引文件的字段要和goods的保持一致。创建goods_del的数据源和索引配置时需复制goods的。配置时将is_updated固定为1,以便合并后能够将要删除的索引在goods主索引中标记出,即其is_updated为1。由于- -merge-dst-range和- -merge的执行可能是异步的。为了保证过滤的成功,再合并goods_new索引时进行过滤。
#########################要删除的商品的数据源与索引配置#######################
#要删除的商品的数据源
source goods_del
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 123456
sql_db = eshopphp
sql_port = 3306
sql_query_pre = SET NAMES utf8
#取出后添加的还没有建索引的商品
sql_query = SELECT a.id,1 is_updated,a.goods_name,a.goods_desc,GROUP_CONCAT(b.attr_value)attr_value FROM es_goods a left joines_goods_attr b on a.id=b.goods_id where a.is_on_sale='是' and a.is_updated=1 group by a.id
#把一个字段的值定义为一个属性,属性可以用来过滤、排序
sql_attr_uint = is_updated
}
#定义新创建的数据源的索引文件
index goods_del
{
source = goods_del #对应的source名称
#生成的索引文件存放的目录\索引文件的名字,不要中文路径,下面最后的goods为索引文件的名字
path =D:\002php\shpinx\var\data\goods_del
docinfo = extern
mlock = 0
morphology = none
min_word_len = 1
html_strip = 0
#中文分词配置,中文分词词库文件所在目录,详情请查看:http:src="__PUBLIC__/Home/images/www.coreseek.cn/products-install/coreseek_mmseg/
#charset_dictpath = /usr/local/mmseg3/etc/#BSD、Linux环境下设置,/符号结尾
#Windows环境下设置,/符号结尾,最好给出绝对路径,例如:C:/usr/local/coreseek/etc/...
charset_dictpath = D:\002php\shpinx\etc
#shpinx只支持utf-8
charset_type = zh_cn.utf-8
}
7. 然后修改脚本文件,加入合并时的过滤条件
::--merge-dst-range表示在合并时过滤,但是合并时未必已经将is_updated为1的商品合并到goods索引主表中
::所以可能是先过滤了然后合并的,这导致没有将is_updated为1的商品索引从goods索引中过滤掉,为了妥善
::分开执行,先将要过滤的商品索引一起合并到goods主索引,然后在后面合并goods_new时做过滤,删除is_updated为1的索引
::合并时只保存is_updated=0其他的属性值的索引都过滤掉
D:\002php\shpinx\bin\indexer.exe -c D:\002php\shpinx\sphinx.conf goods_del --rotate
D:\002php\shpinx\bin\indexer.exe -c D:\002php\shpinx\sphinx.conf --merge goods goods_del --rotate
D:\002php\shpinx\bin\indexer.exe -c D:\002php\shpinx\sphinx.conf goods_new --rotate
D:\002php\shpinx\bin\indexer.exe -c D:\002php\shpinx\sphinx.conf --merge goods goods_new --merge-dst-range is_updated 00 --rotate
由于sphinx的脚本文件的执行是在内存中执行,又在磁盘中执行的,所以,这个执行的结果需要根据实际生产环境中服务器的运行情况,等待一定时间才能见效,并不会执行了代码就立即生效,因为后台可能在做这个脚本的执行中的数据上的传输与存储。