分类目录归档:Linux

in-source builds or out-source builds

最近一段时间在跟进解决一系列的跟编译相关的事情,发现大家特别喜欢在源码目录直接编译代码,功能角度上这没有什么问题,但从源码管理角度来看这种方式还是存在很多不好的地方,最直观的感受就是一堆凌乱的编译中间文件。那么为什么我们应该选择out-source builds而不是in-source builds呢?(这篇文章列得很详细http://voices.canonical.com/jussi.pakkanen/2013/04/16/why-you-should-consider-using-separate-build-directories/)

1. 易于清理
这个是最容易理解也最显而易见的,一堆中间临时文件,干扰你阅读源码,干扰你打包源码tree,干扰你清理编译中间文件。

2. 可以针对不同的编译选项设立独立的编译目录
这样的好处显而易见,可能做测试,可能给不同类型的用户使用,需要多套不同的编译选项,这样in-source builds就不能满足要求了。

3. 可以对源码目录和编译目录做特定控制或优化
比如源码不允许修改(只读),或者需要加速编译,可能会把编译目录放到更快速的存储设备上。

向目标文件注入编译环境信息

【背景】
前段时间一个离职同事几年前的老模块需要交接,由于长时间无人维护,一时找不到代码路径,只得从整个后台中心的代码库里面“人肉“。记得曾经还出现过类似的情况,找不到老的编译环境(现有的系统,机器都升级了),被迫重构了老代码。工作时间越长,这样的问题也就会越来越频繁,老是这么死板的处理也不是个办法,费时费力,苦不堪言。如果可以将server部署前的编译环境信息集成到目标文件中,是否就可以根据线上运行的目标文件定位到相关的信息呢?目前部门的开发方式都是依赖于框架,所以只得在框架上下功夫。定好2个核心原则,撸起袖子就是干:对开发者透明,能便捷的获取信息。

【实现】
整个框架的编译体系是通过cmake维系的,通过其丰富的能力可以实现一些特殊的功效。框架使用gflags来维护命令行参数,本已经集成一些基本的参数,比如threads, conf,pid_file等,所以直接通过这种方式来获取编译环境信息对开发者更加熟悉和便捷。
1. cmake获取编译环境信息。

2. cmake将信息string以宏的方式透传给框架头文件。

3. 框架头文件使用宏来区分框架编译和业务编译。

4. 运行效果。

 

 

编译提速实践

【背景】
公司开发机性能太差,随便编译一个普通的项目都要十几秒。

【先验知识】
1. 我们知道ccache和distcc能够缓存部分结果,加速编译
2. 我们还知道make可以利用多核,加快编译

【实践】
1. distcc需要多台机器,部署稍微复杂,因此先将ccache和make用起来。
2. 我们需要无感知的使用这些提速项

==cmake==

==用以下替换掉make==

【效果】
使用后,正常的二次编译时间大致缩短一半。

关于sigpipe

1. sigpipe产生的原因
简单讲来就是向连接已经断开的socket发送数据。更具体点就是:当A端close一个连接时,若B端接着发数据。根据TCP协议的规定,B端会收到一个RST响应,进程对接收了RST的socket发送数据时,内核会发出一个SIGPIPE信号给B端进程。

2. 工作中为啥没有遇到
虽然这问题很普遍,但是后台开发的工作中确很少遇到。仔细想了下,大致有3点原因:
1) 我们后台不是直接暴露给外部用户,通常是给内部系统调用(cgi或者其他后端),因此他们使用套接字收发消息都是很规范的。
2) 框架使用了zmq这样网络传输组件,屏蔽了tcp链接的这些细节。
3) server大多是req – rep模式,因此没收到包不会发包。

3. 解决办法
前人已经做了很多工作,这里汇总下。
1) 忽略SIGPIPE(很多os都支持),mongrel2用这种方式
2) 设置socket选项 SO_NOSIGPIPE(mac支持,linux不支持)
3) api中带上标志,send/sendto的flags加上MSG_NOSIGNAL(linux支持)

tcpcopy-0.9.6静态编译

1. 背景

受限于公司机器环境,ip_queue特性需要重编内核才能支持,这个不是我自己的机器,简直不可预期,故只能走nfqueue来支持。同时为了保证不污染环境,所有安装不能侵入系统库,所有安装不能修改非自己用户的数据。

