道者编程

sphinx+mysql+php构建千万级搜索引擎

一:介绍

Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL,PostgreSQL做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

Sphinx 单一索引最大可包含1亿条记录,在1千万条记录情况下的查询速度为0.x秒(毫秒级)。Sphinx创建索引的速度为:创建100万条记录的索引只需 3~4分钟,创建1000万条记录的索引可以在50分钟内完成,而只包含最新10万条记录的增量索引,重建一次只需几十秒。

二:安装sphinx

1:下载地址:http://sphinxsearch.com/ 我这里下载Sphinx 3.0.3 released

2:安装编译,2.X版本需要编译,3.0及以上版本解压直接用即可

$ wget http://sphinxsearch.com/files/sphinx-3.0.3-facc3fb-linux-amd64.tar.gz #下载
$ tar -xvf sphinx-3.0.3-facc3fb-linux-amd64.tar.gz #解压 
$ cp sphinx-3.0.3/etc/sphinx.conf.dist sphinx-3.0.3/etc/sphinx.conf #修改配置文件后缀为.conf

sphinx使用步骤:

1、mysql先建立数据
2、建立Sphinx配置文件
3、生成索引
4、启动Searchd服务进程,并开放端口9312
5、用PHP客户端程序去连接Sphinx服务

三:sphinx配置

1:配置:

sphinx最麻烦的就是文件配置,打开/usr/local/sphinx-3.0.3/etc/sphinx.conf配置文件,这个文件有一大堆。

整理一下,其实一共有7个模块:

#主数据源
source src1{
}

#增量数据源
source src1throttled : src1{
}

#主数据索引
index test1{
}

#增量数据索引
index test1stemmed : test1{
}

#分布式索引
index dist1{
}

#索引器
indexer{
}

#服务进程
searchd{
}

 现在用一个数据库做实验,数据库名称:abc,表:news,news表里有三个字段:id,title,content;表中添加一些数据。etc里面还有一个sphinx-min.conf.dist,最小配置,我们先按照这个来改。

#
# Minimal Sphinx configuration sample (clean, simple, functional)
#
#主数据源配置
source main #默认是src1,我们取一个名字:main
{
	type			= mysql


	sql_host		= localhost
	sql_user		= root
	sql_pass		= 123
	sql_db			= abc
	sql_port		= 3306	# optional, default is 3306


	sql_query		= \
				SELECT id,title,content \
				FROM news #获取数据的sql语句
	sql_field_string = title
	sql_field_string = content #设置这两个字段参与全文搜索
	#sql_query_pre = SET SESSION query_cache_type=OFF #关闭查询缓存,如果mysql开启了缓存,则这里开启这一行,否则注释掉,不然会报错
}


#主数据索引配置
index test1
{
	source	= main #索引源声明,我们主数据源配置为main
	path	= /var/data/test1 #索引表放在这里,其他地方也可以,名字也叫test1,data如果不存在就先建立
	ngram_len= 1  #简单分词,搜索中文必须设置1
	ngram_chars= U+3000..U+2FA1F   #需要分词的字符,搜索中文时必须
}


indexer
{
	mem_limit		= 128M
}


searchd
{
	listen			= 9312
	listen			= 9306:mysql41
	log			= /var/log/searchd.log
	query_log		= /var/log/query.log
	read_timeout		= 5
	max_children		= 30
	pid_file		= /var/log/searchd.pid
	seamless_rotate		= 1
	preopen_indexes		= 1
	unlink_old		= 1
	workers			= threads # for RT to work
	binlog_path		= /var/data
}
 

其他的配置先不动,实时索引,增量都先删掉,先看看效果!

2:创建索引:

/usr/local/sphinx-3.0.3/bin/indexer -c /usr/local/sphinx-3.0.3/etc/sphinx.conf --all

 报错,提示没有mysql客户端。

搜索一下:

有啊,

执行一下软连接吧:注意机器位数

ln -s /usr/lib64/mysql/libmysqlclient.so.20.3.4 /usr/lib/libmysqlclient.so #32位机器
ln -s /usr/lib64/mysql/libmysqlclient.so.20.3.4 /usr/lib64/libmysqlclient.so #64位机器

再次执行:

OK,生成了4条索引,这里数据库我加了4条数据。

生产索引之后启动sphinx,启动后PHP才能调用:

/usr/local/sphinx-3.0.3/bin/searchd -c /usr/local/sphinx-3.0.3/etc/sphinx.conf
 

启动成功画面

新版本的sphinx已经取消了seach命令,没法通过内置函数测试,只能通过API,下面安装支持PHP

四:安装libsphinxclient(PHP调用需要用到这个模块)

cd /usr/local/sphinx-3.0.3/api/libsphinxclient
./configure --prefix=/usr/local/sphinxclient
make
make install

五:安装PHP sphinx扩展

pecl上没有php7的扩展,下载地址:

http://git.php.net/?p=pecl/search_engine/sphinx.git;a=shortlog;h=refs/heads/php7

