Loading...

    AD: 猛买网,精彩团购 | Jobsdigg | 很棒的男装店 | 网站地图

解决Rails+Sphinx特定搜索词报错

可任意转载,但必须在醒目位置以超链接形式标明文章原始出处和作者信息
原文地址:http://www.blogkid.net/archives/2660.html

我在一个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 rsp 00007fffea067490 error 4
searchd[16822]: segfault at 0000000000000000 rip 000000000047242c rsp 00007fffea067490 error 4
searchd[16858]: segfault at 0000000000000000 rip 000000000047242c rsp 00007fffea067490 error 4

再用报错的关键词搜索一次,segfault也多了一条。至此,可以断定是sphinx处理出错导致没有返回结果。于是我把coredump打开(如果早打开,可能可以更早定位问题),顺利地获得了一个core文件。

再用core文件gdb,很快就定位了问题。在src/sphinx.cpp中,第12647行出了core。

12631     // boundary checks
12632     if ( !pHlist )
12633     {
12634         // there are no more hits for current docs block; do we have a next one?
12635         assert ( pDocs );
12636         pDoc = pDocs = GetFilteredDocs ();
12637
12638         // we don't, so bail out
12639         if ( !pDocs )
12640             break;
12641
12642         // we do, get some hits
12643         pHlist = m_pRoot->GetHitsChunk ( pDocs, m_uMaxID );
12644         assert ( pHlist ); // fresh docs block, must have hits
12645      }
12646      // carry on
12647      assert ( pDoc->m_uDocid<=pHlist->m_uDocid );
12648      while ( pDoc->m_uDocid<pHlist->m_uDocid ) pDoc++;
12649      assert ( pDoc->m_uDocid==pHlist->m_uDocid );

在gdb中print了一下指针pHlist,发现它指向了0×0,不出问题才怪。

UPDATE(本段应子宁要求而增加) 至此,问题已经明朗:在searchd后台服务执行搜索时,因为暂时未知的原因,pHlist指针指向了0×0,导致程序崩溃。于是前端的rails应用没有收到任何返回。

五、快速修复

定位问题之后,我打算简单修复一下。将代码做如下修改并编译之后,顺利地解决了此问题。奇怪的是,12644行assert为何没起作用?

12643         pHlist = m_pRoot->GetHitsChunk ( pDocs, m_uMaxID );
12644         assert ( pHlist ); // fresh docs block, must have hits
12645       }
12646       // added by blogkid
12647       if (!pHlist)
12648       {
12649         // fix the null point error
12650         // by zhanglei
12651         break;
12652       }
12653       // added end by blogkid
12654       // carry on
12655       assert ( pDoc->m_uDocid<=pHlist->m_uDocid );
12656       while ( pDoc->m_uDocid<pHlist->m_uDocid ) pDoc++;

我用的是coreseek出品的修改版sphinx,csft 3.1rc-1和3.0beta3均有此问题。而我尝试搜许多英文词汇,都不会引发此问题,这也解释了为什么之前我一直找不到解决方法。希望这篇文章,能对遇到同样问题的朋友们有些帮助。

整个追查问题的过程,其实有很多可以“扯远”的细节,比如查看tcpdump结果、配置开启coredump、使用gdb调试core文件等等;而且也留下了一些问题,比如GetHisChunk方法什么情况下会返回0;为什么此处用一句break居然修复了此问题(我本意是想让searchd不再报错,没想到它可以正常返回搜索结果了),这些问题,还有待进一步从sphinx的源码中,寻找答案。

0 Responses to “解决Rails+Sphinx特定搜索词报错”


  1. No Comments

Leave a Reply