2. 解决过程
为此初略有3种方案:
1) tcpcopy依赖动态库 – 为了编译通过
2) tcpcopy默认从当前目录找动态库 – 为了运行更方便
3) tcpcopy变成链静态库 – 为了完美解决工具类可执行文件的形态
1)可以很快搞定,2)需要链接时加上参数-Wl,-rpath,需要改编译配置,3)需要修改编译配置文件,由于预估2)3)的工作量差不多,就直接去研究3)了。

当将所直接依赖的libnetfilter_queue,间接依赖的libnfnetlink,libmnl都编译成静态库时,在tcpcopy的configure阶段就会出现”checking for nfqueue directory… configure: error: nfqueue required. Install libnetfilter_queue(download it from http://www.netfilter.org/projects/libnetfilter_queue/index.html) or specify its path using –with-nfqueue=/dir/”。于是去看了下configure.ac文件,进入enable_nfqueue这一段逻辑里面,发现AC_TRY_LINK这里会试着去编译一小段代码检查下nfqueue库是否可用,估计是这里出了问题,强制加上nfqueue_linked=yes。然后autoconf,编译则通过。后面细致的想了下出现这种情况的原因,应该是链接动态库libnetfilter_queue,里面已经包含了libmnl和libnfnetlink的动态库位置,而链接静态库时,就必须单独指定这两个库,否则就会失败。

3.附编译脚本

 

关于程序入口

今天研讨会涉及到了程序入口的处理,之前在libtask源码中看过这么一个案例,于是顺便做了点测试。

1. 程序默认入口
默认的真实入口是_start,顺序是__start -> __libc_start_main -> main
入口符号__start在/usr/lib/crt1.o中,__libc_start_main在libc的动态库中。

2. 自定义程序入口
a) 写一个程序来做测试

b) 编译执行
[gcc|g++] -nostartfiles -e mymain[1|2] forge_main.[c|cpp]

c) 结果分析
以mymain1为入口,return只是表示当前函数调用结束,并没有做进程退出的清理工作,所以core了,而正常的main入口则是会交由libc做清理工作。mymain2由于显示的调用了exit,会主动去完成进程退出的相关工作,因此正常结束了。另外,g++会对符号加扰,所以加上extern “C”声明,没有这个编译时会出警告且入口永远都为源文件中第一个函数(符号)。

3. 隐藏默认入口,暴露自定义入口
既然系统上的所有程序都实际上都是伪入口,那么对于应用程序就更加可以自由处理入口了。这里以libtask为例子。

main -> taskmainstart -> taskmain
libtask就是将默认入口main封装到了框架内部(动态库,静态库都可),这样既可以完成不方便暴露或者繁琐的初始化工作,又可以简化入口,同时又向应用程序暴露了taskmain这一接口。

sphinx新特性展望

sphinx开发团队的速度着实让人害怕,最近基本都是都是2个月左右就一个正式版。目前2.0.8应该算是用得最多的一个版本,原因我想有两点:
1). 2.0.8到2.0.9间隔较长,差不多4个月,使用sphinx的估计已经妥妥的用在了自己生成环境上,再作变动成本会较高
2). 2.0.8之后的release版本新特性还没有达到不能拒绝的程度。
  昨天在群里面有人聊到了2.2.1 beta版的新特性,然后偶就再一次到官网去琢磨了下。以前的版本特性更多的体现在对实时索引的优化和对json支持的优化,2.2.1真的有几项feature让人不能拒绝。

1.  Integration with RLP for Chinese segmentation.
内建中文分词。在这之前我们只有两种选择:
1). 先用分词工具分好词再去创建索引;
2).使用国内coreseek或者sfc之类的修改版在索引时分词
第一种方法处理起来就麻烦一些,属于sphinx干不了时的变通方法,相当于把一段文本分成有固定分隔符的文本,然后索引时配置以这个分隔符来区分汉字建立索引。
第二种方法用起来木有问题,但是完全跟不上官方的节奏,神马新特性更是无从谈起。
这个特性绝对无法拒绝。

2. New data source for indexing CSV/TSV files in the same fashion as for XML files.
支持csv/tsv这些文件了。好处是神马?就是你现在可以用很简单的明文了。在这之前我们能做的不多:基本只有coreseek能用万能的python来聚合各种数据源,sphinx官方就只有xml和db,xml虽然是明文,但按这种结构组织还不是那么方便。现在你可以用linux下各种shell命令工具来聚合数据,这里又让人有了很大的想象空间。

3. GEODIST() now uses (by default) a lighter algorithm which is based on Polar Flat-Earth formula.  It’s now possible to specify the input/output unit type as well as the calculation algorithm to be used.
以前geodist这个函数是很慢的,因为他使劲的算准确,相比 Polar Flat-Earth formula这个要慢很多。之前要用到geo的计算,基本上都是把Polar Flat-Earth formula算式写到sql语句中。还有就是老的单位是弧度,所以以前算了geo真是要写很多”废话”,现在支持各种单位,一下爽多了。

相关姊妹篇: 关于sphinx – 我有话要说

opensuse 13.1支持ralink的一系列无线网卡芯片了

万恶的普联 tp-link TL-WDN3321 300M双频无线USB网卡 (TL-WDN3321 1.0)终于被linux内核原生支持了,终于不用摆脱官方驱动的各种不稳定掉线的节奏了。这款usb无线网卡采用的是ralink公司的RT5572芯片,得益于新内核的无线网卡驱动,opensuse 13.1 总算是成功支撑了。

opensuse 13.1下网络已经能够识别出来该设备了。lsusb 可以看到识别出ralink了,同时lsmod也可以看到类似rt2x00usb等等的模块了。

 

core与不core你决定

常在河边走哪能不湿鞋,进程crash是常有的事儿,不过工作中与coredump的交集非常固定,我大致将其分为两种:一种是core得太普通,另一种是core得没证据。前者出现的场景通常是“你咋整的,程序怎么又core了”,泛指core得莫名其妙;后者出现的场景大致是“你咋搞的,居然不知道core在哪哈儿“,泛指core得没凭没据。下面来看看如何core得高大上。

1. coredump的出现必须满足3个条件
a) 产生信号
linux下部分信号默认是即终止程序又产生core(也有只终止程序不产生coredump的),SIGSEGV,SIGILL,SIGFPE,SIGABRT,SIGBUS,SIGTERM这几个是常使用的(还有几个也会产生core,硬件信号等等,没见出现过忽略掉),kill -l可查看。
b) 进程环境允许core
这个最容易遗忘,进程执行环境需要保证corefile的设置值有足够大小,该值默认为0,通常设为unlimited。
c) 拥有写core文件的权限且磁盘有空间
这个通常都满足,不赘述。

2. coredump分为主动或被动
a) 被动core掉 – 程序自己执行到非法操作时,会产生信号,触发信号默认行为。
b) 主动core掉 – 自己发送信号触发这个逻辑或者利用gcore强制生成core。特殊场景会需要(如果仅仅只是查看当前堆栈信息,pstack和gdb的attach + bt可以满足),例如做测试时发送SIGQUIT给进程,gdb的generate-core-file指令(gdb –batch –pid=$1 -ex generate-core-file),gcore [pid] -o filename这种方式不需要重启server。

3.生产环境中如何强制保留现场
其实有木有core文件倒还其次,关键是有现场信息,知道程序哪个位置出错了。
a)  如果你跟我一样用glog,那这个就好办了。
google::InstallFailureSignalHandler() 接管会产生core文件的信号函数,然后用InstallFailureWriter(&func)自定义core时的堆栈信息处理,默认这些信息是打印到终端,而通常服务端程序都是以deamon形式运行,已经关闭了终端输出。所以同城将func定义将堆栈信息打印到日志文件。形如

b) 如果你还是一个不满足于glog堆栈信息的人,你或许还需要这个
为什么这么说,因为glog给出的堆栈信息只有地址信息木有行号,所以需要做些改变。
backtracebacktrace_symbols可以获取到堆栈信息,然后需要依靠libbfd或者libdwarf去转换进程的地址信息到函数和行号,后者真心复杂,遂放弃。反正程序都要core了也不在乎退出时的性能问题了,于是配合glog的能力用sscanf匹配出栈帧中的地址信息,然后用addr2line去转换。

4. 如果你无论如何都要生成core文件
当然生成core文件这种机制也是有缺点的,因为占空间且不实时。但是人们熟悉这玩意儿,并且也简单。无奈只有祭出神器 – google-coredumper。这么给你说吧,你想啥时候写core文件,他就啥时候写,不是必须要设置ulimit选项,不是必须要信号触发,当然你还是要有磁盘空间的(需要注意googlecode上的最新版本有部分编译错误需要自己修改下)。常见的场景,例如我要在程序自发core信号产生时强制生成core文件,只需绑定以下信号函数即可:

关于sphinx – 我有话要说

让子弹飞里面是唱着歌吃着火锅,我这是听着歌写着博客,细细想来,生活本该如此…
前段时间被工作搞得欲仙欲死,终于偷来一点闲暇看看自己究竟整了些啥玩意儿。
此处略去几万字儿,直接进入正题 – sphinx

1. 已经有了数据库的全文检索,为什么需要单独的全文检索引擎
a). 两者相辅相成。全文检索是数据库的有力补充,全文检索并不能替代数据库在应用系统中的作用。当应用系统的数据以大量的文本信息为主时,采用全文检索技术可以极大的提升应用系统的价值。
b). 两者能力有差别。全文检索引擎比mysql做检索更灵活,性能更高。在数据量到一定量级之后全文检索引擎性能高过mysql很多(貌似是10w,没精确试过),另外,前者提供更的匹配模式,评分标准等检索功能。
c). 两者定位有差别。全文搜索引擎专注于搜索,指标是海量数据的查询和索引建立;数据库更加专注于数据本身,对业务所需要的高并发和高吞吐不那么关注。

2. 为什么是sphinx
a). 在全文检索领域,入法眼的就3个,lucene,sphinx,xapian。
基于java的lucene最火,有个基于C的分支;xapian基于C++开发,有成熟的迅搜(xunsearch)支持;基于C++的sphinx,有coreseek和sphinx-for-chinese的二次修改版。语言因素加上不想走分支项目,滤掉lucene,剩下xapian和sphinx。
b). sphinx的高性能(搜索都很费时)高过xapian再加上xapian的api头文件让我凌乱,最终扑向sphinx。

3. sphinx他不是搜索引擎
貌似还有很多人以为sphinx就是搜索引擎了,但我更愿意叫他检索引擎,他的关键能力还在于关键词的匹配上即文本检索。而搜索引擎除了检索系统外还包括更多类似数据挖掘,缓存,相关性预测,去重,纠错以及更复杂的数学模型等等。当然围绕sphinx,我们是可以搭建一套搜索引擎。

4. sphinx提供的接口
a). 传统的api模式(请求发向searchd,searchd回复结果,有原生的和第三方的)
b). sphinxQL(类mysql接口,使用标准mysql的客户端库连接,然后用sphinx扩展的关键字和能力进行检索)
c). sphinxSE(编译进mysql的插件引擎,了解不多)http://sphinxsearch.com/blog/2013/07/23/from-api-to-sphinxql-and-back-again/指出api模式由于打包了所有搜索选项等数据到二进制数据中,所以有额外的开销,且第三方api通常不会随server版本实时更新;sphinxQL性能理论上更改高,且提供一些特殊信息(负载状态,统计特性等)。但目前api方式仍被广泛应用,因为简单且最广为人知。

5. 官方指标
索引建立速度:单核10~15MB/s
搜索速度:单核700 queries/s(100W记录,1.2GB数据,3GHz)
已知应用:分布式索引有30亿,峰值查询5000W queries/day

6. 中文的全文检索(中文分词)
a). 生在天朝要解决的东西很多,中文分词就是其中一项。sphinx本身提供中文的检索支持,但不支持正统的中文词切分,因为中文不像英文有明确的词分隔符。但提供一元分词,即将中文词一个一个的切分。
b). 分词模型常见的,正向/逆向/双向最大匹配,基于词频采集,还有高级点的歧义消除模型,机器学习模型等。不开源的中科院分词前辈咱们不敢奢望,只剩下scws和libmmseg选择,单从api简洁性上scws更胜一筹(friso也值得研究下)。在全文检索中总共有2个地方可能用到分词,建立索引时和搜索时,这里就诞生出两种模式:

  • 索引时不分词(一元切分) + 搜索时分词
    好处:搜索数据全;词库更改无需重建索引;
    坏处:索引数据较大,间接影响搜索性能,特别是在数据量本身很大的情况下。
  • 索引时分词 + 搜索时分词
    好处:索引数据小,搜索性能更高;
    坏处:需要索引阶段支持分词;词库变动需重建索引。

实际操作过程中,第二种方案可以通过预先分词以固定分隔符分割的方式预处理,然后检索引擎可按此分隔符进行正确区分;实际运用中,第二种方案由于受限于词库完整性,不可避免会丢掉某些不常见的词进而导致搜索不到。

=======================sphinx进阶=======================
7. 汇总下sphinx搜索本身的能力:过滤(索引源过滤,搜索时过滤),匹配(并,交,相似等),排序(默认排序因子和算法,自定义排序因子,各种排序因子的组合), 组合(子查询(有优化))等。过滤属于边缘属性,重点介绍匹配和评价,因为搜索结果除了数据源的因素外这两个是影响最大的。

a). 匹配模式
sphinx一路走来有过很多匹配模式,最新最被推荐也是功能最强大的是SPH_MATCH_EXTENDED。中文搜索下提供的能力包括但不限于:与,有限制的非,或,字段限制匹配,有序匹配,有限近似匹配,阀值匹配等

  • “A B” 等同于 “A & B” ,与匹配,必须同时存在A和B才会匹配,”A”即词组匹配,对于中文限定了双引号时,表示必须精确匹配到A。
  •  “B -A” ,非匹配,匹配B且不匹配A,有一个限制,就是-A必须不涉及到所有文档,这里因为匹配了B,所以不满足这个限制。
  •  “B | A”,或匹配,匹配B或A。
  •  “@(f1,f2) A”,@!(f1,f2) A”,字段限制匹配,限制只在f1和f2两个字段上匹配A,默认是sphinx配置里面的所有field_string;后者取非,限定只在除f1,f2的字段上进行匹配。
  • “A << B”,有序匹配,匹配A和B,且顺序上A必须在B的前面。
  • “A B”~3,有限近似匹配,即有一个少于5个词(不包括5)的串,包含了A和B,实际上限定了词之间的距离。
  • “A B C”/2,阀值匹配,至少匹配其中两个。

上面是最常用的,sphinx官方文档有很详尽描述,使用时需注意运算符优先级。匹配模式只保证能匹配出结果,这只是搜索的第一步,有时排序可能更加重要,因为他跟搜索质量关系更加密切。

b). 评价模型
评价模型通俗的讲就是打分标准,直接影响结果排序,排序的对象就是评价模型所产生的因子。sphinx内建评分器主要有两个因子: phrase proximity, keyword frequencies;前者是词组评分(精准匹配时获得最大分),后者是统计学评分(遵循基于词频的BM25)。这里只考虑SPH_MATCH_EXTENDED,其默认的ranker是SPH_RANK_PROXIMITY_BM25(其他的匹配模式分别对应不同的默认ranker)。
SPH_RANK_PROXIMITY_BM25 = sum(lcs*user_weight)*1000+bm25
SPH_RANK_BM25 = bm25
SPH_RANK_NONE = 1
SPH_RANK_WORDCOUNT = sum(hit_count*user_weight)
SPH_RANK_PROXIMITY = sum(lcs*user_weight)
SPH_RANK_MATCHANY = sum((word_count+(lcs-1)*max_lcs)*user_weight)
SPH_RANK_FIELDMASK = field_mask
SPH_RANK_SPH04 = sum((4*lcs+2*(min_hit_pos==1)+exact_hit)*user_weight)*1000+bm25
SPH_RANK_EXPR = ….

评价模型的灵活性来自于可以自定义(SPH_RANK_EXPR),提供了以下因子:

  • bm25 相关性评分,值越大相关性就越大
  • max_lcs 词组匹配所能获得的最大评分,代表sum(lcs*user_weight)能取到的最大值
  • field_mask 标记已匹配到的字段的掩码
  • query_word_count 查询词的个数(唯一的个数,重复的不算)
  • doc_word_count 整个文档中关键词的个数
  • lcs 最长公共子串,记录匹配到的子串个数
  • user_weight 字段的权重(用户设置的,默认都均等)
  • hit_count 在所匹配到的文档中的关键词命中个数(非唯一,包含所有字段)
  • word_count 在所匹配到的文档中的任意字段里匹配到的唯一关键词的个数(唯一,单个字段)
  • tf_idf 词频和逆文档频率乘积求和(0代表所有文档都出现,1代表唯一关键词仅出现在一个文档中)
  • min_hit_pos 第一个匹配的关键词所出现的位置
  • exact_hit 在当前匹配文档的某一个字段里是否是精确匹配
  • min_best_span_pos 目前还木有搞明白用法
  • sum 目前是按匹配字段求和的,即对每个匹配字段表达式运算后求和

