Sphinx全文索引


Sphinx全文索引

Sphinx是一个俄国人开发的搜索引擎,
他和Lucene 的比较:

Lucene作为一个全文检索引擎,其具有如下突出的优点:
  (1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。
  (2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。
  (3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。
  (4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。
  (5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询、分组查询等等。MG4J 是另一个搜索engine 。与Lucene 主要区别是,它提供了cluster 功能,具有更OO的设计方式。 MG4J可以让你为大量的文档集合构建一个被压缩的全文本索引,通过使内插编码技术。

Sphinx支持高速建立索引(可达10MB/秒,而Lucene建立索引的速度是1.8MB/秒)
高性能搜索(在2-4 GB的文本上搜索,平均0.1秒内获得结果)
高扩展性(实测最高可对100GB的文本建立索引,单一索引可包含1亿条记录)
支持分布式检索
支持基于短语和基于统计的复合结果排序机制
支持任意数量的文件字段(数值属性或全文检索属性)
支持不同的搜索模式(“完全匹配”,“短语匹配”和“任一匹配”)
支持作为Mysql的存储引擎
可以以如下SQL方式去全文检索了:
select * from xxxx where query='test;sort=attr_asc:group_id' AND ....;




全文检索两个过程
索引创建和搜索索引


如何创建索引
创建索引文档document
原文档传给分词组件tokenizer
将得到的词元token传给语言处理软件组件linguistic processor
将得到的词term传给索引组件indexer


第一步,一些创建索引的文档
id    document
1    The students are playing ball games on the playground this afternoon.

第二步,将原文档传给分词组件
tokenizer会做以下事情
1 将文档分成一个个单独的单词
2 去除标点符号
3 去除停词 stop word
英语中的停词比如the,a,this
中文停词比如,是,的,这个
对于每一种语言的分词组件,都有一个停词集和
经过分词后得到的结果为词元token
students, playing, ball, games, playground , afternoon

第三步,词元交给语言处理组件linguistic processor
处理语言时态转换等处理
对于英语,需要做
变成小写
复数变成词根
时态变化,变成基本形态
处理完变成词Term
student, play, ball, game, playground , afternoon

第四步,传给索引组件indexer
利用得到的词创建一个字典
对字典进行按字母排序
并和documentid对应

合并相同的词成为文档倒排posting list链表
Document Frequence
文档词频,总共多少文件包含这个词
Frequence
词频,此文件中包含了几个该词
如何对索引进行搜索
用户输入查询语句
提交给sphinx后,对查询语句进行词法分析,语法分析,语言处理
搜索索引,得到符合语法树的文档
根据得到的文档和查询语句的相关性,对结果进行排序


总结
1 索引过程
有一系列被索引文件
被索引文件经过语法分析和语言处理,形成一系列词Term
经过索引创建形成词典和反向索引表
通过索引存储将索引写入硬盘
2 搜索过程
用户输入查询语句
对查询语句经过语法分析和语言分析得到一系列词Term
通过语法分析得到一个查询树
通过索引存储将索引读入到内存
利用查询树搜索索引,得到每个词的文档链表,对文档链表进行交,差,并运算得到结果文档
将搜索到的结果文档对查询的相关性进行排序
返回查询结果给用户


sphinx基于sql的全文检索引擎
coreseek 支持中文的全文检索引擎

缺点
必须要有主键
主键必须为整形
不负责数据存储
配置不灵活

相当于数据库的中间层,是一个单独的服务器
分担数据库压力,在大数据并发的时候,会有极大提高


实验手册
Sphinx+linux+apache+mysql+php+coreseek

Centos 5.5
Mysql-5.1
apache-2.2.9
php-5.2.6
    php还需要模块sphinx-1.1.0.tgz
sphinx-0.9.9.tar.gz
coreseek-3.2.14.tar.gz


sphinx
1 下载源码0.9.9-release
wget http://shpinxsearch.com/downloads/sphinx-0.9.9.tar.gz

2 编译和安装
tar -zxvf  sphinx-0.9.9.tar.gz
cd /lamp/sphinx-0.9.9
编译时需要引用mysql
./configure --prefix=/usr/local/sphinx --with-mysql=/usr/local/mysql
make && make install

然后会生成sphinx中三个重要命令,在/usr/local/sphinx/bin下
indexer 创建索引
searchd 是一个服务,进程
search 命令行搜索
/usr/local/sphinx/var下
存放sphinx生成的索引表
/usr/local/sphinx/etc下
配置文件



3 准备mysql
mysql是数据源
mysql -uroot -p123 test
帖子表
create table post(
id int unsigned auto_increment primary key,
title varchar(254),
content text);

一行数据就是一个文本
insert into post(title,content) values("linux1","content11111");
insert into post(title,content) values("php1","php1111");


4 配置sphinx
cd /usr/local/sphinx/etc
cp sphinx.conf.dist sphinx.conf
vi sphinx.conf

主数据源,原来的数据,main可以自己指定名字
source main{
type=mysql
sql_host=localhost
sql_user=root
sql_pass=123
sql_db=test
sql_port=3306
#如果是linux需要开启指定sock文件,php连接mysql也要阅读,否则需要ip和port
sql_sock=/tmp/mysql.sock    
sql_query_pre=SET NAMES UTF8
sql_query_pre=SET SESSION query_cache_type=OFF        #关闭缓存
sql_query=SELECT id,title,content FROM post        #获取数据的sql语句
# sql_attr_unit=group_id                    #对排序字段注释
# sql_attr_timestamp=data_added            #对排序字段注释
sql_query_info=SELECT * FROM post WHERE id=$id    #post表的id为文本id
}

增量数据源,后来新增的数据,继承主数据源main
可以先注释关闭
source delta:main{
    sql_ranged_throttle=100
}

主数据索引
index main{
source=main
path=/usr/local/sphinx/var/data/main    #索引表存放处
charset_type=utf-8
charset_table=0..9,A..Z,_,_a..z,U+410..U+42F->U+430..U+44F,U+430..U+44F
}

增量数据索引,先注释
vi中快速注释,比如注释400到500行
:400,500s/^/#/g
表示在每一行最前方添加#号
index delta:main{
}

分布式索引,先注释
index dist1{
}

索引器,indexer命令要读取,分配内存等
indexer{
mem_limit=128M
}

服务进程,searchd命令要读取,端口等
默认sqhinx开启9312
searchd{
}


5 创建索引
indexer
-c        指定配置文件
--all        对所有索引重新编制索引
--rotate        用于轮换索引,不停止服务的时候,增加索引
--merge        合并索引,增量索引合并到主索引
比如
/usr/local/shpinx/bin/indexer -c /usr/local/sphinx/etc/sphinx.conf --all
这次,配置文件就放在默认目录下,且只有一个main索引,
那么简化为如下就可以了
./indexer --all
报错找不到libmysqlclient.so.16,无法打开mysql客户端
我们需要把这个文件cp到所有程序都可以找到的/usr/lib下
cp /usr/local/mysql/lib/mysql/libmysqlclient.so.16 /usr/lib/
./indexer --all  成功
针对两条数据,总共产生2个文档



6 查询
./search linux1
找到了linux1这个字段
index main:query linux1
document=1, weight=1
id=1
title=linux1
content=linux1111
words
1. 'linux1':1 documents, 1 hits

每次数据库记录更新后
需要重新运行./indexer -all
才可以重新search

这时候光搜索linux是搜不到的
因为只会拆出linux1




7 中文分词
insert into post (title,content) values ('兄弟连',"感谢参加lamp兄弟连linux+php培训,谢谢关注和支持");
./indexer -all
./search 兄弟
是搜不出来的

用户搜索,sphinx , 中文文档?, 中文分词
中文分词系统,利用计算机对中文文本进行词语自动识别,
分词算法3大类
基于字符串匹配,
    机械分词,基于词典,在足够大的词典中匹配
    三个要素,分词词典,文本扫描顺序,匹配原则
    文本扫描顺序,正向,逆向,双向
    匹配原则,最大,最小,逐词,最佳
    例子
    正向最大匹配,从左到右方向
    三亚/酒店/预定
    逆向最大匹配,从右到左方向
    预定/酒店预订/酒店/三亚
    最少切分,使每句中切出的词数最小
    三亚/酒店预订
    双向最大匹配,从左到右,从右到左两次扫描
    三亚/三亚酒店/酒店/酒店预订/预订/三亚酒店预订

基于理解
    计算机模拟人对句子的理解
基于统计
    一个词出现的频率,概率,无词典分词技术
难题
歧义识别
化妆和服装
化妆/和/服装
化妆/和服/装
新词识别
人名,地名,缩略语等新词的出现

什么时候用sphinx
网站数据量增大,对搜索要求更高
网站用户反馈,搜索速度变慢
用户很多,数据很多,处理大数量的并发查询
设计一个千万或亿级别的分布式架构


mysql里的编码一定要都是utf8
\s查看,4大编码都是utf8

1 coreseek---带有中文分词的sphinx
它为sphinx设计的中文词包libmmseg包含mmseg中文分词
www.coreseek.cn下载
解压
tar -zxvf coreseek-3.2.14.tar.gz
进入到mmseg所在文件夹,先安装中文分词mmseg
cd /lamp/coreseek-3.2.14/mmseg-3.2.14
./configure --prefix=/usr/local/mmseg
编译后报错无法找到src/Makefile.in
运行
automake    可以自动添加一些环境变量到makefile里面
然后再运行./configure --prefix=/usr/local/mmseg
编译安装
make && make install
然后运行
/usr/local/mmseg/bin/mmseg 出现版本信息


2 结合sphinx和mmseg
检测安装
coreseek下的sphinx叫做csft
cd /lamp/coreseek-3.2.14/csft-3.2.14/
./configure --prefix=/usr/local/coreseek --with-mysql=/usr/local/mysql --with-mmseg=/usr/local/mmseg --with-mmseg-includes=/usr/local/mmseg/include/mmseg/ --with-mmseg-libs=/usr/local/mmseg/lib/
编译安装
make && make install

配置带有中文分词的sphinx配置文件
coreseek中的配置文件也是csft.conf, 而不是sphinx.conf
cd /usr/local/coreseek/etc/
cp shpinx.conf.dist csft.conf
vi csft.conf

#和之前一样配置
source main{
......
}

index main{
#把下面这些都停掉
#stopwords=/data/stopwords.txt
#wordforms=/data/wordforms.txt
#exceptions=/data/exceptions.txt
#charset_type=sbcs

#加入中文分词
charset_type=zh_cn.utf-8
charset_dictpath=/usr/local/mmseg/etc/        这是词典
}

然后运行indexer生成索引
./indexer --all

查找
./search linux
./search php
./search lamp兄弟连
搜索到
document=3,weight=3
    id=3
    title=
    content=
words
1 lamp: 1documents, 1hits
2 兄弟: 1documents, 2hits
3 连: 1documents, 3hits





*******************************************************
php使用sphinx技术
两种方式
1 sphinx php模块动态加载到php
2 spninxapi类文件
    在coreseek中有对应php,java,ruby,python的api
    ls /lamp/coreseek-3.2.14/csft-3.2.14/api/
    java, ruby,  sphinxapi.php, sphinxapi.py......
    只要把文件包含进去就可以了

首先要有数据
建立sphinx配置文件
生成索引
需要sphinx开启服务,searchd服务,9312端口
php客户程序连接sphinx服务


1 启动spninx服务
cd /usr/local/coreseek/bin
./searchd
pstree |grep search
netstat -tunpl|grep :9312

./searchd参数
-c 指定配置文件
--stop 停止
--pidfile 显示指定一个pid文件
-p 指定端口

如果失败,可以
kill -9 PID



2 php连接使用sphinx
方法一
全php加载sphinx模块
下载php的sphinx模块
wget http://pecl.php.net/get/sphinx-1.1.0.tgz
tar zxf sphinx-1.1.0.tgz
cd /lamp/sphinx-1.1.0
都是通过php的phpize命令来加载模块
phpize用于生成一个configure的脚本用来编译
/usr/local/php/bin/phpize
编译后生成.so文件    
./configure --with-php-conf=/usr/local/php/bin/php-conf
默认会出现error, libsphinxclient找不到
先要编译一下libsphinxclient
cd /coreseek-3.2.14/csft-3.2.14/api/libsphinxclient
./configure
make && make install
在编译模块源码生成.so文件
cd /lamp/sphinx-1.1.0
./configure --with-php-conf=/usr/local/php/bin/php-config --with-shpinx
make && make install
编译到php的动态库文件目录, php.ini文件会加载这些动态库
ls /usr/local/php//lib/php/extensions/no-debug-non-zts-20060613
可以看到一个sphinx.so文件生成
(还可以看到pdo_mysql.so, memcache.so等文件)
vi /usr/local/php/etc/php.ini
extension=sphinx.so

重起apache
/usr/local/apache2/bin/apachectl restart
cd /usr/local/apache2/htdocs/
vi index.php
phpinfo();
访问
192.168.10.1
看到sphinx已经被支持
然后php手册里面的sphinx命令可以使用



方法二
使用api类连接sphinx
cp /lamp/coreseek-3.2.14/csft-3.2.14/api/sphinxapi.php /usr/local/apache2/htdocs/
在所有php页面中包含
include 'sphinxapi.php';




******************************************************************************************
linux 网站服务器在windows下快速开发
1 acl权限设置
2 修改apache进程执行者
3 修改samba的访问用户
4 用samba对apache网站根目录进行共享
5 在windows中用映射网络驱动器
6 确保php代码在linux下创建的目录或文件权限适当


yum -y install samba* --skip-broken
vi /etc/samba/smb.conf
[web]
path=/usr/local/apache2/htdocs
browseable=yes
writable=yes

serbice smb  restart
chkconfig smb on

useradd apache
smbpasswd -a apache
    123

windows下
\\168.192.10.1\web已经可以访问了
或者映射到z:\盘

接下来还有权限问题,apache用户还不能在共享目录里面添加删除文件
setfacl -m u:apache:rwx -R htdocs/
setfacl -m d:u:apache:rwx -R htdocs/
这样,windows里面,就可以使用apache用户在web目录下创建删除文件了

ps -ef|grep httpd可以看到httpd进程目前的执行用户是daemon
而我们在windows这里用apache用户放入的新文件和文件夹
在linux里面,apache服务器运行时,使用的是daemon用户来执行
这个用户对这些新文件是没有权限的
而对于apache服务器使用daemon用户生成的文件,smb用户也没有权限
所以我们需要把执行者变成apache用户
vi /usr/local/apache2/etc/httpd.conf
User apache
Group apache


******************************************************************************************

find.php


#让浏览器选择默认字符集
Find


    

        //匹配模式,还有SPH_MATCH_ALL表示不拆词
        //SPH_MATCH_ANY表示拆词
        $sphinx->SetMatchMode(SPH_MATCH_ANY);
        //sphinx->setLimits(0,0);
        //后面参数是数据源索引,*表示在所有索引中搜索
        //结果中包含了mysql表中命中的记录id
        //然后就可以通过id,找到这条记录
        //比如select * from post where id in (1,2);
        $result=$sphinx->query("$keyword", "*");
        //echo "
" 
  
        //print_r($result);
        //拿出这些命中的id,在matches这个数组里面,形成新的数组
        //print_r(array_keys($result['matches']));
        //echo "
"

        //把取得的id数组,变成6,7这样的格式
        $ids = join(",", array_keys($result['matches']));
        mysql_connect("localhost","root","124");
        mysql_select_db("test");
        $sql="select * from post where id in({$ids})";
        //设置下连接字符集
        mysql_query("set named utf8");
        $rst=mysql_query($sql);
        $opts=array(
            "before_match"=>"",
            "after_match"=>""
        );
        while($row=mysql_fetch_assoc($rst)){
            //对于某个单词作css处理,甚至可以做成button
            $rst2=$sphinx->buildExcerpts($row,"main",$keyword,$opts);
            echo "{$rst2[0]}
";
            echo "{$rst2[1]}
";
            echo "{$rst2[2]}
";
            echo "
";
            
        }
    ?>




当searchd这个服务器来后,后台的indexer命令需要加上--rotate
./indexer --all --rotate

sphinxClient中的高亮显示
给返回的结果直接添加html效果,默认是加粗
buildExcerpts(array $docs, string $index, string $words  [,array $opts])


然后在前台html搜索页面输入 lamp兄弟连
然后可以把所有包含:'lamp' '兄弟' '连'  的mysql表中的记录搜索出来



**************************************************************************************
sphinx实时索引

对于新加入数据库的数据,需要主数据源+增量数据源


帖子表
mysql的unsigned表示禁用负值
只能用在整形
drop table post;
create table post(
id int unsigned auto_increment primary key,
title varchar(254),
content text);
insert into post(title,content) values("linux1","content11111");
insert into post(title,content) values("php1","php1111");
当有了新记录
insert into post(title,content) values("linux2","content2222");
insert into post(title,content) values("php2","php2222");
要么再运行./indexer -all来读取main索引
要么就要当成增量来处理
尽量做到实时索引

1 创建一个计数器
数据库中增加计数器,记录将文档分为两个文档id
每次重新构建主索引时,更新这个表
create table sph_counter(counter_id int unsigned auto_increment primary key, max_doc_id int not null);
每进行一次主索引,就触发更改id成主索引id最大值
表里只一行数据,id永远是1

2 再次修改配置文件
主数据源,继承数据源,主索引,继承索引(增量索引)
主数据源里面需要把要查询的语句修改
主数据源里的数据都是小于等于max_doc_id
vi /usr/local/coreseek/etc/csft.conf
source main{
sql_query_pre=REPLACE INTO sph_counter SELECT 1, MAX(id) FROM post
sql_query=select id, title,content from post where id<=(select max_doc_id from sph_counter where counter_id=1)
}

增量数据源,继承主数据源
sql的检索字段名一定要和主里面一样
source delta:main{
sql_query_pre=set names utf8
sql_query=select id, title, content from post where id > (select max_doc_id from sph_counter where counter_id=1)
}

主索引不用变,记得只要修改索引保存位置path到coreseek下,中文分词
Index main{
source=main
path=/usr/local/coreseek/var/data/main
。。。。。。
}

增量索引
index delta:main{
source=delta
path=/usr/local/coreseek/var/data/delta
}

其他的都不动


测试
cd /usr/local/coreseek/bin
pstree |grep searchd
服务器来后,所有指令一定要跟上--rotate
./indexer --all --rotate
然后就会根据现在的sql表,生成主数据
增量数据还没有

这时候执行insert
insert into post(title,content) values("linux3","nsert into表示插入数据,数据库会检查主键");
insert into post(title,content) values("php3","insert ignore表示,如果中已经存在相同的记录");
现在新数据还不属于任何数据源

执行增量索引创建
./indexer delta --rotate
现在主数据不会变,之后插入的数据全部进入增量数据
然后就可以搜索到了

然后我们每5分钟执行一次indexer,
就让新纪录更新到增量数据中
尽量降低网站搜索的延时
比如每天晚上3点
把主索引更新,计数器修改
之后所有更新数据全进入增量数据
白天每5分钟更新下增量数据



编写脚本,一般放在/etc/ini.d
cd /usr/local/coreseek/var/log/
touch main.log
touch delta.log

cd /usr/local/coreseek/etc/
vi main.sh
#!/bin/bash
#main.sh
/usr/local/coreseek/bin/indexer main --rotate >> /usr/local/coreseek/var/log/main.log

vi delta.sh
#!/bin/bash
#delta.sh
/usr/local/coreseek/bin/indexer delta --rotate >> /usr/local/coreseek/var/log/delta.log

然后计划任务,增量索引每5分钟重建
主索引在凌晨2:30重建

crontab -e
*/5 * * * * /usr/local/coreseek/etc/delta.sh
30 2 * * * /usr/local/coreseek/etc/main.sh

如果是凌晨3点运行就是
00 03 * * * /usr/local/coreseek/etc/main.sh

给它们加权限
chmod a+x *


**********************************************************************
分布式索引
为了改善查询延迟问题和提高多服务器,多cpu或多核环境下的吞吐率
对于大量数据(10亿级的记录数和TB级的文本量)上的搜索应用来说是很关键的
分布式思想,对数据进行水平分区(HP,Horizontally partition),然后并行处理,
当searchd收到一个对分布式索引的查询时,做如下操作
1 连接到远程代理
2 执行查询
3 对本地索引进行查询
4 接收来自远程代理的搜索结果
5 将所有结果合并,删除重复项
6 将合并后的结果返回给客户端

把水平分布在不同的服务器上的数据,抽取过来做处理
index dist{
type=destributed
local=chunk1
agent=localhost:9312:chunk2 本地
agent=192.168.100.2:9312:chunk3 远程
agent=192.168.100.3:9312:chunk4 远程
}





********************************************************************
MYSQL中insert into和replace into以及insert egnore
insert into表示插入数据,数据库会检查主键(PrimaryKey),如果出现重复会报错;
replace into表示插入替换数据,需求表中有PrimaryKey,或者unique索引的话,
    如果数据库已经存在数据,则用新数据替换,如果没有数据效果则和insert into一样
insert ignore表示,如果中已经存在相同的记录,则忽略当前新数据
********************************************************************

你可能感兴趣的:(php)