您的位置:首页 > 其它

分布式系统基础-全文检索

2017-10-17 16:15 309 查看
1、什么是全文检索

我们每个人解除互联网都是从互联网搜索开始的,虽然大家常用的搜索引擎可能不同,搜索的关键词也可能不同,但是我们习惯经常在网上搜索的方式来快速学习技术并解决日常工作中所遇到的各种技术问题,如果没有互联网搜索引擎,那么恐怕我们将会有很多的人要失业了。如何在海量的网页信息中准确且快速的找到包含我们所搜索关键字的所有网页并合理的排序展示,这的确是一个很有挑战的问题。

除了我们日常工作中使用的搜索引擎,大量的互联网应用都离不开关键字搜索功能,不能提供关键字搜索功能的互联网应用基本没有生存的可能性,但奇怪的是,很多人从业多年,可能从来没有认真研究、学习过关键字搜索功能背后的实现技术,却天天将分布式事务这种古老又神秘的术语挂在嘴边。

要理解关键字搜索的价值,我们需要先了解关系型数据库索引的局限性。我们在SQL查询语句中使用like ‘%keyword%’这种查询条件时,数据库的索引是不起作用的。此时,搜索过程就变成了类似于一页一页翻书的遍历过程了,几乎全部是I/O操作,因此对性能的危害是极大的;如果需要对多个关键字进行模糊查询匹配,比如like ‘%keyword1%’ and like ‘%keyword2%’,此时查询效率也就可想而知了。关键字检索也称为全文检索,本质上是将一系列文本文件内容以“词组(关键字)”为单位进行分析并生成对应的索引记录,索引存储了关键字到文章的映射关系,映射关系中记录了关键字所在的文章编号、出现次数等关键信息,甚至包括了关键词在文章中出现的起始位置,于是我们有机会看到关键字“高亮显示”的查询结果页面。

关键字检索的第一步是对整个文档进行分词,得到文本中的每一个词,这对于英文来说毫无困难,因为英文词句中的单词之间是通过空格字符天然分开的,但是中文语句中的字与词是两个概念,所以中文分词就成了一个很大的问题,比如对“北京天安门”如何分词呢?是“北京、天安门”还是“北、京、天安、门”?解决这个问题的最好办法是采用中文词库配合中文分词法,配合开源Lucene使用的比较知名的中文分词有IK(IKAnalyzer)或庖丁(PaodingAnalyzer),直接配置就可以使用了。

2、Lucene

Java生态圈里最有名的全文检索开源项目是Apache Lucene,他已经超过10岁了(2001年成为Apache开源项目),目前Apache官方维护的Lucene相关的开源项目有如下几个。

Lucene Core:用Java编写的核心类库,提供了全文检索功能的API与SDK。

Solr:基于Lucene Core开发的高性能搜索服务,提供了REST API的高层封装接口。还提供了一个Web管理界面。

PyLucene:一个Python版的Lucene Core的高仿实现。

为了对一个文档进行索引,Lucene提供了5个基础类,分别是Document、Field、Index Writer、Analyzer和Directory。

首先,Document用来描述任何待搜索的文档,例如HTML页面、电子邮件或者文本文件。我们知道一个文档可能有多个属性,比如一封电子邮件有接受日期、发件人、收件人、主题、内容等属性,每个属性可以用一个Field对象来描述,此外我们也可以把一个Document对象想象成数据库中的一条记录,而每个Field对象就是这条记录的一个字段。

其次,在一个Document能被查询之前,我们需要对文档的内容进行分词以找出文档包含的关键字,这部分工作由Analyzer对象来实现,Analyzer把分词后的内容交给IndexWriter建立索引。IndexWriter是Lucene用来创建索引(Index)的核心类之一,它的作用是把每个Document对象加到索引中来,并且把索引对象持久化保存到Directory中。Directory代表了Lucene索引的存储位置,目前有两个实现:第一个是FSDirectory,表示在文件系统中存储;第二个是RAMDirectory,表示在内存中存储。

Lucene编程的整个流程如下图所示:



整个流程可以总结为三个独立步骤:

建模:根据被索引文档(原始文档)的结构与信息,建模对应的Document对象与相关的Lucene的索引字段(可以有多个索引),这一步类似于数据库建模。关键点之一是确定原始文档中有哪些信息需要作为Field存储到Document对象中。通常文档的ID或全路径文件名是要保留的(Filed.Store.YES),以便检索出结果后能让用户查看或下载原始文档。

