`
san_yun
  • 浏览: 2594518 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

一种基于Lucene的实时搜索方案

 
阅读更多

实时解决方案

在介绍我们产品方案之前,首先介绍下业内常见的实时解决方案,见图1-1实时架构图:

图1-1

图1-1

该方案一般是由:

  • 内存索引(Ram-IndexA)负责数据更新。
  • 内存索引(Ram-IndexA)达到阀值,角色转换成待合并内存索引(Ram-IndexB),同时重新开辟一块新的内存索引(Ram-IndexA)负责新的更新写入,老的内存索引(Ram-IndexB)合并且优化到主索引中。
  • 内存索引(Ram-IndexA)+磁盘索引(Full-IndexA)提供检索服务。

基于该方案可以带来的最大的优势是内存索引可以合并到主索引,避免了索引碎片,从而可以屏蔽全量索引重新构建和保障搜索服务的性能稳定性。但是这种方案仍然会存在以下问题:

  • 生产者(写入量)远远大于消费能力(内存索引构建),影响上游系统(数据生成方)的稳定性。
  • 内存索引合并磁盘主索引并执行优化过程时间较长,如果该过程出现宕机,重启机器后存在丢失数据的可能性。
  • 内存索引和主磁盘索引合并后,主索引是需要重新打开才能让更新可见,而对于大的磁盘索引重新打开一次耗时是比较长,因为需要重新预热数据到内存中。那么对实时性要求很高的需要明显是不合理的。
  • 另外为了保证重新打开主索引视图期间查询是不中断,也就意味着一份大的磁盘索引的资源视图需要被同时打开2份。那么就意味着承载该主索引的机器资源是需要实际承载主索引2倍以上资源才能满足。
  •  大索引的合并并优化的过程对机器IO资源占用较大,而本身搜索服务本身就是IO密集型的应用,所以合并主索引并优化必定对搜索服务稳定性带来影响。
  • 因为要数据更新可见,所以需要频繁的重新打开合并后的磁盘大索引,这样会导致大索引对应的优化Cache出现频繁清空,重新加载的问题。从而使得为性能优化而设置的Cache访问命中率将非常低,这样对于一些复杂的统计查询带来性能上极大不稳定性,同时Cache如果本身所占内存过大,还会带来JVM频繁FullGC的影响。

所以为了单纯追求系统某个指标值(如永远不需要做全量),而牺牲掉系统稳定性是得不偿失的。当然上述某些原因可能在一些好的硬件配置机器下并不会暴露的特别明显,但是从技术架构的机器成本上考虑的话,上述设计方案就不是一种特别合适的方案。那么我们产品平台便采取了一种更低成本更稳定的实时架构方案来解决上述问题,其主要思路:

  • 采用WAL机制保证上游系统写入磁盘的源数据不丢失,机器宕机重启保证让机器数据快速恢复到宕机前一致。
  • 所有的更新操作只会发生在内存索引,但内存索引不会无限扩大,满足系统设置阀值后就会刷入磁盘,一旦刷入磁盘的索引将不会发生重新打开(只存在标记删除操作),通过这种屏蔽重新打开磁盘索引的操作,也就解决了前面提到由于主索引频繁重新打开导致的查询实时性、资源占用峰值、Cache的命中率、FullGC的问题。
  • 内存索引直接刷磁盘生成子索引,不再去合并主索引,避免大的主索引需要重新打开,同时子索引的大小可控,基本在100MB之内,避免重新打开子索引非常慢。
  • 子索引数目增多,也就意味着冗余数据变多、查询遍历更多的文件,那么性能必定会造成影响,所以子索引必须通过一种合并策略进行合并优化。我们产品采取的策略由合并因子和阀值影响,例如:当相同大小的100MB子索引达到10个的时候,触发合并。虽然触发合并后,仍然会带来IO争用,但因为子索引体积小,所以合并优化时间较快,那么对搜索服务的影响基本不存在。另外当子索引合并生成的新子索引达到一定大小时候,合并策略将不会将其纳入下次待合并列表中,即永远不会再参与合并。

基于上述的思路,我将着重在实时更新处理、实时索引体系两个方面来跟大家介绍下我们产品。

实时更新处理

WAL日志

大型分布式系统中故障很常见,设想一下,如果内存索引没有刷写,服务器就宕机了。内存中没有写于硬盘的数据就会丢失。所以我们的分布式实时搜索产品应对的办法是在写内存索引之前先写入WAL(Write-Ahead Logging,预写式日志)。其写入流程如下:

  • 将WAL日志以追加写的方式写入磁盘日志文件中
  • 将WAL日志的修改操作作用到内存索引中
  •  返回操作成功或者失败

如上所示,在修改内存索引元素之前,要确保与这一个修改相关的操作日志必须要刷入磁盘中。如果检索服务器宕机,没有从RamIndex刷写入Disk的数据将可以通过回放WAL来恢复。而这个过程并不需要人为参与,检索节点内部机制中有恢复流程来处理。

批提交

一般而言搜索系统是需要将WAL日志刷入磁盘才可以构建内存索引的,但是如果每个事务都要求将日志立即刷入磁盘,系统的吞吐量将会很差。因此,对一致性要求很高的应用,需要立即刷入;相应地,对一致性要求不高的应用,可以考虑不要求立即刷入,首先将WAL日志缓存到内存缓存区中,定期刷入磁盘。但是这种做法有一个问题,如果搜索应用系统意外故障,可能丢失最后一部分更新操作。

批提交(Group Commit,如图1-2批处理流程图)技术是一种有效的优化手段。WAL日志首先写入到系统内容缓存区中:

  •  日志缓存区的数据量超过一定大小,比如128KB;
  • 距离上次刷入磁盘超过一定时间,比如10ms。

当满足以上两个条件中的某一个时,将日志缓存区中多个事务的操作一次性刷入磁盘,接着一次性将多个事务的修改操作逐个返回客户端操作结果。批提交技术保证了WAL日志成功刷入磁盘后,才返回操作结果保障数据的不丢失,虽然牺牲了写事务延时,但大大提高了系统吞吐量。

图1-2

图1-2

CheckPoint检查点

考虑数据写入需要实时可查,那么更新的数据都是在内存索引中,那么可能出现一些问题:

故障恢复时需要回放所有WAL,效率较低。如果WAL超过100GB,那么,故障恢复时间根本无法接受。另外内存有限,内存索引需要达到阀值后转储到磁盘。所以,我们需要在内存索引转储到磁盘的时候,记录checkpoint时刻的日志回放点,以后故障恢复只需要回放checkpoint时刻日志之后的WAL日志,如图1-3检查点方案流程图所示:

图1-3

图1-3

当机器发送重启,只需要重新加载subindexA、subindexB、subindexC的索引,并重放checkpointC之后的WAL日志,变可让数据恢复到宕机前一致。

 

实时索引体系

图1-4

图1-4

根据图1-4实时方案架构图我们详细说明下实时模式实现流程:

  • 更新操作都会在服务端以WAL落地磁盘
  •  服务端异步线程顺序消费WAL构建成内存索引(Ram-IndexA)
  •  内存索引(Ram-IndexA)大小达到内存阀值将转换角色为Ram-IndexB
  •  重新开辟新的内存索引(Ram-IndexA)负责当前WAL的消费
  •   Ram-IndexB内存索引直接刷入磁盘生成以Index前缀的子索引,如:Index_0,Index_1,Index_2….。
  •  防止索引碎片(Index_0,Index_1…)会越来越多从而影响性能,我们采取一种合并策略可以通过合并因子和索引大小来选出可以合并的小索引进行合并。
  •   达到阀值的子索引将不会在参与合并,那么系统运行一段较长时间后,索引碎片(子索引)也将会越来越多,那么系统可以通过重新做一次全量的方式来消除索引碎片带来的影响。

子索引合并策略

前面我们说到内存索引一旦达到阀值,将被刷入到磁盘,那么磁盘将会存在很多类似index_0、index_1、index2,index_3的索引碎片。如果不对这些索引碎片进行合并,那么随着这些索引碎片的增加,会导致搜索服务性能降低。所以我们的产品对索引碎片采取了一种合并策略对其进行定期合并。

图1-5-1

图1-5

如图1-5子索引合并流程图所示,内存索引刷入磁盘,将会依次递增的生成index_0,index_1,index_2的磁盘索引碎片。假设当前的合并因子是2,当合并管理器发现存在2个大小一致索引index_0,index_1的时候,变会触发合并操作:index_0和index_1合并成index_3,合并过程中index_0、index_1依然提供正常服务,当合并操作成功完成,即index_3生成完毕,并对外提供服务。接下来将index_0和index_1的资源引用计数减1,即当基于index_0、index1的查询访问线程结束时,index_0,index_1的资源引用计数为0、索引将正常关闭,这样一个索引碎片合并操作正常结束。但是如果合并过程出现宕机或者异常情况,即当前合并事务未正常结束,那么整个合并过程将会回滚,即index_3被清理,index_0,index_1正常提供服务。当然如果内存索引继续刷到磁盘生成了index_4、index_5,通过合并策略生成了index_6,这个时候发现index_3和index_6又满足了合并条件,那么index_3和index_6又会合并生成index_7。所以通过这种合并策略,小索引碎片逐步会被合并成大索引碎片,但是如果索引碎片越大,那么带来的合并代价也越大,我们需要设置一个合并阀值,凡是索引碎片达到指定文件大小阀值后,将不会进一步再参与合并,这样就很好的屏蔽了大索引碎片合并代价过大的问题。

 

全量索引构建

因为前面说到我们产生索引碎片,而这些索引碎片即使进行了碎片合并而减少碎片数,但是一旦当碎片达到一定大小后就不适合继续进行合并,否则合并代价很大,所以我们无法避免的会因为碎片问题而导致更新实时性和查询QPS性能损耗问题。所以我们的解决的办法就是通过一段时间对具体业务全部源数据进行一次构建全量索引DUMP工作,用构建好的新的全量主索引去替换原来老的主索引和磁盘索引,从而让实时更新、搜索服务性能恢复到最佳。

阿里的业务数据规模都很庞大,动辄就上10亿到百亿,那么我们如果使用Solr原生的基于检索服务节点的索引构建模式会带来2个很大问题:

  • 构建索引就是一个IO密集型的任务,而搜索服务也是IO密集型,那么两个任务如果在一台机器上并存,将会导致双方服务变得都不稳定。
  • 搜索业务数据规模大,导致传统原生构建索引的方式在几十亿数据量规模下所需要的时间特别长,即使深夜访问低峰期开始全量任务,也需要延续到白天甚至是访问高峰期还未结束,从而使得搜索服务出现频繁超时现象。

所以基于上述原因我们的搜索平台实现一个分布式全量索引任务调度框架来解决搜索业务全量索引构建的问题。

图1-6

图1-6

 

如图1-6 DUMP中心架构图所示简单描述下一个业务全量索引构建的流程:

  •  将一个具体业务相关的上下文信息以全量索引构建任务的形式提交给JobNode
  • JobNode根据TaskNode空闲程度,选择好若干个TaskNode并将全量任务下发到具体的TaskNode。JobNode根据了这些上下文信息把一个全量任务分解成若干个TaskNode进行,这样有效的用到分布式并行任务的优势来加速索引构建。
  • 被选择出来的若干个TaskNode根据授予任务的上下文信息,获取业务数据的来源类型和存储地址,(如数据库、Hadoop云梯),然后通过流的方式消费源数据内容并构建成索引。
  • 一个索引任务执行完毕后,将完成的全量索引根据指定的目标存储源进行回流(一般还是HDFS)。

那么通过这种离线的分布式索引构建中心具体为搜索业务带来什么呢,主要在以下几个方面体现:

  • 彻底隔离全量构建索引和搜索服务的耦合性,杜绝资源抢用情况。
  • 实现与业务细节无关的全量构建任务集群,能为接入搜索平台的所有业务进行全量索引构建服务,意味着极大提高机器综合利用率,不用为具体业务搭建具体的全量DUMP集群。
  • 索引构建和检索服务隔离后,DUMP任务节点(TaskNode)可以在最大化利用机器资源,即对底层索引构建细节深入优化,所以极大提升了海量数据索引构建速度。
  • DUMP中心快速构建海量数据索引并回流索引文件到存储中心的过程,为在线搜索服务无缝扩容和线上故障快速恢复提供了数据来源基础。

其他优化

我们产品在solr和Lucene上做了很多优化来适应一些业务的需求,本文篇幅有限,所以在这里我主要挑出一个比较有代表性的优化实践:Cache改造。

Cache改造

用过solr的同学们都知道所有的Cache都是由SolrIndexSearcher来管理的,如图1-7 Searcher结构图所示:

图1-7

图1-7

而在我们的实时模式下需要让更新的数据实时可见,那么必须近实时的用新的SolrIndexSearcher-new去替换SolrIndexSearcher-old。(如图1-7)而这样实时的替换 也就引发如下问题:

  • solrIndexSearcher的替换,意味着基于solrIndexSearcher层的Cache(如图1-7所示的4种Cache)全部失效,那么意味着毫秒级别会频繁有大空间的内存需要被垃圾回收,最终会触发频繁的FullGC。
  • 如果Cache配置还打开了预热功能(warm),那么新的SolrIndexSearcher在替换之前需要将其管理的新Cache进行预热。数据量如果较大,那么预热时间会较长,从而引发数据实时性可见问题。

所以基于如上的问题,终搜产品重新设计了一些Cache,将Cache的管理由SolrIndexSearcher迁移到IndexReader层中来,如图1-8  Cache结构图所示:

图1-8

图1-8

首先,先阐述下我们这种优化思路的前置条件,前文中提到我们内存索引会直接刷磁盘而不用合并到主索引中,这样在磁盘存在的主索引、子索引对应的内存视图对象IndexReader在任何时候都不需要重新打开,而以IndexReader管理的Cache一旦创建后将不会被失效,而需要涉及到预加载Cache的过程只是在刷入磁盘或者系统重新启动过程中一次将配置涉及到的Cache都预加载到内存中,那么之前存在的频繁失效导致GC、预加载慢引起实时性的若干问题都将不复存在。

所以通过将Cache从Searcher层迁移到IndexReader层的设计使得实时模式下的引擎在复杂的统计查询下性能也能得到很好的保证。

总结与展望

本文中我们深入的探讨了一种高稳定性实时搜索引擎系统实践,这些实践内容也依托于我们的产品服务于阿里众多业务线。而目前我们的产品搜索服务集群已经将近700台,接入业务范围也涵盖整个阿里集团。而这些业务特别是在数据量和访问量的成倍增长的情况下,我们产品更加需要关注

  • 不再需要为数据规模和访问规模增长而提心吊胆。
  • 更加合理利用机器资源,搜索服务集群吞吐量可以根据业务实际情况来动态调整。

归根结底其实这些要求是对搜索服务系统的易扩展提出了更高的要求,即如何提供一种无缝的在线扩容方案达到搜索服务吞吐量无上限的目标,而这个目标也正是我们产品目前正在重点关注的方向,而关于这块的内容希望有机会在新的文章中跟大家做深入探讨。

作者简介

柳明(花名:洪震),阿里技术专家,阿里一站式搜索服务平台TSearcher的负责人。目前关注于分布式、高性能、高稳定性的搜索服务领域。

分享到:
评论

相关推荐

    深入理解Luncen搜索引擎开发

    Lucene是用java实现的、成熟的开源项目,是著名的Apache Jakarta大家庭的一员,并且基于Apache软件许可 [ASF, License]。同样,Lucene是当前非常流行的、免费的Java信息搜索(IR)库。 适合对象:掌握JAVA基础 ...

    开源企业搜索引擎Solr的应用教程.doc

    适合于对数据库驱动的应用数据进行索引,尤其是替代传统的like ‘%expression%’来实现对varchar或clob等字段的索引,对于实现站内搜索是一种值得采纳的方案。但在分布式处理、接口封装上尚需要自己进行一定程度的...

    ASP.NET基于Ajax+Lucene构建搜索引擎的设计和实现(源代码+thesis).zip

    ASP.NET MVC是一种成熟的开发框架,它采用模型-视图-控制器的设计模式,将应用程序的逻辑和界面分离,提供了更好的代码组织和可维护性。我们选择ASP.NET MVC框架是因为它具有良好的可扩展性和灵活性,可以轻松地添加...

    Solr详细学习笔记.doc

    Solr 是一种可供企业使用的、基于 Lucene 的搜索服务器,它支持层面搜索、命中醒目显示和多种输出格式。在这篇分两部分的文章中,Lucene Java的提交人 Grant Ingersoll 将介绍 Solr 并向您展示如何轻松地将其表现...

    BLOG4J基于 java / jsp 技术构建的博客系统

     基于 Lucene 的全文检索技术,搜索性能优异  采用 DIV + CSS 设计网站模板页面,使用户可灵活定义博客模板  插件式开发架构,可持续扩展  URL结构优化,利于搜索引擎收录  静态化信息,分布式部署,性能可控

    基于文本聚类与分布式Lucene的知识检索 (2013年)

    针对传统集中式索引处理大规模数据的性能和效率问题,提出了一种基于文本聚类的检索算法。利用文本聚类算法改进现有的索引划分方案,根据查询与聚类结果的距离计算判断查询意图,缩减查询范围。实验结果表明,所提...

    海量数据引擎SF1R.zip

    社区目前绝大多数应用都已经采用Lucene,以及基于Lucene的一系列搜索解决方案比如Solr 和ElasticSearch,这些搜索方案经过十多年很多人的改进,在通用化方面已经非常优秀。那么 基于此,为什么还要再采用新的搜索...

    BLOG4J v2008.rar

    基于 Lucene 的全文检索技术,搜索性能优异 采用 DIV CSS 设计网站模板页面,使用户可灵活定义博客模板 插件式开发架构,可持续扩展 URL结构优化,利于搜索引擎收录 静态化信息,分布式部署,性能可控

    BLOG4J 博客源码

     基于 Lucene 的全文检索技术,搜索性能优异  采用 DIV + CSS 设计网站模板页面,使用户可灵活定义博客模板  插件式开发架构,可持续扩展  URL结构优化,利于搜索引擎收录  静态化信息,分布式部署,性能可控

    canal-elasticsearch:基于阿里巴的canal向elasticsearch中同步数据mysql数据的小工具

    是一个分布式搜索服务,提供Restful API,可以基于Lucene,采用多分片的方式保证数据安全,并提供自动重新分片的功能,github等大型的站点也采用Elasticsearch作为其搜索服务。 对于Elasticsearch,如果要在项目中...

    网络爬虫调研报告.doc

    Nutch是一个基于Lucene,类似Google的完整网络搜索引擎解决方案,基于Hadoop的 分布式处理模型保证了系统的性能,类似Eclipse的插件机制保证了系统的可客户化,而 且很容易集成到自己的应用之中。 Larbin 开发语言...

    网络爬虫调研报告(1).doc

    Nutch是一个基于Lucene,类似Google的完整网络搜索引擎解决方案,基于Hadoop的 分布式处理模型保证了系统的性能,类似Eclipse的插件机制保证了系统的可客户化,而 且很容易集成到自己的应用之中。 Larbin 开发语言...

    网络爬虫调研报告(2).doc

    Nutch是一个基于Lucene,类似Google的完整网络搜索引擎解决方案,基于Hadoop的 分布式处理模型保证了系统的性能,类似Eclipse的插件机制保证了系统的可客户化,而 且很容易集成到自己的应用之中. Larbin 开发语言:...

    网络爬虫调研报告.docx

    另外很吸引人的一点在于,它提供了一种插件框架,使得其对各种网页内容的解析、各种数据的采集、查询、集群、过滤等功能能够方便的进行扩 展,正是由于有此框架,使得 Nutch 的插件开发非常容易,第三方的插件也...

    分布式全文检索系统SolrCloud简介

    SolrCloud是Solr4.0版本开发出的具有开创意义的基于Solr和Zookeeper的分布式搜索方案,或者可以说,SolrCloud是Solr的一种部署方式。Solr可以以多种方式部署,例如单机方式,多机Master-Slaver方式,这些方式部署的...

    大型分布式网站架构与实践

     垂直化的搜索引擎在分布式系统中的使用,包括搜索引擎的基本原理、Lucene详细的使用介绍,以及基于Lucene的开源搜索引擎工具Solr的使用。  2.1 分布式缓存 60  2.1.1 memcache简介及安装 60  2.1.2 memcache ...

    ELK架构和Filebeat工作原理详解

    ELK不是一款软件,而是Elasticsearch、Logstash和Kibana三种...基于ApacheLucene构建,能对大容量的数据进行接近实时的存储、搜索和分析操作。通常被用作某些应用的基础搜索引擎,使其具有复杂的搜索功能; Logstash:

    基于jbpm与activiti的工作流平台技术架构介绍

    通过实施BPM方案让您的企业在运营效率、透明度、控制力和敏捷性方面迅速受益。 BPMX3是基于J2EE开源、轻量级的企业业务开发平台,基于代码重用、组件重用、业务逻辑重用、组装重用,结合快速开发平台中的工具,将...

Global site tag (gtag.js) - Google Analytics