评价模型直接影响内部属性的值,间接影响排序,根据这些因子可以定制自己的评分标准,然后通过sphinx内建属性@weight表现出来。

c). 排序模型

  • SPH_SORT_RELEVANCE模式 = @weight desc, @id asc. 按相关度降序排列(最好的匹配排在最前面)
  • SPH_SORT_ATTR_DESC模式 = attribute asc, @weight desc, @id asc. 按属性降序排列 (属性值越大的越是排在前面)
  • SPH_SORT_ATTR_ASC模式 = attribute desc, @weight desc, @id asc. 按属性升序排列(属性值越小的越是排在前面)
  • SPH_SORT_TIME_SEGMENTS模式, 先按时间段(最近一小时/天/周/月)降序,再按相关度降序
  • SPH_SORT_EXTENDED模式, 按一种类似SQL的方式将列组合起来,升序或降序排列。
  • SPH_SORT_EXPR模式,按某个算术表达式排序。

评价模型的因子在排序模型中抽象成了内部属性@weight(@rank,@relevance),最主流的还是SPH_SORT_EXTENDED模式,绝大部分排序场景都可以用其满足,少部分新闻类数据需要SPH_SORT_TIME_SEGMENTS模式,如果实在不能满足通过SPH_SORT_EXPR模式定制能完成效果。

  • 内建属性排序: @weight,@id,@random。例如: @weight desc。
  • 另外,还可以通过自己source中定义或者api重写select的”虚拟字段”来参与排序; 例如: f_gid desc (f_gid可能是f_id as f_gid)。
  • 当然还可以使用一些运算的组合来作为排序因子。 f_top_weight desc (f_type = 1 as f_top_weight)。
  • 用SPH_SORT_EXPR定制自己的排序模式,聚合成一个因子进行排序。

d). 数据组合

  • 一条查询命令中按顺序组合了多类数据(按类型排序即可)
  • sphinx内部通过子查询,返回类各自的数据到结果集中。
  • sphinx外部,应用层通过多次查询,来取得类各自的数据到结果集中。

第一种灵活性最差,但是最简单,逻辑单一时使用不错。
第二种灵活性适中,能够应付较为复杂的逻辑,且sphinx内部对子查询有优化,理论性能较高。
第三种最为灵活,但时耗是个严峻的考验。

e). 其他能力
sphinx除了基本的查询数据的能力之外,当然还有很多相关能力,包括但不限于:

  • geo能力,能够根据经纬度进行近似计算距离 (http://sphinxsearch.com/blog/2013/07/02/geo-distances-with-sphinx/)。
  • 分布式能力,这个是sphinx扩展上的基本能力。
  • 词形映射,可以映射词A到词B(查询A时相当于是查询B)。
  • 结果分组聚类,有提供内部属性来获取组内匹配数等能力。
  • 自定义函数(https://github.com/minisotm/sphinx-hamming-distance-plugin),目前可以在mysql类的模式中使用,自定义算法和因子时可以采取该方式。

8. 实际运用
a). 关键词搜索
这个是最基本的,就不阐述了。常用来用来搭建论坛和博客的内建检索引擎。
b). 关联搜索(http://sphinxsearch.com/blog/2013/05/21/simple-autocomplete-and-correction-suggestion/)
上面是官方博客的一篇文章,简单介绍了搜索框搜索词建议的实现。即完全匹配的相关性条目进行关联展示。体验就像百度搜索框的自动补全建议。
c). 相关词推荐
这个跟关联搜索类似,但是通常出现在相关词推荐中。简单的实现就是提取自己库的关键词作为一个数据源,然后在其中搜索
与查询词相关的词。体验就像百度搜索页底部的”相关搜索”区域。
d). 做缓存
当然理论上是不需要用sphinx来做缓存的,因为数据源通常是数据库,再不进行检索的情况下,从sphinx取数据和从db取数据性能应该说是相近的。
但db通常会有很多并发任务和操作,而sphinx是读静态文件。这里可以用来做带条件的数据读取。
e). 日志分析(http://sphinxsearch.com/blog/2013/07/31/logzilla-big-data-log-analysis-with-sphinx/)
海量日志的查询也是一件蛋疼的事情,至少从db中检索是贼慢的。

利用这个还可以做出很多有意思的玩意儿,例如词的分类建议(制定好类目,然后特定类目下扩充行业分类词库,对查询词进行搜索,然后给出匹配到的类目)等等。

9. 常见的架构
a). 数据量少时 => 单机全量索引 多机部署
全量索引时有效控制文档数及检索文本量。单机包含所有数据,机器用来做负载均衡和容灾。
b). 数据量较大(更新频繁) => 单机全量+增量索引+定时合并 多机部署
全量+增量,有多种逻辑,例如有按文档划分的,也有按其他字段,例如更新时间划分的。需要注意的是文档相同时,增量索引中的数据会覆盖主文档数据。单机包含所有数据,机器用来做负载均衡和容灾。
c). 数据量超大 => 划分数据区+增量索引+定时合并 多机分布式部署
划分数据区,可以按文档,也可以按业务来划分。单机放部分文档或某(几)类业务数据,包含部分数据,其他数据需要从其他节点获取,机器用来做分布式数据存储及负载均衡和容灾。

注:
1). 区分主索引和增量索引是为了更快的重建索引应对数据变更。
索引的重建也面临一些问题:最朴素的处理方式就是定时重建,当然这是个很耗cpu的工作。时间间隔长了,达不到准实时的效果,时间间隔短了,cpu利用率间隔性陡增也是非常影响业务的。目前想到有几种途径可有效缓解,部分方法急需在业务中验证:

  • 在其他机器上来重建索引,然后将数据同步到运营机,(.new的新索引后缀,加上手动发出一条sighup信号),这个有效降低cpu占用,当然这里只能是增量索引,否则大量的数据复制也会很耗时太长。
  • 异步事件通知,目前有较多的分布式消息组件,数据的更新可以通过一条异步消息发出,运营机上可以阻塞式的监听消息,若真有数据更新再重建。
  • 主动式的发现,短时间间隔查询db数据是否有更新(这个很轻量级),若更新再重建。

2). 划分数据区主要是出于系统优化的考虑(文档越大查询越慢),例如单个索引超过百万级通常就进行拆分(单个索引的理论文档数是亿级别的),还有部分原因是为了便于业务区分。这类似于理论上数据分布 – “复制”和“分片”,前者用于把所有数据放置在所有节点,后者每个节点存放部分数据。
3). 如果db表并发任务较多,myisam引擎有时会出现死锁或阻塞时间过长,这里有几种解决办法:

  • sphinx重建索引时,使用步进式的拉取方法(sql_query_range,sql_range_step)。但这会让索引建立时间被延长(有时会很长)。
  • 将sphinx的数据源表独立出来供sphin专用,避免重建索引时可能被阻塞的情况。

10. 质量评价标准
搜索的质量评价主观性太强,难以评价,目前主要有三种方式:
a). 精确率/召回率
精确率 = 本次搜索结果中相关文档数 / 本次搜索结果文档总数。
召回率 = 本次搜索结果中相关文档 / 整个文档集合中所有相关文档数。
对搜索引擎和用户而言,召回率太难以估计,没人能翻完所有的相关文档,因此精确率显得更有实际意义。
b). P@n
p@n = 搜索结果中的top(n)中相关文档数 / n
该指标更关注搜索结果排名最靠前的n个文档的结果质量。
c). AP/MAP
AP/MAP = 搜索结果中相关文档的实际位置和 / 搜索结果中相关文档的理想位置和
该指标用以衡量单次或多次查询的平均准确率,理想结果是搜索结果中所有相关文档n都排在top(n),此时AP/MAP = 1。

11. sphinx的roadmap特性和未来
a). 实时索引能力越来越强,性能也越来越好,未来可能作为主推的一种能力。
b). 富媒体的支持,新版本直接能解析json对象的元素,最近一个版本性能也有大幅提升。
作者对sphinx的愿望是把sphinx做到mysql里面去,作为mysql预置的一个引擎。

【参考文档】
官方博客和论坛是目前最好的资料源
国内最大的sphinx应用及论坛
这就是搜索引擎-核心技术详解(张俊林 著)
Sphinx之Coreseek、Sphinx-for-chinaese、Sphinx+Scws 评测
Sphinx + PHP + scws构建MySQL准实时分布式全文检索
Sphinx多线程分布式搜索
基于Sphinx构建准实时更新的分布式通用搜索引擎平台
基于Sphinx+MySQL的千万级数据全文检索(搜索引擎)架构设计
亿级数据的高并发通用搜索引擎架构设计
很好的专业博客
sphinx的各种第三方api,各种插件和管理工具
sphinx charset table