收录:编写一段程序扫描每个待检索的目标文档,将其转换为对应的Document对象,并且创建相关的索引,最后存储到Lucene的索引仓库(Directory)中。这一步可以类比为初始化数据步骤(批量导入数据)。

检索:使用类似于SQL查询的Lucene API来编写我们的全文检索条件,从Lucene的索引仓库中查询符合条件的Document并且输出给用户,这一步完全类似于SQL查询。

Lucene还普遍与网络爬虫技术相结合,提供基于互联网资源的额全文检索功能,比如不少提供商品比较和最优购物的信息类网站,通过爬虫去抓取各个电商平台上的商品信息并录入Lucene索引库里,然后提供用户检索服务,如下图所示为此类型系统的一个典型架构图。



3、Solr

如果我们把Lucene与MySQL做对比,你会发现Lucene像MySQL的某个存储引擎,比如InnoDB或者MyISAM。Lucene只提供了最基本的全文检索相关的API,这不是一个独立的中间件,功能也不够丰富,API也比较复杂,不太方便使用。除此之外,Lucene还缺乏一个更为关键的特性–分布式,当我们要检索的文档数量特别庞大时,必然会遇到宕机的瓶颈,所以有了后来的Solr和ElasticSearch,他们都是基于Lucene的功能丰富的分布式全文检索中间件。

如下图所示为Solr的架构示意图,我们可以看到Solr在Lucene的基础上开发了很多企业级增强功能。

提供了一套强大的Data Schema来方便用户定义Document的结构;

增加了高效灵活的缓存功能;

增加了基于Web的管理界面以提供集中的配置管理功能;

此外Solr的索引数据还可以分片存储到多个节点上,并且通过多副本复制的方式来提升系统的可靠性。



Solr的分布式集群模式也被称为SolrCloud,这是一个灵活的分布式索引和检索系统。SolrCloud也是一种去中心化思想的分布式集群,集群中没有特殊的Master节点,而是依靠ZooKeeper来协调集群。SolrCloud中一个索引数据(Collection)可以被划分为多个分片(Shard)并存储在不同的节点上(Solr Core或者Core),在索引数据分片的同时,SolrCloud也可以实现分片的复制(Replication)功能以提升集群的可用性。SolrCloud集群的所有状态信息都放在ZooKeeper中统一维护,客户端访问SolrCloud集群时,首先要向ZooKeeper查询索引数据(Collection)所在的Core节点的地址列表,然后就可以连接到任意Core节点上来完成索引的所有操作(CRUD)了。

如下图所示给出了一个SolrCloud参考部署方案,本方案的索引数据(Collection)被分为两个分片,同时每个Shard分片的数据有3份,其中一份所在Core节点被称为Leader,其它两个Core节点被称为Replica。所有索引数据分布在8个Core中,他们位于3台独立的服务器上,所以其中任何一台机器宕机,都不会影响到系统的可用性。如果某个服务器在运行中宕机,那么SolrCloud会自动触发Leader的重新选举行为,具体通过ZooKeeper提供分布式锁功能来实现的。



之前我们说到SolrCloud中的每个Shard分片都是由一个Leader和N个Replica所组成的,而却客户端可以连接到任意一个Core节点上进行索引数据操作,那么,此时索引数据是如何实现多副本同步的内?下面给出了背后的答案。



如果客户端连接的Core不是Leader,此时节点会把请求转发给所在的Shard分片的Leader节点。

Leader会把数据(Document)路由到所在Shard分片的每个Replica节点上。

如果文档分片路由规则计算出目标Shard分片是另外一个分片,则Leader会把数据转发给该分片对应的Leader节点去处理。

SolrCloud采用了什么算法进行索引数据分片Shard呢?为了选择合适的分片算法,SolrCloud提出了一下两个关键要求。

分片算法的计算速度必须快,因为建立索引及访问索引的过程都频繁用到分片算法。

分片算法必须保证索引数据能均匀的分布到每个分片上,SolrCloud的查询时先分后汇总的过程,如果某个分片中的索引文档(Document)的数量远大于其他分片,那么查询此分片时所花的时间就会明显多于其他分片。片大于其他分片,也就是说最慢分片的查询速度决定了整体的查询速度。

基于以上两点考虑,SolrCloud选择了一致性Hash算法来实现索引分片。