./configure --with-php-config=/usr/local/php/bin/php-config --with-sphinx
make
make install

我这里make不成功,一堆报错,PHP7.2,先不管了,先用sphinx api调用。PHP调用sphinx有两种方式:一种是通过pecl扩展,另外一种就是通过API。

六:通过API调用:

把sphinx目录下的api目录下的sphinxapi.php文件复制一份出来:

<?php
header('Content-Type:text/html;charset=utf-8'); 

require('sphinxapi.php');
$sphinx = new SphinxClient();    
$sphinx->SetServer('192.168.17.160',9312);         
$res = $sphinx->Query("lijie","*");    
print_r($res);
if ( !$res )
{
	die ( "ERROR: " . $sphinx->GetLastError() . ".\n" );
} 

新加一条记录:INSERT INTO `news` (`title`, `content`) VALUES('china', 'china chinese');

查找china,发现啥都没有?怎么回事?因为新添加了记录,所以要重新更新索引,有两种方式,一种是增量更新,一种是全部更新,虽然sphinx全部更新很快,但也还是推荐增量更新,没必要重复工作。

这里先试一下全部更新,运行:

/usr/local/sphinx-3.0.3/bin/indexer -c /usr/local/sphinx-3.0.3/etc/sphinx.conf --all --rotate
# --rotate:通俗的说就是:在sphinx运行的时候,需要更新索引就带上这个参数,干脆叫热更新吧,否则需要重启searchd
# --all :全部索引,也可以单独制定索引,比如我们这里是:test1 --rotate

 数据找到了,id为:5

七:增量索引

所谓增量索引,就是mysql数据有改动,那么sphinx也只改动mysql改动的部分,不需要全部更新,是不是很科学?试下怎么配置。

思路:mysql表中建立一个计数器表,更新主索引的时候,把数据源中最大的ID值写入计数器,做索引只更新小于等于该值的记录,增量索引更新大于该值的记录

步骤:

1:新建一个计数器表

CREATE TABLE sph_counter(counter_id INTEGER PRIMARY KEY NOT NULL,max_doc_id INTEGER NOT NULL);
2:运行sql语句,把当前sphinx用到的mysql表中最大的id值插入进去,这里是news表

replace into sph_counter select 1,max(id) from news
3:修改sphinx配置文件

#
# Minimal Sphinx configuration sample (clean, simple, functional)
#
#主数据源配置
source main
{
	type			= mysql


	sql_host		= localhost
	sql_user		= root
	sql_pass		= 123
	sql_db			= abc
	sql_port		= 3306	# optional, default is 3306


	sql_query_pre		= SET NAMES utf8


	#把news表中id的最大值插入到sph_counter表里,这就实现了计数器更新
	sql_query_pre	= replace into sph_counter select 1,max(id) from news


	#主数据源的数据取之于计数表中最大值以前的数据,因为最大值以后的数据在增量数据源中取
	sql_query		= \
					SELECT id,title,content \
					FROM news WHERE id<=(SELECT max_doc_id FROM sph_counter WHERE counter_id=1);


	sql_field_string = title
	sql_field_string = content #设置这两个字段参与全文搜索
	#sql_attr_uint		= group_id
	#sql_attr_timestamp	= date_added
}


#增量数据源
source delta : main #delta是增量数据源名字,main是主数据,delta继承main
{ 
	sql_query_pre		= SET NAMES utf8
	sql_query = SELECT id,title,content \
				FROM news WHERE id>(SELECT max_doc_id FROM sph_counter WHERE counter_id=1);
}


#主索引配置
index test1
{
	source			= main
	path			= /var/data/test1 #主数据索引位置
	ngram_len= 1  #简单分词,搜索中文必须设置1
	ngram_chars= U+3000..U+2FA1F   #需要分词的字符,搜索中文时必须
}


#增量索引
index delta : delta { #delta是增量索引名字 增量数据源
	source = delta #继承增量索引源
	path   = /var/data/delta #增量索引位置
	ngram_len= 1  #简单分词,搜索中文必须设置1
	ngram_chars= U+3000..U+2FA1F   #需要分词的字符,搜索中文时必须
}


indexer
{
	mem_limit		= 128M
}


searchd
{
	listen			= 9312
	listen			= 9306:mysql41
	log			= /var/log/searchd.log
	query_log		= /var/log/query.log
	read_timeout		= 5
	max_children		= 30
	pid_file		= /var/log/searchd.pid
	seamless_rotate		= 1
	preopen_indexes		= 1
	unlink_old		= 1
	workers			= threads # for RT to work
	binlog_path =   /var/data/#关闭binlog日志
}
 运行一下:

#单独更新delta,delta是我们的增量索引
/usr/local/sphinx-3.0.3/bin/indexer -c /usr/local/sphinx-3.0.3/etc/sphinx.conf delta --rotate

 更新成功

添加一条数据,更新一下增量数据源,PHP查看一下:

//单独查询delta索引,查询所有索引用 *
$res = $sphinx->Query("fefds","delta");    
print_r($res);
可以看到,数据成功检索


4:索引合并:

添加了增量索引,这里就出现了两个索引源,比如这里是test1和delta;有两种方式解决这个问题。

(1):索引合并

indexer --merge test1 delta --rotate #将delta合并到test1中
 (2):客户端调用解决

索引不合并,通过客户端调用解决。两个索引合并时,都要读入,然后还要写一次硬盘,IO操作量很大。而在php API调用时,Query($query,$index)中$index可以设置多个索引名,如Query($query,"test1;delta"),也就没有必要一定将两个索引合并。

5:合并过滤(参考以下图片,该图片来自于互联网)


6:定时执行:

我们弄一个定时脚本:主索引,一天更新一次即可,可以选择在深夜人少的时候干这个事情,增量索引5分钟更新一次。具体时间根据业务需求调整完善。

八:分布式索引

分布式是为了改善查询延迟问题和提高多服务器、多CPU 或多核环境下的吞吐率,对于大量数据(即十亿级的记录数和TB级的文本量)上的搜索应用来说是很关键的。


分布式思想:对数据进行水平分区(HP,Horizontally partition),然后并行处理,当searched收到一个对分布式索引的查询时,它做如下操作

1.连接到远程代理

2.执行查询

3.对本地索引进行查询

4.接收来自远程代理的搜索结果

5.将所有结果合并,删除重复项(这里就要用到第七节的4,5)

6.将合并后的结果返回给客户端

配置很简单:

index dist
{
    type=distributed #表示分布式索引
    local=chunk1 #本地索引chunk1
    agent=localhost:9312:chunk2    本地
    agent=192.168.100.2:9312:chunk3  远程
    agent=192.168.100.2:9312:chunk4  远程
}

九:中文分词

sphinx提供了简单的中文分词,但精确度不高,实际使用意义不大,本来Coreseek很不错,Coreseek是基于sphinx的,说白了其实就是支持中文的sphinx,不过Coreseek网站N久打不开了,换来换去经常不稳定,这里我们试试另外一个开源分词系统——scws

官方地址:http://www.xunsearch.com/scws/

大概看了下,还不错,居然支持PHP7,业界良心啊。

1:安装scws

wget http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2
tar -xvf scws-1.2.3.tar.bz2
cd scws-1.2.3/
./configure --prefix=/usr/local/scws
make
make install

2:安装scws php扩展

扩展在上一步解压的scws文件夹的phpext文件下

cd phpext/
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config --with-scws=/usr/local/scws/
make 
make install

把scws.so添加到php配置文件中去,php.ini设置:

[scws]  
extension = scws.so  
scws.default.charset = utf-8  
scws.default.fpath = /usr/local/scws/etc


3:安装scws词库

wget http://www.xunsearch.com/scws/down/scws-dict-chs-utf8.tar.bz2
tar -xvf scws-dict-chs-utf8.tar.bz2
#解压出来是:dict.utf8.xdb 文件
#把这个文件移到/usr/local/scws/etc中
cp dict.utf8.xdb /usr/local/scws/etc
4:测试

<?php
header('Content-Type:text/html;charset=utf-8'); 


$key = "你好";
//========================================scws分词
 
$so = scws_new();
$so->set_charset('utf-8');
//默认词库
$so->add_dict(ini_get('scws.default.fpath') . '/dict.utf8.xdb');
//自定义词库
// $so->add_dict('./dd.txt',SCWS_XDICT_TXT);
//默认规则
$so->set_rule(ini_get('scws.default.fpath') . '/rules.utf8.ini');
 
//设定分词返回结果时是否去除一些特殊的标点符号
$so->set_ignore(true);
 
//设定分词返回结果时是否复式分割,如“中国人”返回“中国+人+中国人”三个词。
// 按位异或的 1 | 2 | 4 | 8 分别表示: 短词 | 二元 | 主要单字 | 所有单字
//1,2,4,8 分别对应常量 SCWS_MULTI_SHORT SCWS_MULTI_DUALITY SCWS_MULTI_ZMAIN SCWS_MULTI_ZALL
$so->set_multi(false);
 
//设定是否将闲散文字自动以二字分词法聚合
$so->set_duality(false);
 
//设定搜索词
$so->send_text($key);
$words_array = $so->get_result();
$words = "";
foreach($words_array as $v)
{
	$words = $words.'|('.$v['word'].')';
}
 
//加入全词
#$words = '('.$key.')'.$words;
$words = trim($words,'|');
$so->close();
echo '<p>输入:'.$key.'</p>'."\r\n";
echo '<p>分词:'.$words.'</p>'."\r\n";
//========================================sphinx搜索
require('sphinxapi.php');
$sphinx = new SphinxClient();    
$sphinx->SetServer('192.168.17.160',9312);      
$res = $sphinx->Query($words,"*");    
print_r($res);
if ( !$res )
{
	die ( "ERROR: " . $sphinx->GetLastError() . ".\n" );
}  
 


最新评论:
我要评论:

看不清楚