几种常见的基于Lucene的开源搜索解决方案对比

一  直接使用 Lucene  ( http://lucene.apache.org )

  1. 说明:Lucene 是一个 JAVA 搜索类库,它本身并不是一个完整的解决方案,需要额外的开发工作
  2. 优点:成熟的解决方案,有很多的成功案例。apache 顶级项目,正在持续快速的进步。庞大而活跃的开发社区,大量的开发人员。它只是一个类库,有足够的定制和优化空间:经过简单定制,就可以满足绝大部分常见的需求;经过优化,可以支持 10亿+ 量级的搜索。
  3. 缺点:需要额外的开发工作。所有的扩展,分布式,可靠性等都需要自己实现;非实时,从建索引到可以搜索中间有一个时间延迟,而当前的“近实时”(Lucene Near Real Time search)搜索方案的可扩展性有待进一步完善

二  Solr  ( http://lucene.apache.org/solr/ )

  1. 说明:基于 Lucene 的企业级搜索的开箱即用的解决方案
  2. 优点:比较成熟的解决方案,也有很多的成功案例。Lucene 子项目,实现了大部分常见的搜索功能需求,包括 facet 搜索(搜索结果分类过滤)等。
  3. 缺点:可定制性比 Lucene 要差,一些不常见的需求,定制的难度比直接在 Lucene 上做要大的多。性能上,由于 Solr 的建索引和搜索是同一个进程,耦合度比较高,对于性能调优有一定的影响。

三 Katta ( http://katta.sourceforge.net/ )

  1. 说明:基于 Lucene 的,支持分布式,可扩展,具有容错功能,准实时的搜索方案。
  2. 优点:开箱即用,可以与 Hadoop 配合实现分布式。具备扩展和容错机制。
  3. 缺点:只是搜索方案,建索引部分还是需要自己实现。在搜索功能上,只实现了最基本的需求。成功案例较少,项目的成熟度稍微差一些。因为需要支持分布式,对于一些复杂的查询需求,定制的难度会比较大。

四 Hadoop contrib/index ( http://svn.apache.org/repos/asf/hadoop/mapreduce/trunk/src/contrib/index/README )

  1. 说明:Map/Reduce 模式的,分布式建索引方案,可以跟 Katta 配合使用。
  2. 优点:分布式建索引,具备可扩展性。
  3. 缺点:只是建索引方案,不包括搜索实现。工作在批处理模式,对实时搜索的支持不佳。

五 LinkedIn 的开源方案 ( http://sna-projects.com/ )

  1. 说明:基于 Lucene 的一系列解决方案,包括 准实时搜索 zoie ,facet 搜索实现 bobo ,机器学习算法 decomposer ,摘要存储库 krati ,数据库模式包装 sensei 等等
  2. 优点:经过验证的解决方案,支持分布式,可扩展,丰富的功能实现
  3. 缺点:与 linkedin 公司的联系太紧密,可定制性比较差

六 ElasticSearch  ( http://www.elasticsearch.com/ )

  1. 说明:基于 Lucene 的,分布式,云端,提供 rest 接口的搜索解决方案
  2. 优点:开箱即用,分布式,rest 接口,支持云端调用
  3. 缺点:一个新的项目,没有经过很多的验证。(只有一个人在开发?)分片的数目不能动态调整,只能在初始化索引的时候指定(跟 HBase 不一样的地方)

七 Lucandra ( https://github.com/tjake/Lucandra )

  1. 说明:基于 Lucene,索引存在 cassandra 数据库中
  2. 优点:参考 cassandra 的优点
  3. 缺点:参考 cassandra 的缺点。另外,这只是一个 demo,没有经过大量验证

八 HBasene ( https://github.com/akkumar/hbasene )

  1. 说明:基于 Lucene,索引存在 HBase 数据库中
  2. 优点:参考 HBase 的优点
  3. 缺点:参考 HBase 的缺点。另外,在实现中,lucene terms 是存成行,但每个 term 对应的 posting lists 是以列的方式存储的。随着单个 term 的 posting lists 的增大,查询时的速度受到的影响会非常大

欢迎补充!请到 几种常见的基于Lucene的开源搜索解决方案对比 原文处参与讨论。

参考材料: http://mail-archives.apache.org/mod_mbox/hbase-user/201006.mbox/%3C149150.78881.qm@web50304.mail.re2.yahoo.com%3E

翻译:Facebook的新实时信息系统

您可能已经在某个地方了解到 Facebook 已经推出了新的整合了电子邮件,即时通讯,手机短信,Facebook 站内信的 社会收件箱 的消息。所有的这一切加起来,他们每个月需要存储超过1350亿条消息。 他们用什么存储这些消息呢?Facebook的Kannan Muthukkaruppan给出了一个让人惊讶的回答: 消息系统的底层技术HBase HBase 击败了MySQL,Cassandra,和其他几种备选方案。

为什么惊讶? Facebook 开发了 Cassandra,最初的目的就是用来搭建类似收件箱的应用,但他们发现 Cassandra 的最终一致性模型与新的实时消息系统的要求不相匹配。 Facebook也拥有广泛的 MySQL基础设施,但他们发现当数据集和索引快速增长时,mysql 的性能会受到很大的影响。 他们也可以选择建立自己开发一套系统,但他们没有那么做,而是选择了 HBase。

HBase的是一种 在大数据量下仍支持快速的行级别的更新的可扩展表存储。这正是消息系统所需要的。HBase 是建立在 BigTable的 模型上的,基于列的 key-value 存储。 它擅长于根据 key 获取一行或多行数据,或者扫描数据区间,以及过滤操作。 这也是消息系统需要的 HBase 不支持复杂的查询。 复杂的查询通常由分析工具来完成,比如 Hive,Hive 也是 Facebook 开发的,用来分析他们 PB 级别的数据仓库。Hbase 和 Hive 一样,都使用 Hadoop的文件系统,HDFS。

Facebook 选择 Hbase,是因为他们 分析了消息系统的使用情况,并找出了系统真正需要的特性 他们需要的是一个可以处理两种模式类型的数据的系统:

  1. 一个经常被访问的,不稳定的,小的临时数据集
  2. 一个不断增长的,很少访问的,大的数据集

就这样。 你读过了当前收件箱中的消息,然后很少,如果有的话,会再去看它。这两种模式差别很大,所以人们可能需要两个不同的系统。恰巧的是, HBase 能很好的支持两者。 目前尚不清楚他们是如何处理通用搜索功能的,这是 HBase 的弱点,尽管它能够整合 多种搜索系统

他们的系统的一些关键点:

  • HBase:
    • 比Cassandra简单一致性模型。
    • 对于他们的数据模式来说,HBase 具有很好的扩展性和性能。
    • 功能丰富:自动负载平衡和故障转移,支持压缩,每个服务器上可部署多个分片等
    • HDFS,HBase 使用的文件系统,支持复制,端到端的校验,和自动平衡。
    • Facebook 的运维小组有很多使用HDFS的经验,因为 Facebook 是 Hadoop 的大用户,而 Hadoop 也使用分布式文件系统 HDFS。
  • 使用 Haystack 存储附件。
  • 全新开发的自定义应用服务器,以服务来自不同源的大量消息流入。
  • 基于 ZooKeeper 的用户自动发现服务
  • 提供基础服务:电子邮件帐户验证,朋友关系,个性定义,和交付路由(消息通过聊天还是通过短信发送?)。
  • 保持他们小团队做大事情的传统,目前 15个工程师一年发布20个基础服务
  • Facebook 不会在单一的数据库平台上做标准化,他们使用不同的平台承担不同的任务。

我很惊喜的看到,Facebook 选择 HBase,是因为他们有很多使用 HDFS/ Hadoop/ Hive 的经验。 借着作为另一个很流行的产品的好搭档的方式,来加入它的生态系统,这是任何一个产品都有的梦想 这是 HBase 已经取得的成就。 鉴于 HBase 在持久存储方面涵盖的特性: 实时,分布,线性可扩展,健壮,BigData,开放源码,键值存储,面向列存储, 它将会变得越来越受欢迎,特别是在 Facebook 宣布了他们的选择以后。

原文见:http://highscalability.com/blog/2010/11/16/facebooks-new-real-time-messaging-system-hbase-to-store-135.html

转:基于lucene实现自己的推荐引擎

采用基于数据挖掘的算法来实现推荐引擎是各大电子商务网站、SNS社区最为常用的方法,推荐引擎常用Content-Based 推荐算法及协同过滤算法(Item-Based 、User-based)。但从实际应用来看,对于大部分中小型企业来说,要在电子商务系统完整采用以上算法还有很大的难度。

1、常用推荐引擎算法问题

    1)、相对成熟、完整、现成的开源解决方案较少
    粗略分来,目前与数据挖掘及推荐引擎相关的开源项目主要有如下几类:
    数据挖掘相关:主要包括Weka、R-Project、Knime、RapidMiner、Orange 等
    文本挖掘相关:主要包括OpenNLP、LingPipe、FreeLing、GATE 、Carrot2 等,具体可以参考LingPipe’s Competition
    推荐引擎相关:主要包括Apache Mahout、Duine オンライン カジノ framework、Singular Value Decomposition (SVD) ,其他包可以参考Open Source Collaborative Filtering Written in Java
    搜索引擎相关:Lucene、Solr、Sphinx、Hibernate Search等
    2)、常用推荐引擎算法相对复杂,入门门槛较高
    3)、常用推荐引擎算法性能较低,并不适合海量数据挖掘
    以上这些包或算法,除了Lucene/Sor相对成熟外,大部分都还处于学术研究使用,并不能直接应用于互联网规模的数据挖掘及推荐引擎引擎使用。

    2、采用Lucene实现推荐引擎的优势

      对很多众多的中小型网站而言,由于开发能力有限,如果有能够集成了搜索、推荐一体化的解决方案,这样的方案肯定大受欢迎。采用Lucene来实现推荐引擎具有如下优势:
      1)、Lucene 入门门槛较低,大部分网站的站内搜索都采用了Lucene
      2)、相对于协同过滤算法,Lucene性能较高
      3)、Lucene对Text Mining、相似度计算等相关算法有很多现成方案
      在开源的项目中,Mahout或者Duine Framework用于推荐引擎是相对完整的方案,尤其是Mahout 核心利用了Lucene,因此其架构很值得借鉴。只不过Mahout目前功能还不是很完整,直接用其实现电子商务网站的推荐引擎尚不是很成熟。只不过从Mahout实现可以看出采用Lucene实现推荐引擎是一种可行方案。

      3、采用Lucene实现推荐引擎需要解决的核心问题

        Lucene对于Text Mining较为擅长,在contrib包中提供了MoreLikeThis功能,可以较为容易实现Content-Based的推荐,但对于涉及用户协同过滤行为的结果(所谓的Relevance Feedback),Lucene目前并没有好的解决方案。需要在Lucene中内容相似算法中加入用户协同过滤行为对因素,将用户协同过滤行为结果转化为Lucene所支持的模型。

        4、推荐引擎的数据源

          电子商务网站与推荐引擎相关典型的行为:

        1. 购买本商品的顾客还买过
        2. 浏览本商品的顾客还看过
        3. 浏览更多类似商品
        4. 喜欢此商品的人还喜欢
        5. 用户对此商品的平均打分
        6. 因此基于Lucene实现推荐引擎主要要处理如下两大类的数据
          1)、内容相似度
          例如:商品名称、作者/译者/制造商、商品类别、简介、评论、用户标签、系统标签
          2)、用户协同行为相似度
          例如:打标签、购买商品、点击流、搜索、推荐、收藏、打分、写评论、问答、页面停留时间、所在群组等等

          5、实现方案

            5.1、内容相似度 基于Lucene MoreLikeThis实现即可。
            5.2、对用户协同行为的处理
            1)、用户每一次协同行为都使用lucene来进行索引,每次行为一条记录
            2)、索引记录中包含如下重要信息:
            商品名、商品id、商品类别、商品简介、标签等重要特征值、用户关联行为的其他商品的特征元素、商品缩略图地址、协同行为类型(购买、点击、收藏、评分等)、Boost值(各协同行为在setBoost时候的权重值)
            3)、对评分、收藏、点击等协同行为以商品特征值(标签、标题、概要信息)来表征
            4)、不同的协同行为类型(例如购买、评分、点击)设置不同的值setBoost
            5)、搜索时候采用Lucene MoreLikeThis算法,将用户协同转化为内容相似度
            以上方案只是基于Lucene来实现推荐引擎最为简单的实现方案,方案的准确度及细化方案以后再细说。
            更为精细的实现,可以参考Mahout的算法实现来优化。

            挑战邮箱搜索(续一)

            接上 挑战邮箱搜索

            搜索速度的问题还没找到好的办法,其他的问题又来了。

            首先是排序的问题。Lucene 默认的排序考虑了很多因素,套用到邮箱搜索的结果里,很多时候反而显得结果很混乱,不同文件夹,不同时间,不同主题,不同发件人的邮件混在一起,更严重的是,已读邮件和未读邮件混在一起了:已读和未读邮件的 css 样式是不一样的,混在一起的结果就是,界面看起来非常混乱。一方面协调前台的工程师修改界面的 css ,另一方面也在想办法整理搜索返回的结果列表。在与产品老大的一次讨论中,产品老大提出了一个想法:能不能将 Lucene 的打分从连续的实数改成阶梯状的区间范围?如果能,在同一区间内的邮件,便可以按照收信的时间,或者发件人,或者主题等明显有序的字段进行排序了。经过深入的研究 Lucene 的打分过程,最终实现了这样的目标:首先实现了一个自己的 Similarity 子类,将那些影响排序的因子值都设置为 1.0f ,这样,最终的分数就只是命中词的得分的简单相加,命中词的个数成了得分的唯一影响因子;然后又实现了一个 FieldComparator,提供针对文档 score 的 precisionCompare:如果 |score1-score2|/(score1+score2)<1%,则返回 0,即认为两个文档得分相同,以使用下一个字段进行排序。经过这样的处理以后,默认的排序结果终于看起来不那么混乱了。

            还有邮件解析问题。就像我在我的微博http://t.sina.com.cn/tangfl)上抱怨的那样:“强烈鄙视当初设计邮件格式的人,把一个简单的东西搞的无比的复杂,到处出错。来自新浪微博”   “谁能告诉我为什么有那么多封邮件的编码被设置成 GB2132 ?没办法,只能用更猥琐的办法来兼容如此猥琐的垃圾邮件。来自新浪微博”    以及一堆的小问题:outlook 转发时带的 winmail.dat ,To 字段为空,mime part 之间缺少空行,不指定任何编码(甚至有指定错误的编码的,不知是故意的还是无意的),text 段里带 html 标签,base64 编码错误,等等等等。更别说部门内部因为历史原因,当前存在三个解信库:后台 C 库,前台 php 库,以及搜索使用的 java mail 库,三个库对不符合规范的错误邮件的容忍程度不一样,导致各种解信错误层出不穷。最初设计邮件系统的人一定不是做技术的吧,可是最初实现 mta,mua 的那些人,为什么不直接把不符合 rfc 的邮件拒之门外呢?

            最痛苦的问题,莫过于更新了。为了支持复杂的查询,我们把一些非邮件本身的数据,如邮件在哪个文件夹,邮件的已读未读状态等,都建到了索引里。而这些数据的更新,都会导致索引的更新。Lucene 的索引,严格来说是不存在所谓的“更新”操作的,更新是用删除加添加两个动作的组合来实现的。而且,Lucene 更新文档的时候,不能只更新指定的一个或多个字段,而是一定要整个文档更新。更而且,在更新这个文档的时候,文档所在的整个 Lucene 的索引段的数据,都需要更新。更新的代价如此的高昂,而真正使用的概率又如此的小。权衡之下,我们选择了另一条路:“用 Lucene 做搜索,最痛恨的莫过于某个文档的某几个字段频繁变动,引起频繁的重建整个文档的索引。如果这几个字段只用来限制结果范围,而不参与排序,则可以将这几个字段拿到索引外面去,正常的搜索完成以后,再从外部获取数据,过滤结果集合。邮件搜索里的文件夹,标志位等数据,完全可以这么干 来自新浪微博

            网络层也出过很郁闷的问题:搜索用的是 netty 包,而与之通讯的其他进程,几乎都是用 C 写的,而且是不同的原作者,又经过不知多少的人做过不知多少次的更新。搜索的机器是 Centos,而邮箱其他地方几乎都是 Freebsd,存储上是 Solaris + zfs。于是问题就来了,时不时的 netty 里就报一个 ClosedChannelException,或者 broken pipe,或者 read/write timeout。机器之间不是直接调用,中间还隔着 F5 。查了几次,都没有得出最终的结论。在所有的调用发起方都增加了 retry ,也算是 work around 了。

            (未完待续)

            挑战邮箱搜索

            做完 论坛搜索 和 音乐搜索 (续),接下来开始做邮箱搜索

            邮箱搜索与其它的搜索引擎最大的区别莫过于每个用户只能搜索自己的邮件内容。搜索引擎一般都是开放性的搜索,每个用户都有权访问所有的索引项目,每次搜索请求都会在所有的索引项目中进行匹配。而邮箱搜索是私密搜索,每个用户只能访问索引中很小的一部分数据,相应的,也就可以将每个用户的索引单独存放,以加快建索引和搜索的速度。

            在最开始做方案的时候,因为种种原因,并没有选择给每个用户单独建一个索引的方案。毕竟,每个做方案的人在面临要建立上亿个目录的方案的时候,都会犹豫一下的。于是开始计算总的原始数据量,总的索引数据量,平均每个用户的原始数据量和索引数据量,每天的更新量,每天的搜索量等数据,以此来规划索引的布局和更新的策略。但在尝试了好几种布局方案之后,才发现无论如何布局,严重失衡的读写比例都会导致负载不均衡,更何况 lucene 的一些做法,会导致大量的额外数据读写,浪费本来已经很宝贵的IO。最后,只得退回到最初的想法上来。

            定下来给每个用户单独建立一个 lucene 索引,接下来要做的事情当然就是如何安置这么多个 lucene 索引目录了。路径 Hash 是最容易想到的办法,而事实是,到现在为止我们也没有找到更好的途径。服务器是 Centos,文件系统是 Ext3,所以每个目录最多不能超过 1024 个子目录或者文件。单个目录下两级 Hash 子目录,1024*1024 已经是百万了,如果 Hash 算法比较均匀,每个子目录下放置 100 个左右的用户,那么就可以存放 1 亿用户的索引,基本满足了预算的要求。

            为了方便上线部署,我们抽象了一个 node 节点的概念:一个 node 由一个索引目录及Hash子目录作为存储,一个或多个建索引进程更新索引,一个搜索进程对外提供搜索服务,再加上一些 memcached, memcacheq,memcachedb,mysql 以及 monitor 等辅助进程组成的独立的节点。每台服务器上根据负载可以部署一个或多个这样的节点。机器出现故障后,如果恢复所需的时间较长,而存储并没有损坏的情况下,可以将存储直接接到备份的机器上,重启所有的进程后,继续提供服务。

            这么多用户在同一台机器上,并发的搜索量还好说,因为邮箱用户并不都是搜索的重度用户,麻烦的是并发的更新。邮件的到达和一些必要的更新操作,底层收发系统通过一个 memcacheq 通知给搜索系统,建索引进程读取 mq 里的消息,到指定的地方获取邮件内容,调用 java mail 库进行解信,用 IK 进行分词(词库来自:新浪拼音输入法),然后更新索引。 为了支撑巨大的更新,我们在同一个 node 上启动了多个进程,每个进程又启动了多个线程进行并发的更新。为了避免可能的 lucene 锁争用,我们在打开 lucene 索引前,还使用 memecache 对路径进行加锁;为了提高更新效率,我们将同一个用户的多封邮件打包,提交给某一个线程进行处理。大部分的情况下,这种策略都运行的很好,但也有意外:如果更新索引的过程出现了某种可恢复的错误,本次更新涉及到的所有的消息都会回写到 memcacheq 队列里。而这些回写,很可能会打乱正常的用户到信次序(某个用户多个消息批量回写也有可能被正常的更新打乱),最终在队列里形成诸如 ABACBCACB 这样的交叉消息。一旦某个用户上一次的更新还未完成,下一个线程就必须等待,这样的交叉更新,会严重的拖慢整体的更新速度。最后,我们也只能将回写队列与正常更新队列分开,单独为回写队列起一个处理进程,work around 吧。

            起初在 12k 转速的 SAS 盘上做搜索压力测试,随机抽取用户,单个 node 能够很轻松的跑到 200 并发。可是最终的线上机器,为了节约成本,使用的是 7.2k 转速的 SATA 盘,还做了 RAID5 ,IO 随机小文件读取的瓶颈太明显了,随机抽取用户,连 50 并发都跑不上去。于是继续想办法。。。

            (未完待续)

            音乐搜索系统部署说明

            工作日志,转载请保留出处:唐福林 博客雨 音乐搜索系统部署说明 http://blog.fulin.org/2009/11/pcsearch_deploy.html

            PC 客户端搜索系统部署说明

            唐福林 <tangfulin AT gmail.com>

            PC 客户端搜索系统主要由 负责建索引的 IndexServer 和负责提供搜索服务的 SearchServer 两部分组成。
            IndexServer (目前是 98)负责接收资源库发过来的xml原始文件,解析原始文件,更新索引,并将更新后的索引推送到 SearchServer 上的指定目录供搜索使用。当前架构中,IndexServer 不支持分布式和负载均衡,只能部署在一台机器上。因为索引更新并不频繁,所以这里并不会带来性能瓶颈。如果是为了消除单点故障,可以在另外一台机器上起一个备份进程,当主 IndexServer 故障的时候接收资源库发过来的xml原始文件,但为了保持索引一致,建议不做实际的索引更新操作。这样 IndexServer 故障带来的唯一影响是索引更新延迟。只要做了必要的监控措施,延迟的时间是可以控制在可接受范围内的。IndexServer 主要消耗磁盘IO,也会有一些 cpu 和内存消耗。
            SearchServer (目前是 221)负责对外提供搜索服务。SearchServer 当前使用 Resin 提供的 Servlet 环境,以 HTTP 协议提供 Rest 风格的服务。SearchServer 会定期查看工作目录下是否有新的索引到达,如果有,则打开新索引,关闭旧索引。已关闭的旧索引随后会被脚本删除。SearchServer 主要消耗内存和网络IO。

            一。部署之前的服务器检查:

            1. 确认操作系统发行版本: cat /etc/redhat-release
            Red Hat Enterprise Linux Server release 5.3 (Tikanga) 或以上
            2. 确认系统安装的 java 版本:java -version
            java version “1.5.0_15” 或以上
            3. 确认系统安装的 rsync 版本: rsync –version
            rsync version 3.0.5 protocol version 30 或以上
            4. 确认磁盘空间:df -h
            确保有超过 2G 的剩余空间,建议预留 5G,方便打log。如果/home下磁盘空间不够,可以使用软链接
            5. 确认svn客户端版本:svn –version
            确保含有 https 模块:
            ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
            – handles ‘http’ scheme
            – handles ‘https’ scheme
            6. 确认 ant 版本:ant -version
            Apache Ant version 1.6.2 或以上
            7. 确认 resin 版本:
            3.1.9 或以上

            二。建立目录结构

            项目根目录设置为 /home/yyjd,以下的相对路径,如无特殊说明,都是相对于项目的根目录

            mkdir /home/yyjd && cd /home/yyjd && mkdir code data software && cd data && mkdir -p backup dict indexes indexserver logs pids webapps/search xml

            三。取得文件数据

            1. 取得必要的软件
            如果 “一” 中的检查不满足,或者希望安装 Hudson 或 JIRA,可以从 98 的 /home/yyjd/software 中取得软件,并进行安装。安装的时候,如果是 rpm 包,可以使用 rpm -ivh xxx.rpm 进行安装,如果是zip或tgz压缩包,解压缩即可,Hudson 是一个 war 包,可以直接运行,参考 code/PCSearcher/trunk/shell/server-start.sh 里的运行命令说明。
            注意,在安装 rpm 包的时候,可能会有一些依赖问题,按照提示先安装依赖包即可;安装 rpm 包如果报冲突,在命令行上加一个 –force 参数即可。

            2. 取得代码
            cd /home/yyjd/code && svn checkout https://*.*.*.254/svn/PCSearch

            四。相关软件设置

            1. rsync 设置:在搜索机上 vi /etc/rsyncd.conf

            uid = root
            gid = root
            max connections = 200
            timeout = 600
            use chroot = no
            read only = no
            pid file=/var/run/rsyncd.pid
            hosts allow=*.*.*.98

            [search]
            path=/home/yyjd
            comment = PCsearch project sync model

            保存退出,然后运行 rsync –daemon

            在 IndexServer 上测试 rsync 是否畅通:
            rsync -vn 搜索机ip::search/data/indexes .
            如果不通,则再测试一下使用 ssh 协议是否畅通:
            首先将 IndexServer 的 /root/.ssh/id_rsa.pub (如果没有该文件,运行一下 ssh-keygen 命令)的内容添加到搜索机 /root/.ssh/authorized_keys 文件的末尾(注意不要引入多余的换行符),然后测试:
            rsync -vn root@搜索机ip:/home/yyjd/data/indexes .

            测试正常之后,将测试成功的地址添加到 IndexServer 的 /home/yyjd/code/PCSearcher/trunk/shell/trans-dest.conf 文件末尾

            2. resin 设置:
            将 data/webapps/search 目录添加到 resin app 目录列表,也可以在 resin 的 webapps 目录下建一个到 data/webapps/search 的软链接

            五。编译代码

            cd /home/yyjd/code/PCSearcher/trunk && svn up && ant && ant indexserver

            六。启动进程
            进入 /home/yyjd/code/PCSearcher/trunk/shell 目录
            参考 server-start.sh
            1. IndexServer:
            a. 启动 resin (仅作测试,非必要)
            b. 启动 IndexServer: ./indexUpdater.sh restart
            c. 启动 trans 索引传输脚本:
            (nohup ./trans-indexsnap.sh songs 2>&1 >> /home/yyjd/data/logs/trans-songs.log &)
            (nohup ./trans-indexsnap.sh albums 2>&1 >> /home/yyjd/data/logs/trans-albums.log &)
            (nohup ./trans-indexsnap.sh keywords 2>&1 >> /home/yyjd/data/logs/trans-keywords.log &)
            d. 启动 clean 索引清除脚本(如果启用了 resin,就必须启动这些清除脚本):
            (nohup ./clean-indexsnap.sh songs 2>&1 >> /home/yyjd/data/logs/clean-songs.log &)
            (nohup ./clean-indexsnap.sh albums 2>&1 >> /home/yyjd/data/logs/clean-albums.log &)
            (nohup ./clean-indexsnap.sh keywords 2>&1 >> /home/yyjd/data/logs/clean-keywords.log &)
            e. 检查 /home/yyjd/data/logs 下的日志是否都正常

            2. SearchServer:
            a. 启动 resin
            b. 启动 IndexServer: ./indexUpdater.sh restart (可以作为 IndexServer 接收xml原始文件的功能的备份,非必要)
            c. 启动 clean 索引清除脚本:
            (nohup ./clean-indexsnap.sh songs 2>&1 >> /home/yyjd/data/logs/clean-songs.log &)
            (nohup ./clean-indexsnap.sh albums 2>&1 >> /home/yyjd/data/logs/clean-albums.log &)
            (nohup ./clean-indexsnap.sh keywords 2>&1 >> /home/yyjd/data/logs/clean-keywords.log &)
            d. 检查 /home/yyjd/data/logs 下的日志是否都正常

            七。日常管理

            1. shell 脚本介绍:code/PCSearcher/trunk/shell
            build.sh : Hudson 使用的编译,重启 IndexServer 进程的脚本,不建议使用
            clean-indexsnap.sh : 无用索引清除脚本,SearchServer 使用
            indexUpdater.sh : 索引更新 daemon 进程,接受 {start|stop|restart} 参数调用
            initKeyword.sh : 关键词索引初始化脚本,平常不用
            processManager.sh : 底层的进程启动,停止管理脚本,由其他脚本调用
            rebuild.sh : 模拟发送重建歌曲,专辑索引的xml原始数据的脚本,重建索引时使用
            server-start.sh : 记录了新部署机器时可能会用到的启动命令,不建议直接运行
            trans-dest.conf : 传输脚本的目的地址配置文件
            trans-indexsnap.sh : 索引传输脚本,IndexServer 使用

            2. logs 日志介绍:data/logs
            classpath.log : 索引更新 daemon 进程启动的时候的 classpath,debug 使用
            clean-*.log : 索引清除日志
            trans-*.log : 索引传输日志
            search.log : 搜索日志
            indexserver.log : 索引更新日志
            另外,resin 的日志也需要留意观察。

            八。常见错误及修正方法

            1. svn 不支持 https
            2. ant 编译出错
            3. indexUpdater.sh 启动 索引更新 daemon 进程 出错
            4. rsync 传输出错
            5. 搜索出错,出白页面或 404
            6. 搜索出错,报 Exception:确认一下 data/indexes/ 下面的每个子目录下面都有索引文件

            音乐搜索的极致(续)

            12530 PC客户端音乐搜索项目一期的总结和思考。

            SlideShare 上的 pdf:

            PPT 的文字内容:

            1. 音乐搜索的极致 唐福林 tangfulin@gmail.com http://blog.fulin.org
            2. 目录  项目简介  需求描述  搜索实现  查询示例  持续改进
            3. 项目简介 (1/3)  中国移动  12530  咪咕  Miniportal  搜索  Out source : edadao
            4. 项目简介 (2/3)  时间: 2009 年 9 月 12 日到 10 月 22 日  地点:成都,郫县,犀浦,移动音乐基 地  参与人员:  需求提供: wangquanli@12530,   zhengchangsong@12530  开发人员: mike,tangfulin,xww,wanghui  特别贡献: dave
            5. 项目简介 (3/3)  部署情况:  位置:移动音乐基地,西区枢纽机房  机器:  建索引: 227.98  搜索: 227.221  Dual Core AMD Opteron(tm) Processor 8218 2.6G * 8  8G mem  Red Hat Enterprise Linux Server release 5.3 (Tikanga)  Linux 2.6.18­128.el5PAE #1 SMP  i686 athlon  GNU/Linux  索引大小: 3 个索引目录共 1.2G  流量:  机器负载情况:
            6. 需求描述  搜索字段:歌手,歌曲,专辑,歌词  搜索方式:  精确匹配  前缀匹配  分词匹配  模糊匹配  拼音全量匹配  拼音首字母匹配  拼音同音匹配  拼音纠错匹配  关键词提示: • 搜索框下拉提示 • 纠错提示
            7. 需求-精确匹配  规则:精确匹配或过滤所有特殊字符后精 确匹配  单个字段:  歌手:阿唬 , 80 前后  歌曲:爱情 BT 大讲堂  专辑: Alive!  多个字段联合:  歌手名+歌曲名:刘德华 今天  歌手名+专辑名:许茹芸 爱 . 旅行 . 一公里
            8. 需求-前缀匹配  规则:过滤所有特殊字符后前缀匹配  单个字段:  歌手:刘德,张学  歌曲:  专辑:
            9. 需求-分词匹配  规则:过滤所有特殊字符后分词匹配  单个字段:  歌手:德华,杰伦,学友  歌曲:  专辑:  歌词:  多个字段联合:先 Must ,再 Should  歌手名+歌曲名+专辑名:  歌手名+专辑名:
            10. 需求-模糊匹配  规则:一定模糊度的词匹配(注:很 慢)  单个字段:  歌手:刘大华  歌曲: beautful, califonia  专辑:
            11. 需求-拼音全量匹配  规则:用户输入拼音匹配  单个字段:  歌手: liudehua, zhangxueyou  歌曲:  专辑:
            12. 需求-拼音首字母匹配  规则:用户输入拼音首字母匹配  单个字段:  歌手: ldh, zxy  歌曲:  专辑:
            13. 需求-拼音同音匹配  规则:用户拼音输入法,输入错误的同 音字匹配  单个字段:  歌手:柳的话,两用器  歌曲:  专辑:
            14. 需求-拼音纠错匹配  规则:用户拼音输入法输入错误的字, 或直接输入错误的拼音  n l,h­f,z zh,c ch,s sh,an ang,en eng,in ing  单个字段:  歌手:牛德华, niudehua  歌曲:  专辑:
            15. 搜索实现  建索引策略  冗余字段  中文将拼音,首字母也建进索引里  搜索 Query 策略  弃用多次查询的策略  采用多个 Query 拼装成一个 BooleanQuery ,设置不同的权值, 一次查询的策略
            16. 搜索实现:建索引策略 (1/2)  歌手: singer_name  singer_name_save: 保存字段, trim 后,原封不动  singer_name_filtered:  过滤字段,过滤所有的特殊字符,转小写  singer_name_analyzed:  分词字段  singer_name_notanalyzed:  不分词字段,前缀匹配使用  singer_name_full:  拼音全量字段  singer_name_first:  拼音首字母字段  歌曲: song_name   专辑: album_name
            17. 搜索实现:建索引策略 (2/2)  关键词: keyword  所有的歌手名,歌曲名,专辑名, 歌手+歌曲,歌手+专辑,都视为 关键词  单独一个索引文件  供下拉提示和搜索无结果时的纠错 提示使用  也提供拼音全量,拼音首字母,拼 音纠错等功能
            18. 搜索实现:搜索策略 (1/6)  策略列表: 1. 精确匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,精确匹配 2. 过滤后的精确匹配:歌手,歌曲,专辑,过滤字段,去掉所有特殊字符,英文转成小 写,精确匹配 3. 拼音全量匹配:歌手,歌曲,专辑,拼音全量字段,去掉所有非英文字符,英文转成小 写,精确匹配 4. 同音纠错匹配:歌手,歌曲,专辑,拼音全量字段,只对含中文的搜索词使用,中文转 拼音,英文转小写,去掉所有特殊字符,精确匹配 5. 拼音首字母匹配:歌手,拼音首字母字段,中文转拼音首字母,英文转小写,去掉所有 特殊字符,精确匹配 6. 前缀匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,英文转小写,前缀匹 配 7. 分词 Must 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Must 连 接,分词匹配
            19. 搜索实现:搜索策略 (2/6)  策略列表(续): 1. 分词 Should 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Should 连接,分词匹配 2. 合并分词 (must) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 must  连 接),分词匹配 3. 合并分词 (should) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 Should  连 接),分词匹配 4. 拼音纠错匹配查询(忽略掉鼻音等) : 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 5. 中文模糊匹配 ,中文时模糊度: 0.65: 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 6. 英文模糊匹配,英文模糊度: 0.85: 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 .
            20. 搜索实现:搜索策略 (3/6)  精度选择  只搜索精确匹配结果  精确匹配,过滤后精确匹配,前缀匹配  拼音全量,首字母  分词全部命中  只搜索模糊匹配结果  分词部分命中  同音纠错,拼音纠错  模糊匹配  去掉精确匹配的结果  搜索全部结果
            21. 搜索实现:搜索策略 (4/6)  字段选择  只搜索歌手  只搜索歌曲  只搜索专辑  只搜索歌词  搜索关键词字段  搜索全部字段(暂时不包括关键词 和歌词)
            22. 搜索实现:搜索策略 (5/6)  设置权值  将所有的策略置入一个有序列表中  列表中相邻的两个策略之间权值相差常数倍(当前 设置为 10 )。过大可能会导致 lucene 评分溢出,过 小可能会导致不同策略命中的结果集重叠  调整列表中策略的先后次序以调整结果集中各种命 中的出现顺序  二级权值:在搜索全部的时候,歌手 > 歌曲 > 专 辑,所以需要在同一个策略内部再设置字段权值  中文分词命中的权值设置: Lucene  默认打分策略 中,并没有考虑命中的词的长度。为了优先显示长 的词命中的结果,对分词 Query 中每个词根据长度 设置不同的权值
            23. 搜索实现:搜索策略 (6/6)  索引文件划分  搜索歌曲索引  搜索专辑索引  搜索关键词索引  排序策略  编辑置顶  Lucene  评分  业务量(点击,订阅,播放等)  关键词词频
            24. 搜索示例  歌曲索引,全部字段,精确搜索  搜 刘德华,结果条数: 329  QUERY:(((singer_name_notanslysis: 刘德华 ^9.0  song_name_notanslysis: 刘德华 ^4.0 album_name_notanslysis: 刘德 华 ))^10000.0) (((singer_name_filtered: 刘德华 ^9.0 song_name_filtered: 刘德华 ^4.0 album_name_filtered: 刘德华 ))^1000.0)  (((singer_name_filtered: 刘德华 *^9.0 song_name_filtered: 刘德华 *^4.0  album_name_filtered: 刘德华 *))^100.0) ((((((+singer_name_anslysis: 刘 德华 +singer_name_anslysis: 德华 )^9.0) ((+song_name_anslysis: 刘德 华 +song_name_anslysis: 德华 )^4.0) (+album_name_anslysis: 刘德华 +album_name_anslysis: 德华 ))))^10.0) ((((+singer_song_album: 刘德华 +singer_song_album: 德华 ))))
            25. 搜索示例  歌曲索引,全部字段,精确搜索  搜 ldh ,结果条数: 401 (命中刘德华,刘大浩等)  QUERY:(((singer_name_notanslysis:ldh^9.0  song_name_notanslysis:ldh^4.0  album_name_notanslysis:ldh))^1000.00006)  (((singer_name_filtered:ldh^9.0 song_name_filtered:ldh^4.0  album_name_filtered:ldh))^100.00001) (((singer_name_full:ldh^9.0  song_name_full:ldh^4.0 album_name_full:ldh))^10.000001)  (((singer_name_first:ldh))^1.0000001) (((singer_name_filtered:ldh*^9.0  song_name_filtered:ldh*^4.0 album_name_filtered:ldh*))^0.10000001)  ((((((+singer_name_anslysis:ldh)^9.0) ((+song_name_anslysis:ldh)^4.0)  (+album_name_anslysis:ldh))))^0.010000001)  (((((+singer_song_album:ldh))))^0.0010)
            26. 搜索示例  歌曲索引,全部字段,精确搜索  搜 liudehua ,结果条数: 324 (歌名《我不是刘德华》无法命中)  QUERY:(((singer_name_notanslysis:liudehua^9.0  song_name_notanslysis:liudehua^4.0  album_name_notanslysis:liudehua))^1000.00006)  (((singer_name_filtered:liudehua^9.0 song_name_filtered:liudehua^4.0  album_name_filtered:liudehua))^100.00001)  (((singer_name_full:liudehua^9.0 song_name_full:liudehua^4.0  album_name_full:liudehua))^10.000001)  (((singer_name_filtered:liudehua*^9.0 song_name_filtered:liudehua*^4.0  album_name_filtered:liudehua*))^1.0000001)  ((((((+singer_name_anslysis:liudehua)^9.0)  ((+song_name_anslysis:liudehua)^4.0)  (+album_name_anslysis:liudehua))))^0.10000001)  (((((+singer_song_album:liudehua))))^0.010000001)  (((singer_name_first:liudehua))^0.0010)
            27. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 刘德华,结果条数: 44 (命中爱德华,杨德华等)  QUERY: ((((singer_name_notanslysis: 刘德华 ^9.0 song_name_notanslysis: 刘德华 ^4.0  album_name_notanslysis: 刘德华 ))^10000.0) (((singer_name_filtered: 刘德华 ^9.0  song_name_filtered: 刘德华 ^4.0 album_name_filtered: 刘德华 ))^1000.0)  (((singer_name_filtered: 刘德华 *^9.0 song_name_filtered: 刘德华 *^4.0  album_name_filtered: 刘德华 *))^100.0) ((((((+singer_name_anslysis: 刘德华 +singer_name_anslysis: 德华 )^9.0) ((+song_name_anslysis: 刘德华 +song_name_anslysis: 德华 )^4.0) (+album_name_anslysis: 刘德华 +album_name_anslysis: 德华 ))))^10.0)  ((((+singer_song_album: 刘德华 +singer_song_album: 德华 ))))) +(((((singer_song_album: 刘德华 ^9.0 singer_song_album: 德华 ^4.0)))^10000.0) (((((singer_name_anslysis: 刘德华 ^9.0 singer_name_anslysis: 德华 ^4.0)^9.0) ((song_name_anslysis: 刘德华 ^9.0  song_name_anslysis: 德华 ^4.0)^4.0) (album_name_anslysis: 刘德华 ^9.0  album_name_anslysis: 德华 ^4.0)))^1000.0) (((((singer_name_full:liudehua)^9.0)  ((song_name_full:liudehua)^4.0) (album_name_full:liudehua)))^100.0) ((())^10.0)  ((singer_name_anslysis: 刘德华 ~0.65^9.0 song_name_anslysis: 刘德华 ~0.65^4.0  album_name_anslysis: 刘德华 ~0.65)))
            28. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 牛德华,结果条数: 840 (命中刘德华,爱德华,杨德华等)  QUERY: ((((singer_name_notanslysis: 牛德华 ^9.0 song_name_notanslysis: 牛德华 ^4.0  album_name_notanslysis: 牛德华 ))^10000.0) (((singer_name_filtered: 牛德华 ^9.0  song_name_filtered: 牛德华 ^4.0 album_name_filtered: 牛德华 ))^1000.0)  (((singer_name_filtered: 牛德华 *^9.0 song_name_filtered: 牛德华 *^4.0  album_name_filtered: 牛德华 *))^100.0) ((((((+singer_name_anslysis: 牛 +singer_name_anslysis: 德华 )^9.0) ((+song_name_anslysis: 牛 +song_name_anslysis: 德 华 )^4.0) (+album_name_anslysis: 牛 +album_name_anslysis: 德华 ))))^10.0)  ((((+singer_song_album: 牛 +singer_song_album: 德华 ))))) +(((((singer_song_album: 牛 singer_song_album: 德华 ^4.0)))^10000.0) (((((singer_name_anslysis: 牛 singer_name_anslysis: 德华 ^4.0)^9.0) ((song_name_anslysis: 牛 song_name_anslysis: 德华 ^4.0)^4.0) (album_name_anslysis: 牛 album_name_anslysis: 德华 ^4.0)))^1000.0)  (((((singer_name_full:niudehua)^9.0) ((song_name_full:niudehua)^4.0)  (album_name_full:niudehua)))^100.0) ((())^10.0) ((singer_name_anslysis: 牛德华 ~0.65^9.0  song_name_anslysis: 牛德华 ~0.65^4.0 album_name_anslysis: 牛德华 ~0.65)))
            29. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 niudehua ,结果条数: 324  QUERY:­((((singer_name_notanslysis:niudehua^9.0 song_name_notanslysis:niudehua^4.0  album_name_notanslysis:niudehua))^1000.00006) (((singer_name_filtered:niudehua^9.0  song_name_filtered:niudehua^4.0 album_name_filtered:niudehua))^100.00001)  (((singer_name_full:niudehua^9.0 song_name_full:niudehua^4.0  album_name_full:niudehua))^10.000001) (((singer_name_filtered:niudehua*^9.0  song_name_filtered:niudehua*^4.0 album_name_filtered:niudehua*))^1.0000001)  ((((((+singer_name_anslysis:niudehua)^9.0) ((+song_name_anslysis:niudehua)^4.0)  (+album_name_anslysis:niudehua))))^0.10000001)  (((((+singer_song_album:niudehua))))^0.010000001)  (((singer_name_first:niudehua))^0.0010)) +(((((singer_song_album:niudehua^64.0)))^1000.0)  (((((singer_name_anslysis:niudehua^64.0)^9.0) ((song_name_anslysis:niudehua^64.0)^4.0)  (album_name_anslysis:niudehua^64.0)))^100.0) (((((singer_name_full:niudefua  singer_name_full:liudehua)^9.0) ((song_name_full:niudefua song_name_full:liudehua)^4.0)  (album_name_full:niudefua album_name_full:liudehua)))^10.0)  ((singer_name_anslysis:niudehua~0.85^9.0 song_name_anslysis:niudehua~0.85^4.0  album_name_anslysis:niudehua~0.85)))
            30. 搜索示例  歌曲索引,歌手字段,精确搜索  搜 杰伦,结果条数: 270  QUERY:(((singer_name_notanslysis: 杰伦 ))^1000.0) (((singer_name_filtered: 杰伦 ))^100.0)  (((singer_name_filtered: 杰伦 *))^10.0) ((((+singer_name_anslysis: 杰伦 ))))
            31. 搜索示例  歌曲索引,歌曲字段,模糊搜索  搜 li ,结果条数: 117  (命中 你 )  QUERY: ((((song_name_notanslysis:li))^10000.0) (((song_name_filtered:li))^1000.0)  (((song_name_full:li))^100.0) (((song_name_filtered:li*))^10.0)  ((((+song_name_anslysis:li))))) +(((((song_name_anslysis:li^4.0)))^100.0)  ((((song_name_full:ni)))^10.0) ((song_name_anslysis:li~0.85)))
            32. 搜索示例  关键词索引,搜索歌曲  搜 liu ,结果:  流浪 流星 留恋 六月雪 浏阳河 流浪狗 流浪者之歌 留不住你的温柔  QUERY:+keyword_type:2  keyword_word_notanslysis:liu + (((keyword_word_filtered:liu*)^100.0) ((keyword_word_full:liu*)^100.0)  ((keyword_word_first:liu*)^100.0))
            33. 搜索示例  关键词索引,搜索歌手  搜 liu ,结果:  刘德华 刘冠群 刘亦敏 刘韵 刘若英 刘基俊 刘益中 刘芳 刘庆  QUERY:+keyword_type:1  keyword_word_notanslysis:liu + (((keyword_word_filtered:liu*)^100.0) ((keyword_word_full:liu*)^100.0)  ((keyword_word_first:liu*)^100.0))
            34. 搜索示例  关键词索引,搜索全部字段  搜 zhoujie ,结果:(有歌手+歌曲的命中)  周杰伦 周杰磊 周杰伦我求求你了 周杰伦传递祝福 周杰伦春节祝福 周杰伦情人节祝福  周杰伦 蒲公英的约定  周杰伦 最长的电影  周杰伦 阳光宅男  周杰伦 甜甜的  QUERY:+(keyword_type:1 keyword_type:2 keyword_type:3)  keyword_word_notanslysis:zhoujie +(((keyword_word_filtered:zhoujie*)^100.0)  ((keyword_word_full:zhoujie*)^100.0) ((keyword_word_first:zhoujie*)^100.0))
            35. 搜索示例  关键词索引,搜索全部字段  搜 柳的话,结果:(拼音同音命中)  刘德华 留得华  刘德华 冰雨  刘德华 中国人  刘德华 幸福这么远那么甜  刘德华 百分百好戏  刘德华 笑着哭  刘德华 谢谢你的爱  刘德华 爱你一万年  刘德华 情义俩心坚  QUERY:+(keyword_type:1 keyword_type:2 keyword_type:3)  keyword_word_notanslysis: 柳的话 +(((keyword_word_filtered: 柳的话 *)^100.0)  (((keyword_word_full:liudehua*)^100.0) ((keyword_word_full:liudihua*)^100.0)))
            36. 持续改进  性能调优: resin ,内存, cache  汉字转拼音:多音字,特殊符号  拼音纠错:另一种思路,标准化 vs 排列组合  关键词: Trie 树,按词频排序,加入歌词数据  业务要求以频繁更新的业务量作为排序依据  标签搜索:新需求  搜索关键词,保持先后顺序  自定义打分算法的尝试  Lucene  升级到 2.9.1 , bug 1974 , explain  显示 0 或者 NaN  显示时最佳片段截取, html 实体截断问题  标红,按策略的山寨标红与 Lucene 自带标红的优劣比较及取舍
            37. 性能调优 • 目标:单台机器,百万数量级的索引, 1000 个并发 下, 99% 0.5 秒内返回 • 优势: • 索引更新不是很频繁,可以忽略不计 • 服务器性能不错, 8cpu , 8G 内存 • 劣势: • 并发大,返回时间 0.5 秒要求太苛刻 • 一期代码有很多不合理的地方 • 项目时间紧张 • 使用了 resin 作为容器,有太多不可控因素
            38. 汉字转拼音 • Pinyin4j  的词库 • 自己整理的多音字表 • 当前将所有多音字的组合都建在索引里 • 莫文蔚: mowenwei, mowenyu  • 优点:保证能查到 • 缺点:输入 mowenyu  能查到莫文蔚,而且一个歌 名中如果有好几个多音字,排列组合的数量比较 可观 • 拼音首字母,而不是拼音声母 • 张学友: zxy ,不是 zhxy
            39. 拼音纠错 • 规则: • n l,h f,z zh,c ch,s sh,an ang,en eng,in ing (on ong) • 当前实现: • 将用户输入的搜索词中的每个出现,依次替换成对应的纠 错,联合成一个 Should Query • 如: liudehua: niudehua, liudefua • 另一种思路:标准化 • 规定规则中的替换只能单向: n >l,z >zh 等 • 在索引中增加一个标准化拼音字段,如曾经最美,该字段 存储的值为 chengjingzhuimei • 用户输入关键词,也经过同样的标准化后,在该字段进行 查询
            40. 关键词 • 关键词当前使用 Lucene  的索引前缀查询的方式实现 • 也包括拼音,首字母,纠错等 Query • 本来还有模糊查询的 Query ,但后来发现太影响查询 速度了,于是就暂时去掉了 • 当前没有把歌词数据建到关键词索引中去。如果把歌 词建进去,这个索引就太大了,必须要进行分拆 • 考虑使用 Trie 树: • 多棵树,拼音,首字母,中文需要各自建树 • 模糊查询的问题
            41. 排序 vs 更新 • 业务需求希望能以歌曲的业务量(播放,下载等量) 作为排序的一个依据 • 意味着需要频繁的更新索引,而且为了更新这样一个 数字字段,需要将整个文档删除重新添加,不划算 • 打算重载 Lucene  的 Collection  类,自己实现排序字 段值的加载,不从索引里面读取 • 问题: 2.4  与 2.9  在这个地方的实现上有很大的不 同,没法无缝切换
            42. 标签搜索 • 固定维度的标签,编辑填写,非用户产生内容 • 如:奥运,免费,铃声,开心,悲伤等 • 关键是产品设计,非技术实现 • 参考: google  泡泡挑歌
            43. 保持搜索关键词的顺序 • 延后实现的一个需求 • 只命中跟用户输入的多个关键词之间的顺序一致的结果 • 如: • 用户输入 “谢谢 爱”命中“谢谢你的爱”,但输入 “爱 谢谢”不命中 • 用户输入 “眼睛 背叛 心”命中“你的眼睛背叛了 你的心”,但输入“心 背叛 眼睛”不命中 • 实现: • 分词命中,无法保留顺序信息 • 模糊查询,效率太差 • ???
            44. 打分算法  需求: • 产品人员对 Lucene 打分算法的不理解,要求单纯的以某一个 依据来进行排序,如命中的词的个数 • 拼多个 Query 查询的副作用:多个 Query 的评分累加得到的 最后得分,会导致各个 Query 的命中结果重叠(想法:把 累加改成取最大值?) • 一个想法:同样的分词命中,命中较长的词的结果排前面  教训: • 没有金刚钻,别揽瓷器活!不要轻易的去改 Lucene 的评分算法
            45. Lucene 版本的选择 • 首选 2.9.0 • Bug 1974 : https://issues.apache.org/jira/browse/LUCENE 1974 • 换成了 2.4.1  • 为了性能及长远打算,还是希望换回 2.9.1 • Explain  函数调用返回评分 0  或 NaN
            46. 摘要截取 • Miniportal  空间有限,歌曲,专辑,甚至歌手名都可 能需要截断 • 截断时需要考虑标红问题 • 截断时需要考虑 html  实体的问题
            47. 标红 • Lucene  标红的优点与不足 • 优点:正统,可升级 • 缺点:不能满足需求,前缀标红,拼音标红等 • 山寨标红 • 按照每种 Query 进行相应的标红,最后合并 •
            48. 更多讨论 http://blog.fulin.org

            关于音乐搜索

            音乐搜索属于垂直搜索的一种,但它又有着自己独特的一些需求。

            首先,几乎所有的音乐搜索都实现了用户输入时的关键词提示功能。但在网上搜索相关的技术文章,大多是讲如何用 Js 实现前台表现层的功能,少有的几篇关于后台技术实现的文章,也都太过简单。标准的办法是使用 Trie 树,但太过晦涩,不够直观。我们打算直接使用 Lucene 的前缀查询来实现,并且计划在项目上线后写一个比较详细的说明。

            其次,很多的音乐搜索都提供了拼音查询的功能。比如说用户输入 “liudehua”,关键词提示里会给出 “刘德华”,但即使用户不理会提示,直接点击提交,在服务器端,还是可以查询到关于 “刘德华” 的条目。甚至,用户输入拼音首字母 “ldh”,都可以匹配到 “刘德华”。这主要是考虑到使用音乐搜索的用户群的特点(低龄?懒惰?互联网初级用户?),以及某些艺人的名字确实比较难拼写吧。技术上其实很简单,建索 引的时候,将歌曲名,歌手名等都转成拼音一并进行索引就可以了。唯一一点需要注意的地方在于,多音字的处理。

            再次,有些搜索引擎,像 qq music,提供了同音字纠错的功能,可以在用户输入“周洁论”的时候,命中关于“周杰伦”的结果。有了上一步的拼音索引,这一步也很容易实现了。再多做 一步,考虑到南北方的口音差别,很多人 en 与 eng,zh 与 z,n 与 l 不分,在搜索过程中进行一些简单的替换,拼音模糊纠错功能也就水到渠成了。

            最后,汉字的模糊搜索。我们常用的一个例子就是,用户输入“刘大华”,能否命中“刘德华”?技术上肯定是可以的,lucene 本身就提供这样的查询,只是在产品设计上,是否有代替用户思考的嫌疑呢?这就需要产品人员去仔细思量了。

            前面说的是功能,后面说说排序。

            最基础的排序当然是按文档匹配度,也就是 lucene 的 score 来排了。但是有时候编辑推荐的歌曲是一定要排前面的,这个比较好实现。可是点击率比较高的歌曲也要靠前排,这个就有点麻烦了,因为牵涉到频繁的字段更新,以及 boost 值的微调。

            最麻烦的是上面说的那一堆的特殊处理。比如用户输入了一个词,精确匹配肯定应该排最前面了,没有精确匹配中文的,拼音全量匹配也可以,分词匹配,或 者部分匹配的结果次之,再接下来应该是前缀搜索,同音字纠错,模糊搜索的匹配条目。最开始的想法一直是多次搜索,可是在多次搜索里,一是无法控制所谓的精 确匹配;二是多次搜索打包的结果用于排序的时候,很麻烦;三则,多次搜索,本身的逻辑就非常复杂。不过今天学会一招,如果不考虑性能损耗,可以说是屠龙刀 级别的必杀技:打包多个 Query 对象,一次搜索!排序的问题,当然使用 Query.setBoost 解决了。至于精确匹配,冗余一个字段,不分词就行。

            搭建好了 Hudson,写了一个看起来蛮复杂的 build.xml ,然后每天看着它自动的编译,测试,发布,还是有点成就感的。

            开始写测试用例。一边写也一边在思考,搜索引擎项目该如何进行功能正确性的测试,又如何进行搜索结果好坏的评价呢?

            当前几个主要的Lucene中文分词器的比较

            1. 基本介绍:

            paoding :Lucene中文分词“庖丁解牛” Paoding Analysis
            imdict :imdict智能词典所采用的智能中文分词程序
            mmseg4j : 用 Chih-Hao Tsai 的 MMSeg 算法 实现的中文分词器
            ik :采用了特有的“正向迭代最细粒度切分算法“,多子处理器分析模式

            2. 开发者及开发活跃度:

            paodingqieqie.wang, google code 上最后一次代码提交:2008-06-12,svn 版本号 132
            imdictXiaoPingGao, 进入了 lucene contribute,lucene trunk 中 contrib/analyzers/smartcn/ 最后一次提交:2009-07-24,
            mmseg4jchenlb2008,google code 中 2009-08-03 (昨天),版本号 57,log为:mmseg4j-1.7 创建分支
            iklinliangyi2005,google code 中 2009-07-31,版本号 41

            3. 用户自定义词库:

            paoding :支持不限制个数的用户自定义词库,纯文本格式,一行一词,使用后台线程检测词库的更新,自动编译更新过的词库到二进制版本,并加载
            imdict :暂时不支持用户自定义词库。但 原版 ICTCLAS 支持。支持用户自定义 stop words
            mmseg4j :自带sogou词库,支持名为 wordsxxx.dic, utf8文本格式的用户自定义词库,一行一词。不支持自动检测。 -Dmmseg.dic.path
            ik : 支持api级的用户词库加载,和配置级的词库文件指定,无 BOM 的 UTF-8 编码,rn 分割。不支持自动检测。

            4. 速度(基于官方介绍,非自己测试)

            paoding :在PIII 1G内存个人机器上,1秒 可准确分词 100万 汉字
            imdict483.64 (字节/秒),259517(汉字/秒)
            mmseg4j : complex 1200kb/s左右, simple 1900kb/s左右
            ik :具有50万字/秒的高速处理能力

            5. 算法和代码复杂度

            paoding :svn src 目录一共1.3M,6个properties文件,48个java文件,6895 行。使用不用的 Knife 切不同类型的流,不算很复杂。
            imdict :词库 6.7M(这个词库是必须的),src 目录 152k,20个java文件,2399行。使用 ICTCLAS HHMM隐马尔科夫模型,“利用大量语料库的训练来统计汉语词汇的词频和跳转概率,从而根据这些统计结果对整个汉语句子计算最似然(likelihood)的切分”
            mmseg4j : svn src 目录一共 132k,23个java文件,2089行。MMSeg 算法 ,有点复杂。
            ik : svn src 目录一共6.6M(词典文件也在里面),22个java文件,4217行。多子处理器分析,跟paoding类似,歧义分析算法还没有弄明白。

            6. 文档

            paoding :几乎无。代码里有一些注释,但因为实现比较复杂,读代码还是有一些难度的。
            imdict : 几乎无。 ICTCLAS 也没有详细的文档,HHMM隐马尔科夫模型的数学性太强,不太好理解。
            mmseg4jMMSeg 算法 是英文的,但原理比较简单。实现也比较清晰。
            ik : 有一个pdf使用手册,里面有使用示例和配置说明。

            7. 其它

            paoding :引入隐喻,设计比较合理。search 1.0 版本就用的这个。主要优势在于原生支持词库更新检测。主要劣势为作者已经不更新甚至不维护了。
            imdict :进入了 lucene trunk,原版 ictclas 在各种评测中都有不错的表现,有坚实的理论基础,不是个人山寨。缺点为暂时不支持用户词库。
            mmseg4j : 在complex基础上实现了最多分词(max-word),但是还不成熟,还有很多需要改进的地方。
            ik :  针对Lucene全文检索优化的查询分析器IKQueryParser

            8. 结论

            个人觉得,可以在 mmseg4j 和 paoding 中选一个。关于这两个分词效果的对比,可以参考:

            http://blog.chenlb.com/2009/04/mmseg4j-max-word-segment-compare-with-paoding-in-effect.html

            或者自己再包装一下,将 paoding 的词库更新检测做一个单独的模块实现,然后就可以在所有基于词库的分词算法之间无缝切换了。

            ps,对不同的 field 使用不同的分词器是一个可以考虑的方法。比如 tag 字段,就应该使用一个最简单的分词器,按空格分词就可以了。

            beta技术沙龙 邀请

            http://club.blogbeta.com/82.html

            beta技术沙龙·大型网站的lucene搜索实战

            时间:7月26日14点30分开始
            地点:奇遇花园咖啡馆 http://storygarden.me/cafe/map

            主题:大型网站的lucene搜索实战
            演讲简介:本次活动介绍基于Lucene的站内搜索的实践,后台技术层面的一些想法与实践,包括缩短更新周期,简化重建索引流程,支持大数据量频繁更新的索引,以及在性能和可用性方面作的努力。
            主讲人:唐福林 (http://blog.fulin.org https://twitter.com/tangfl)
            主讲人简介:从高中的 NOIP 到大学的ACM,是编程竞赛的参与者,也是算法的爱好者,以追求程序或系统的性能的极致为乐。 目前就职于手机之家,负责基于lucene的站内搜索的开发及持续改进。

            参与人员:最大的前提是技术出身,第二个要求是写blog。积极参加现场沙龙聊天;如有可能对话题通过blog保持跟进关注。http://club.blogbeta.com/ 网上报名或直接现身沙龙,参与免费,点单另付。
            关于沙龙:blogbeta技术沙龙由一群技术人员发起并参与,旨在以技术的视角看待社会、互联网和未来,以务实精神深化交流,促进创新。

            update: 讲稿的最终版本下载:

            http://files.getdropbox.com/u/648709/%E5%9F%BA%E4%BA%8Elucene%E7%9A%84%E7%AB%99%E5%86%85%E6%90%9C%E7%B4%A2-beta.pdf

            https://docs.google.com/fileview?id=0By5e0-mxw7O0NWM5MzhhZTctZGEwOS00NzkzLTliMGYtY2U0ZjBlMDJhZjcy&hl=zh_CN