Solr为了提供更实时的检索能力,提供了Soft Commit的新模式,在这种模式下仅把数据提交到内存,此时没有写入到磁盘索引文件中,但是索引Index可见,Solr会打开新的Searcher从而使得新的Document可见,同时,Solr会进行预热缓存及查询以使得缓存的数据也是可见的。为了保证数据最终会持久化到磁盘上,可以每1~10分钟自动触发Hard Commit而每秒钟自动触发Soft Commit。Soft Commit也是一把双刃剑,一方面Commit越频繁,查询的实时性越高,但同时增加了Solr的负荷,因为Commit越频繁越会生成小且多的索引段,于是Solr Merge的行为会更加频繁,在实际项目中建议根据业务的需求和忍受度来确定Soft Commit的频率。

4、ElasticSearch

ElasticSearch简称ES,它并不是Apache出品的,和Solr类似,也是基于Lucene的一个分布式索引服务中间件。在日志分析领域,以ElasticSearch为核心的ELK三件套(ELK Stack)成为事实上的标准。ELK其实并不是一款软件,而是一整套的解决方案,是三款软件ElasticSearch、Logstash和Kibana首字母的缩写。这三款软件都是开源软件,通常是配合使用的,又先后归于Elastic.co公司名下,故称为ELK Stack。在当前流行的日志管理平台,而在当前流行的基于Docker与Kubernetes的Paas平台中,ELK也是标配之一,非Apache出品的ES之所以能后来居上,与ELK的流行和影响力有着千丝万缕的联系。

实际上,所有分布式系统中最需要全文检索的地方就是日志模块了。如果尝试对节点数超过5个的分布式系统做Trouble Shooting,你就会明白日志集中收集并提供全文检索功能的重要性和紧迫性了。

如下所示是ELK Stack的一个架构组成图。



Logstash是一个有实时管道能力的数据收集引擎,用来收集日志并且作为索引数据写入ES集群中,我们可以开发自定义的日志采集探头并按照ELK的日志索引格式写入ES集群中;

Kibana则为ES提供了数据分析及数据可视化的Web平台,它可以在ES的索引中查找数据并生成各种维度的标图。

ES通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文检索变得简单,它提供了近时的索引、搜索、分析等功能。我们可以这样理解和描述ES。

分布式的实时文档存储,文档中的每个字段都可被索引并搜索。

分布式的实时分析搜索引擎。

可以拓展到上百台服务器,处理PB级结构化或非结构化数据。

ES中增加了Type这个概念,如果我们把Index这个类比为Database,那么Type就相当于Table,但是这个比喻又不是很恰当,因为我们知道不同Table的表结构完全不同,而一个Index中所有的Document的结构是高度一致的。ES中的Type其实就是Document中的一个特殊字段,用来在查询时过滤不同的Document,比如我们在做一个B2C的电商平台时,需要对每个店铺进行索引,而可以用Type区分不同的商铺。实际上,Type的使用场景非常少,这是我们需要注意的问题。

与SolrCloud一样,ES也是分布式系统,但是ES并没有采用ZooKeeper作为集群的协调者,而是自己实现了一套被称为Zen Directory的模块,该模块主要负责集群中节点自动发现和Master节点的选举。Master节点维护集群的全局状态,比如节点加入和离开时进行Shard的重新分配,而集群的节点之间则使用P2P的方式进行直接通信,不存在单点故障问题。ES不使用ZooKeeper的一个好处是部署和运维更加简单了,坏处就是可能出现所谓的脑裂问题。要预防脑裂问题,我们需要重视一个参数是discovery.zen.minimum_master_nodes,他决定了在选举Master的过程中需要有多少个节点通信,一个基本原则是要设置成N/2+1,N是集群中节点的数量。

ES集群与SolrCloud还有一个重大区别,即ES集群中的节点类型不止一种,有以下几种类型。

Master节点:它有资格被选举为主节点,控制整个集群。

Data节点:该节点保存索引数据并执行相关操作,例如增删改查、搜索及聚合。

Load balance节点:该节点只能处理路由信息,处理搜索及分发索引操作等,从本质上来说该节点的表现等同于智能负载平衡器。Load balance节点在较大的集群中是非常有用的,Load balance节点加入集群后可以得到集群的状态,并可以根据集群的状态直接路由请求。

Tribe节点:这是一个特殊的Load balance节点,他可以链接多个集群,在所有链接的集群上执行搜索和其他操作。

Ingest节点:是ES5.0新增的节点类型,该节点大大简化了以往ES集群中添加数据的复杂度。

如下所示是Tribe节点链接多个ES集群展示日志的ELK部署方案,据说魅族就采用了这种方案来解决各个IDC机房日志的集中展示问题。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: