February 11th, 2010 by 张磊
我在一个Rails应用中使用了sphinx做全文检索(用的是coreseek提供的版本),但在搜索一些词(比如“环境”)时,Rails日志中会有“comparison of Fixnum with nil failed”的错误,Sphinx的query.log/sphinx.log却无任何记录。此问困扰了我很久,也没搜到什么解决方案。今天最终解决了,于是把查找问题的过程,写在这里。 一、问题初析 仅从Rails的报错信息来看,是将一个数字与nil作比较,导致ruby报错(谁再把脚本语言和弱类型语言混在一起我跟他急)。出错的代码在ultrasphinx-1.11/vendor/riddle/lib/riddle/client.rb的433行: 430 header = socket.recv(8) 431 status, version, length = header.unpack(‘n2N’) 432 433 while response.length < length 434 part = socket.recv(length – response.length) 435 response << part if part 436 end 经验证,length是nil导致ruby报错,而length是从431行的header.unpack得到的。进一步debug发现,header是一个空串,也就是说,430行的socket.recv根本没有从服务器获得响应。 最初想得很简单,在header之外用了一个死循环。只要header为空,就继续调用socket.recv。没想到这直接导致ruby进程陷入死循环,也就是说,对某些query,根本无法收到服务器的响应。 二、tcpdump未果 ruby不停报错,于是想到抓包,看看究竟是哪里出问题了。 祭出tcpdump,自己功力不深,感觉dump出的内容极其晦涩,根本无助于定位问题。 三、初试gdb 仔细看了ruby代码后,感觉Riddle中使用的Socket,是对C语言中Socket操作的简单封装,出问题的可能性不大。因此我把目光放在了sphinx的后台服务searchd上面。 服务器上还留有编译时的C++代码,选了几个看看,没能理出头绪。开了gdb也实在不知该从何处下断点。 到此时,我已经到了放弃的边缘。我一直觉得,是自己对Socket不熟悉于是不能深入地追踪此问题。直到想到去查一下dmesg。 四、从coredump找到线索 dmesg是从一同事处学来的,没想到今天帮我了大忙。服务器的dmesg显示,searchd进程出现了segfault: searchd[16818]: segfault at 0000000000000000 rip 000000000047242c [...]
March 27th, 2009 by 张磊
已经决定把服务器换成Linux,我很快就在一台虚拟机上操练了起来。通过apt-get和gem能搞定大部分应用程序和ruby的库,但mmseg(分词算法)和Sphinx(搜索引擎)是需要编译安装的。在此我使用了coreseek提供的版本。 准备工作: apt-get install python-dev make g++ 下载并解压软件包不再赘述。首先编译安装mmseg: ./configure –prefix=/usr/local/mmseg make make install 我在make的过程中遇到两次错误,都提示“strncmp was not defined in the scope”。这时需要找到报错的文件,打开编辑,加入一行:#include “string.h”。搜索发现这个问题并不普遍,可能和使用的gcc版本有关。修改之后就编译通过了。 之后编译安装Sphinx: ./configure –prefix=/usr/local/coreseek –with-python –with-mysql –with-mmseg-includes=/usr/local/mmseg/include/mmseg –with-mmseg-libs=/usr/local/mmseg/lib/ make make install 在make过程中也遇到两次错误,提示为“cannot convert int* to Py_ssize_t*”。经搜索发现只有在64-bit的Linux上编译时才会出现,解决方法也很简单:打开报错的文件,找到对应的行,把int pos = 0改为 Py_ssize_t pos = 0即可。一共需要修改两处,修改之后就可以安装成功了。 最后,再把/usr/local/coreseek/bin加入到PATH变量中,Sphinx的命令(indexer/searchd/search)就可以正常使用了,至此,安装完成。 UPDATE: make容易失败,谨记make clean。
February 1st, 2009 by 张磊
以前写过三篇关于ultrasphinx和sphinx的文章,都是关于如何更好地利用sphinx来做搜索引擎。最近在我需要在一台windows服务器上部署一个rails系统和相应的sphinx,积累了一些心得写出来与大家分享。如果你的服务器是linux,这篇文章应该对你没什么作用。 首先简单解释下这几个名词,sphinx是一个全文检索软件,类似于lucene;ultrasphinx是一个rails的插件,可以让rails项目轻松使用sphinx;而我使用的sphinx是由coreseek改造过的版本,加入了中文分词的算法(mmseg)。 1、以服务的方式运行sphinx 在开发环境中,只要执行”rake ultrasphinx:daemon:start“,就可以启动一台sphinx服务器。但如果在生产环境还能这么做么?把sphinx安装为服务无疑是个靠谱的办法,这样它可以像mongrel、apache一样随系统启动。sphinx自带了安装为windows服务的命令: searchd –install –config xxxx.conf 不妨把这个加入到rake命令中,于是我hack了一下ultrasphinx插件的任务列表,加入了一个”rake ultrasphinx:daemon:install“命令。名为ultrasphinx.rake的文件我将稍后提供。 既然把sphinx安装为服务,相应的start和stop命令,也需要改改,改为使用”net start/stop searchd”。 2、主索引和增量索引(delta) 对于大量的数据,经常重建主索引很不现实。我的项目中几十万条数据,重建一次索引需要将近10个小时,所以打算以每天多次增量索引+一次合并索引的形式去做。 但javaeye论坛有人发帖说,在windows上要编制索引必须先停止sphinx服务器,因为searchd服务会把索引文件挂住。实际上并非如此:不论在windows还是在linux,sphinx服务器都会锁住索引文件,但只要在编制索引时加上–rotate参数,就可以不停止sphinx服务器来编制索引。sphinx的官方文档解释: –rotate creates a second index, parallel to the first (in the same place, simply including .new in the filenames). Once complete, indexer notifies searchd via sending the SIGHUP signal, and searchd will attempt to rename the indexes (renaming [...]
August 20th, 2008 by 张磊
前段时间我写了在Rails中使用Ultrasphinx代替acts_as_ferret插件来做全文检索,相应地也会用sphinx取代ferret。具体的做法可以看这篇文章。 先明确下概念,sphinx是类似lucene的一个全文检索实现,而ultrasphinx是rails的插件,有了ultrasphinx,我们可以方便地使用sphinx进行索引、检索。 但我在使用的过程中发现,每次进行检索之后,ultrasphinx都会调用一次或多次find_all_by_id来获得符合条件的ActiveRecords。但如果在项目中用了cache_fu之类的插件,使用memcached做对象缓存后,ultrasphinx依然没法使用缓存。为了避免这成为性能的瓶颈,我开始探索,是否可以让ultrasphinx和cache_fu合作,让搜索的结果也能使用memcached的缓存。 查了一下,ultrasphinx的作者还真的提供了这个口子,虽然他也不确定是否会有人用。打开/vendor/plugin/ultrasphinx/lib/ultrasphinx/search/internals.rb,找到一个叫reify_results的函数,里面有这样一段: finder = ( Ultrasphinx::Search.client_options['finder_methods'].detect do |method_name| klass.respond_to? method_name end or # XXX This default is kind of buried, but I’m not sure why you would need it to be configurable, since you can use ['finder_methods']. “find_all_by_#{klass.primary_key}” ) 也就是说,只要设置Ultrasphinx::Search.client_options['finder_methods']的值,就可以自定义查找ActiveRecord所使用的方法。正巧,在使用cache_fu插件后,有一个get_caches可以达到我们的目标。 于是在config/enviroment.rb中增加一行: Ultrasphinx::Search.client_options['finder_methods'] = ['get_caches'] 然后重新启动应用。试着搜索一下,不出意外会看到一个出错的提示。这里是因为get_caches函数返回数据的格式和find_all_by_id有点小区别。find_all_by_id返回的是一个ActiveRecord的数组,而get_caches返回的是一个数组的数组。只消在上面的internal.rb中的reify_results函数里,找到下面这个地方: records.each do |record| record = record[1] if record.is_a? Array [...]
July 23rd, 2008 by 张磊
关于ultrasphinx的在windows上的安装和简单修改,请看这篇文章:深入探索:Windows+Rails+中文全文检索。 在上一篇文章里,我们初步可以让sphinx、ultrasphinx以及libmmseg正常地工作了。但是这些还不够,因为在实际的使用中,rails的AR之间会有很多关联。比如用户搜索一本图书时,可能希望也搜索它的作者,但如果图书和作者放在两个表呢?我们需要在ultrasphinx中处理这些关联。我在学习时能找到的资料都是英文,今天写这篇文章,希望能对英文不好的同学们有些帮助。 在Rails项目的ActiveRecord定义中,我们通过is_indexed函数告诉ultrasphinx这个Model需要进行索引的字段信息,最简单的写法: class Book< ActiveRecord::Base is_indexed :fields =>[:title] end 这里就表示将title属性交给ultrasphinx去做索引。但如果是一些关联的对象,该怎么做呢?如果使用acts_as_ferret,可以比较灵活,因为索引是绑定在Model的。而ultrasphix的思路是把索引和model独立开来,在特定的时候,使用SQL语句从数据库中获得数据,然后异步生成索引。 那么,如果要加入关联对象的字段,该怎么办呢?上面已经说到,信息是从数据库里得到,然后异步进行索引的。也就是说我们需要进行连接查询,然后获得相应的字段——大体思路就是这样,ultrasphinx提供了一些办法尽量避免让你编写复杂的SQL。 返回刚才的例子,假设每本图书都属于一个出版社,这是一对多的关系。出版社用Press来表示,表示名称的属性是name。我们需要让用户可以一起检索到出版社的名字,ultrasphinx中提供了一个”:include”来帮我们做到: class Press < ActiveRecord::Base has_many :books end class Book < ActiveRecord::Base belongs_to :press is_indexed :fields=>[:title], :include=>[{:association_name=>'press' , :field=>'name' , :as=>'press_name }] end 保存后重新运行”rake ultrasphinx:configure”,将在RAILS_ROOT/config/ultrasphinx/目录生成一个新的development.conf。如果有兴趣可以打开看看其中的SQL生成成了什么样子,不过还是建议你别看了,继续看文章吧。之后运行”rake ultrasphinx:index”,就会重新生成索引,将出版社信息也增加进去。可以再运行”rake ultrasphinx:daemon:start”启动伺服器,测试的办法请看这篇文章,不再赘述。 上面交代了belongs_to的时候的处理方式,如果是has_many呢?ultrasphinx为提供了另一个”:concatenate”。假如每本书(Book)都有一些描述信息(Info.content),我们想把这些信息也一起加入到索引中。 class Info < ActiveRecord::Base belongs_to :book end class Book < ActiveRecord::Base belongs_to :press has_many :infos [...]
July 21st, 2008 by 张磊
先说点题外话,以后大家买独立服务器的时候,还是买linux的吧。如今这么活跃的开源社区为linux贡献了好多柴火,而相应的软件在windows实在发展得缓慢。要是我当初买了linux服务器,以前和现在都能少许多烦恼,今天的“深入探索”,根本就用不着。 事情是这样的,我手头的一个项目里,打算用rmmseg+ferret+acts_as_ferret搭配起来做全文检索。一直也相安无事,直到我今天决定放到生产环境试试看。这一试不要紧,发现DRB服务器启动不起来,提示的是”fork() funtion is unimplemented on this machine”。windows上哪里有fork啊,我顿时欲哭无泪。如果不用DRB的话,就也不能用acts_as_ferret了(原因请看robbin的这个帖子)。 恩,这时我并没有灰心,因为我在前几天看到了另一个解决方案,用的是sphinx,狮身人面。其中有windows版本的安装: 在Windows上安装 在Windows上为Sphinx打补丁、编译、连接libmmseg要比在Linux上做这些事情麻烦得多,而且大多数Windows上的开发人员都没有自己编译开源软件的习惯,幸好李沫南已经做了一个安装包: http://www.coreseek.com/ft/csft_setup_2.5.1.exe 执行这个安装包即可安装Coreseek的Windows版,假设将Coreseek安装在D:\CsFullText25 将D:\CsFullText25\bin加入到环境变量PATH中,以便以后在命令行能够找到Sphinx提供的各种工具。 安装Ultrasphinx Sphinx在Linux和Windows上都已经安装好了,我们可以通过一个Rails程序来做一下测试。 假设我们原先有一个Rails应用thought_log cd thought_log ruby script/plugin install -x svn://rubyforge.org/var/svn/fauna/ultrasphinx/trunk 若这个Rails应用尚未提交到SVN中,或者使用其他版本管理工具,则使用 ruby script/plugin install svn://rubyforge.org/var/svn/fauna/ultrasphinx/trunk 注意,执行这条命令前需要先安装好SVN for Windows(不是TortoiseSVN)。 当我按部就班执行到红色的那一步,命令行里提示我该svn不存在。我直接打开,也是404错误。这个最重要的rails插件,在我要安装的时候,居然消失了。 百般无奈,只好放一放,出去打个电话。电话回来,突然有了灵感,尝试用”gem install ultrasphinx”,居然安装成功了,不过是作为gem。没关系,我跑到gems目录下,找到ultrasphinx的文件夹,整个复制到rails项目的vendor下面,同时把文件夹改名为ultrasphinx。 果然,gem安装的ultrasphinx就是那个消失的rails插件,不晓得为啥从插件变身gem了。但是探索还没有结束: 为了在Windows上正常使用Ultrasphinx,需要为Ultrasphinx打一点补丁: 修改vendor/plugins/ultrasphinx/tasks/ultrasphinx.rake 在文件最后加入: def os_path path if RUBY_PLATFORM =~ /mswin32/ path.gsub!(‘/’, ‘\\’) end path end 然后将其中的: “searchd –config [...]