兼济天下则达 独善其身则穷
在前面的介绍中,都没有处理更新和删除问题,这里有必要说说。在关于sphinx引擎的一些想法中说过公司所用的引擎中,处理更新和删除的办法是在索引中增加一个属性来标志这条记录是否失效,每次做增量时,就要去主索引和增量索引中更改相应id的属性值,这确实可以解决问题。不过并不是一个很好的解决办法,Sphinx的作者也说过这种方法既麻烦又容易出错。既然有更新和删除这个需求,必然会提供解决的办法,这个办法就是kilst。所谓的klist,就是kill list,按照字面理解,就是删除列表。我们只需要在增量索引中保存一个id列表,搜索时,如果在主索引中搜到相关文档,而文档的id存在于增量索引的id列表中,则这个文档将被丢弃。
这里有一个需要注意的是,当文章被删除时,仅仅通过增量抓取,在增量索引中并不能知道主索引中哪一个文档被删除了,所以这就必须在表中文档被删除时,能够记录下被删除的id,这就需要用到触发器,也需要建立一个辅助表来保存这些id。辅助表的建立如下:
create table sphinxklist( id integer not null, ts timestamp not null );
触发器的建立如下:
DELIMITER // CREATE TRIGGER sphinx_kill AFTER DELETE ON wp_posts FOR EACH ROW BEGIN INSERT INTO sphinxklist VALUES (OLD.ID, NOW()); END //
有了这些准备工作后,我们就可以使用klist了,事实上在之前的配置文件的基础上,只需要修改一点点内容就好了。首先修改主索引
source srcmain : base{ sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = UPDATE sphinx_helper SET main_tmp_maxts=NOW() WHERE appid='blog_search'; sql_query = \ SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified < (SELECT main_tmp_maxts FROM sphinx_helper WHERE appid='blog_search'); sql_query_post_index = UPDATE sphinx_helper SET main_maxts=main_tmp_maxts WHERE appid='blog_search'; sql_query_post_index = DELETE FROM sphinxklist WHERE ts < (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search'); sql_attr_timestamp = post_modified sql_field_string = post_title }
可以看到,相对于之前的配置,这里只添加了一行
sql_query_post_index = DELETE FROM sphinxklist WHERE ts < (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search');
添加这行是为了防止之前运行引擎时留下的id再次被使用。
之后修改临时索引:
source srcdelta_temp : srcmain { sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = SET @maxtsdelta:=NOW(); sql_query_pre = UPDATE sphinx_helper SET delta_tmp_maxts=@maxtsdelta WHERE appid='blog_search'; sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search')\ AND post_modified < @maxtsdelta; sql_query_killlist = SELECT ID FROM wp_posts WHERE post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE \ appid='blog_search') AND post_modified < @maxtsdelta UNION SELECT id FROM sphinxklist; sql_query_post_index = UPDATE sphinx_helper SET delta_maxts=delta_tmp_maxts WHERE appid='blog_search'; }
也只是添加了一行,也就是将这次抓取的id与sphinxlist中的id合并。 之后还需要修改Shell脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/bin/bash baseDir=/home/long/sphinxforchinese/blog_search conf=$baseDir/etc/blog_search.conf binDir=$baseDir/bin cd $binDir while [ true ] do #./indexer -c $conf --merge-klists --rotate --merge delta deltaTemp ./indexer -c $conf --merge-klists --rotate --merge delta delta_temp if [ "$?" -eq "0" ]; then cat $baseDir/script/post_merge.sql | mysql -u root --password=123456 blog ./indexer -c $conf --rotate delta_temp fi sleep 60 done |
这个脚本相对于原来的只增加了--merge-klists这个参数,这个参数的意义是,将delta_temp合并到delta时,并不会删除delta的klist,而是将delta_temp的klist和delta的klist合并,这正是我们想要的。经过这样的变化,一个可以处理更新和删除的main+delta索引就建好了。
感谢Sphinx团队,感谢Sphinx-for-chinese团队,给我们提供了一个这么好用的开源引擎。
最近一个星期都在看Sphinx搜索引擎的文档,并和组里的一个同事合作为公司的企业空间搜索建立索引,提供搜索服务,所以对于Sphinx有了一些了解,顺便几下来,以后用到了可以再看看。
先八卦一下,Sphinx首先是俄罗斯人Andrew Aksyonoff开发的全文搜索引擎,开源之后有其他人参与进来,功能更加强大了。俄罗斯人还是真是厉害,之前是Nginx,现在是Sphinx。可是Sphinx不支持中文,所以要下载Sphinxforchinese才可以用。
Sphinx的数据源主要来自数据库,如Mysql,这也是最常见的方式。以下主要写给公司配置引擎时的一些体会。
1.一般使用都是一个主索引和增量索引,主索引建立后一直不变,变化的是增量索引,搜索的结果为合并主索引和增量索引的搜索结果。每隔一段时间就到数据源中抓取数据,保存在一个tmp索引中,然后这个tmp索引和增量索引合并,当然也可以隔一段时间就将增量索引和主索引合并,但这个时间间隔最好长一些。
2.建索引需要的数据分布在许多个表上,所以要先写爬虫将这些表的数据从数据库中抓取出来,存到另一个表中。之后在Sphin的配置文件中,数据就可以来自这个新建的表。这个新建的表最好有一个自动变化的时间字段,也就是每次在这个表中插入数据或更新数据,这个时间自动都会变化,这个字段将用于增量索引。另外还需要建一个表来保存上一次抓取的时间,从这个时间往后,抓取新的数据。
3.默认情况下,从数据源中选出的数据都是建索引的。而默认情况下,对于建索引的数据,Sphinx将不会保存原始数据,如果需要Sphinx既建索引,又要保存数据,在配置文件中,将这个字段写为sql_field_string。对于时间类型,在sql_query中select数据时,就要用函数unix_timestamp将它转为整形的时间戳,在配置文件中,要将这个字段写为sql_attr_timestamp,这样在客户端中调用api转化时间时才会准确。
4.sql_query_post和sql_query_post_index是有区别的。前者是当Sphinx从数据库中得到数据后,立刻就会运行,而后者只有当索引真正成功建立后才会运行,这个区别还是很重要的。对于真正严格的程序,不应该在sql_query_pre和sql_query_post中更新增量时间,而应该在sql_query_post_index中更新增量时间。还有一个区别是sql_query_post和sql_query_post_index是存在与两个不同的tcp连接中,因为Sphinx从数据库中得到数据后去建索引,将会花费很长时间,所以它会将数据库连接关闭,等到索引建好之后,再去连接数据库,所以sql_query_post_index会在另一个连接中
5.对于可以使用id来做增量索引的数据,需要将这次增量的最大id保存到数据库中。一个很诱人的做法是,将这次增量的最大id保存在一个值中,然后在sql_query_post_index中将这个值保存到数据库中,这其实是不对的。因为上一条中说过,sql_query_post_index会在另一个连接中,所以之前连接中的值在这个连接中失效了。一个做法是将增量的最大id保存到数据库中一个tmp字段中,等到索引建成功后,在sql_query_post_index中,将这个最大id从数据库中读出,写到用于做增量索引的字段中。
6.事实上,比较难的一点是在于数据有更新的情况下,如何处理。当数据有更新时,在主索引中原来的数据将会失效,但是搜索时还是会搜到它。一个解决的办法是将原来的数据标示为删除,这就需要一个标示字段了。这个办法是我组长想出来的,在sql_query中就给它添加一个字段,标示未删除的。每次增量索引结束后,就通过每条记录的id(在Sphinx中,会给每条记录一个id),将主索引中相应的记录标示为删除。在搜索时,只需要搜索出标示为未删除的就可以了。对于官方文档中,我还没有看到如何解决这个问题的介绍。
7.如果能写一个程序来自动生成配置文件,那就再好不过了。上次我是手动输的,既容易出错,有耗眼力和精力。
整个过程最重要的还是将分布在多个表中的数据合并为一个表以及处理更新这两步上,如果能解决这两个问题,一个可用的全文搜索就完成了。暂时先写到这里,等以后有了新的体会再补充。
看来是我错了,文档中有说到数据更新这个问题,是用Klist,具体可以看文档。看Sphinx的源码很不舒服,因为可恶的匈牙利命名。