ES近实时搜索原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES近实时搜索原理相关的知识,希望对你有一定的参考价值。

参考技术A Segment(段):Lucene里面的一个数据集概念
提交点文件:有一个列表存放着所有已知的所有段
ES底层是基于Lucene,最核心的概念就是Segment(段),每个段本身就是一个倒排索引。
ES中的Index由多个段的集合和commit point(提交点)文件组成。

提交点文件中有一个列表存放着所有已知的段,下面是一个带有1个提交点和3个段的Index示意图:

Doc新增提交主要过程如下:
一、写入磁盘后可见:
1、Doc写入Buffer
Doc会先被搜集到内存中的Buffer内,这个时候还无法被搜索到,如下图所示:

(1)创建一个新段,作为一个追加的倒排索引,写入到磁盘(文件系统缓存)
(2)将新的包含新段的Commit Point(提交点)写入磁盘(文件系统缓存)
(3)磁盘进行fsync,主要是将文件系统缓存中等待的写入操作全部物理写入到磁盘,保证数据不会在发生错误时丢失
(4)这个新的段被开启, 使得段内文档对搜索可见
(5)将内存中buffer清除,又可以把新的Doc写入buffer了
下面展示了这个过程完成后的段和提交点的状态:

为了数据安全,每次的索引变更都最好要立刻刷盘, 所以 Commit 操作意味着将Segment 合并并写入磁盘。保证内存数据尽量不丢。刷盘是很重的 IO 操作, 所以为了机器性能和近实时搜索, 并不会刷盘那么及时。

新文档被索引意味着文档会被首先写入内存 buffer 和 translog 文件。每个 shard 都对应一个 translog 文件。

在 elasticsearch 中, _refresh操作默认每秒执行一次,意味着将内存 buffer 的数据写入到一个新的Segment 中,这个时候索引变成了可被检索的。

Flush操作意味着将内存buffer的数据全都写入新的Segments中,并将内存中所有的Segments全部刷盘,并且清空translog日志的过程。

1、refresh:
Lucene支持对新段写入和打开 - 可以使文档在没有完全刷入硬盘的状态下就能对搜索可见,而且是一个开销较小的操作,可以频繁进行。
下面是一个已经将Docs刷入段但还没有完全提交的示意图:

2、translog
为了避免在两次commit操作间隔时间发生异常导致Doc丢失,ES中采用了一个事务日志记录每次对ES的操作。加上translog后新增文档流程如下:
文档被添加到buffer同时追加到translog,如图:

下面示意图展示了这个状态:

4、flush
flush就是执行commit清空、干掉老translog的过程。默认每个分片30分钟或者是translog过于大的时候自动flush一次。可以通过flush API手动触发,但是只会在重启节点或关闭某个索引的时候这样做,因为这可以让未来ES恢复的速度更快(translog文件更小)。
三、 ES对Doc删除的处理
(1)删除一个ES文档不会立即从磁盘上移除,它只是被标记成已删除。因为段是不可变的,所以文档既不能从旧的段中移除,旧的段也不能更新以反映文档最新的版本。
ES的做法是,每一个提交点包括一个.del文件(还包括新段),包含了段上已经被标记为删除状态的文档。所以,当一个文档被做删除操作,实际上只是在.del文件中将该文档标记为删除,依然会在查询时被匹配到,只不过在最终返回结果之前会被从结果中删除。ES将会在用户之后添加更多索引的时候,在后台进行要删除内容的清理。
(2)Doc删除与段合并的关系
通过每秒自动刷新创建新的段,用不了多久段的数量就爆炸了,每个段消费大量文件句柄,内存,cpu资源。更重要的是,每次搜索请求都需要依次检查每个段。段越多,查询越慢。
ES通过后台合并段解决这个问题。ES利用段合并的时机来真正从文件系统删除那些version较老或者是被标记为删除的文档。被删除的文档(或者是version较老的)不会再被合并到新的更大的段中。
ES对一个不断有数据写入的索引处理流程如下:
索引过程中,refresh会不断创建新的段,并打开它们。
合并过程会在后台选择一些小的段合并成大的段,这个过程不会中断索引和搜索。合并过程如图:

两个已提交的段 和一个未提交的段合并为一个更大的段。从上图可以看到,段合并之前,旧有的Commit和没Commit的小段皆可被搜索。
(3)段合并后的操作:
a、新的段flush到硬盘
b、编写一个包含新段的新提交点,并排除旧的较小段。
c、新的段打开供搜索
d、旧的段被删除
合并完成后新的段可被搜索,旧的段被删除,如下图所示:

注:
什么情况下要强制刷新:
1、reindex后,手动修改refresh,由-1(不刷新)改为想要的刷新值
2、在读多,写少时,可以强制不刷新,因为每写入一条数据就会产生一个新段,查询时就会查一次,降低效率
3、即时性要求高,:如广告立马需要被看到,需要手动强制刷新

ES: 架构及原理

     Elasticsearch 是一个兼有搜索引擎和NoSQL数据库功能的开源系统,基于Java/Lucene构建,可以用于全文搜索,结构化搜索以及近实时分析。可以说Lucene是当今最先进,最高效的全功能开源搜索引擎框架。 说明: Lucene:只是一个框架,要充分利用它的功能,需要使用JAVA,并且在程序中集成Lucene,学习成本高,Lucene确实非常复杂。 Elasticsearch 是 面向文档型数据库,这意味着它存储的是整个对象或者 文档,它不但会存储它们,还会为他们建立索引,这样你就可以搜索他们了

目录:

  • 应用场景
  • solr VS ES
  • 核心概念
  • ES模块结构
  • 分片示例

应用场景


  • 站内搜索:主要和 Solr 竞争,属于后起之秀
  • NoSQL json文档数据库:主要抢占 Mongo 的市场,它在读写性能上优于 Mongo ,同时也支持地理位置查询,还方便地理位置和文本混合查询,属于歪打正着 (对比测试参见:http://blog.quarkslab.com/mongodb-vs-elasticsearch-the-quest-of-the-holy-performances.html
  • 监控:统计以及日志类时间序的数据的存储和分析以及可视化,这方面是引领者
  • 国外:Wikipedia使用 ES 提供全文搜索并高亮关键字、StackOverflow结合全文搜索与地理位置查询、Github使用Elasticsearch检索1300亿行的代码
  • 国内:百度(在casio、云分析、网盟、预测、文库、直达号、钱包、风控等业务上都应用了ES,单集群每天导入30TB+数据,总共每天60TB+)、新浪 (见大数据架构--log,阿里巴巴、腾讯等公司均有对ES的使用
  • 使用比较广泛的平台ELK(ElasticSearch, Logstash, Kibana)

solr VS ES


  • Solr是Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。
  • Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。
  • Solr是用Java编写、运行在Servlet容器(如 Apache Tomcat 或Jetty)的一个独立的全文搜索服务器。 Solr采用了 Lucene Java 搜索库为核心的全文索引和搜索,并具有类似REST的HTTP/XML和JSON的API。
  • Solr强大的外部配置功能使得无需进行Java编码,便可对 其进行调整以适应多种类型的应用程序。Solr有一个插件架构,以支持更多的高级定制
  • Elasticsearch 与 Solr 的比较总结
    1. 二者安装都很简单
    2. Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能
    3. Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式
    4. Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供
    5. Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch
    6. Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用

核心概念


  • 集群(Cluster): 包含一个或多个具有相同 cluster.name 的节点.
    1. 集群内节点协同工作,共享数据,并共同分担工作负荷。
    2. 由于节点是从属集群的,集群会自我重组来均匀地分发数据. 
    3. cluster Name是很重要的,因为每个节点只能是群集的一部分,当该节点被设置为相同的名称时,就会自动加入群集。
    4. 集群中通过选举产生一个mater节点,它将负责管理集群范畴的变更,例如创建或删除索引,添加节点到集群或从集群删除节点。master 节点无需参与文档层面的变更和搜索,这意味着仅有一个 master 节点并不会因流量增长而成为瓶颈。任意一个节点都可以成为 master 节点。我们例举的集群只有一个节点,因此它会扮演 master 节点的角色。
    5. 作为用户,我们可以访问包括 master 节点在内的集群中的任一节点。每个节点都知道各个文档的位置,并能够将我们的请求直接转发到拥有我们想要的数据的节点。无论我们访问的是哪个节点,它都会控制从拥有数据的节点收集响应的过程,并返回给客户端最终的结果。这一切都是由 Elasticsearch 透明管理的
  • 节点(node): 一个节点是一个逻辑上独立的服务,可以存储数据,并参与集群的索引和搜索功能, 一个节点也有唯一的名字,群集通过节点名称进行管理和通信.
  • 索引(Index): 索引与关系型数据库实例(Database)相当。索引只是一个 逻辑命名空间,它指向一个或多个分片(shards),内部用Apache Lucene实现索引中数据的读写
  • 文档类型(Type):相当于数据库中的table概念。每个文档在ElasticSearch中都必须设定它的类型。文档类型使得同一个索引中在存储结构不同文档时,只需要依据文档类型就可以找到对应的参数映射(Mapping)信息,方便文档的存取
  • 文档(Document) :相当于数据库中的row, 是可以被索引的基本单位。例如,你可以有一个的客户文档,有一个产品文档,还有一个订单的文档。文档是以JSON格式存储的。在一个索引中,您可以存储多个的文档。请注意,虽然在一个索引中有多分文档,但这些文档的结构是一致的,并在第一次存储的时候指定, 文档属于一种 类型(type),各种各样的类型存在于一个 索引 中。你也可以通过类比传统的关系数据库得到一些大致的相似之处:
    关系数据库       ⇒ 数据库 ⇒ 表    ⇒ 行    ⇒ 列(Columns)
    Elasticsearch  ⇒ 索引   ⇒ 类型  ⇒ 文档   ⇒ 字段(Fields)
  • 模拟示意图如:
  • 技术图片
  • Mapping: 相当于数据库中的schema,用来约束字段的类型,不过 Elasticsearch 的 mapping 可以自动根据数据创建
  • 分片(shard) :是 工作单元(worker unit) 底层的一员,用来分配集群中的数据,它只负责保存索引中所有数据的一小片。
    1. 分片是一个独立的Lucene实例,并且它自身也是一个完整的搜索引擎。
    2. 文档存储并且被索引在分片中,但是我们的程序并不会直接与它们通信。取而代之,它们直接与索引进行通信的
    3. 把分片想象成一个数据的容器。数据被存储在分片中,然后分片又被分配在集群的节点上。当你的集群扩展或者缩小时,elasticsearch 会自动的在节点之间迁移分配分片,以便集群保持均衡
    4. 分片分为 主分片(primary shard) 以及 从分片(replica shard) 两种。在你的索引中,每一个文档都属于一个主分片
    5. 从分片只是主分片的一个副本,它用于提供数据的冗余副本,在硬件故障时提供数据保护,同时服务于搜索和检索这种只读请求
    6. 索引中的主分片的数量在索引创建后就固定下来了,但是从分片的数量可以随时改变。
    7. 一个索引默认设置了5个主分片,每个主分片有一个从分片对应

ES模块结构


  • 模块结构图如下
  • 技术图片
  • Gateway: 代表ES的持久化存储方式,包含索引信息,ClusterState(集群信息),mapping,索引碎片信息,以及transaction log等
    1. 对于分布式集群来说,当一个或多个节点down掉了,能够保证我们的数据不能丢,最通用的解放方案就是对失败节点的数据进行复制,通过控制复制的份数可以保证集群有很高的可用性,复制这个方案的精髓主要是保证操作的时候没有单点,对一个节点的操作会同步到其他的复制节点上去。
    2. ES一个索引会拆分成多个碎片,每个碎片可以拥有一个或多个副本(创建索引的时候可以配置),这里有个例子,每个索引分成3个碎片,每个碎片有2个副本,如下:
      $ curl -XPUT http://localhost:9200/twitter/ -d ‘
      index :
          number_of_shards : 3
          number_of_replicas : 2
    3. 每个操作会自动路由主碎片所在的节点,在上面执行操作,并且同步到其他复制节点,通过使用“non blocking IO”模式所有复制的操作都是并行执行的,也就是说如果你的节点的副本越多,你网络上的流量消耗也会越大。复制节点同样接受来自外面的读操作,意义就是你的复制节点越多,你的索引的可用性就越强,对搜索的可伸缩行就更好,能够承载更多的操作

    4. 第一次启动的时候,它会去持久化设备读取集群的状态信息(创建的索引,配置等)然后执行应用它们(创建索引,创建mapping映射等),每一次shard节点第一次实例化加入复制组,它都会从长持久化存储里面恢复它的状态信息
  • Lucence Directory:  是lucene的框架服务发现以及选主 ZenDiscovery: 用来实现节点自动发现,还有Master节点选取,假如Master出现故障,其它的这个节点会自动选举,产生一个新的Master
    1. 它是Lucene存储的一个抽象,由此派生了两个类:FSDirectory和RAMDirectory,用于控制索引文件的存储位置。使用FSDirectory类,就是存储到硬盘;使用RAMDirectory类,则是存储到内存
    2. 技术图片
    3. 一个Directory对象是一份文件的清单。文件可能只在被创建的时候写一次。一旦文件被创建,它将只被读取或者删除。在读取的时候进行写入操作是允许的
  • Discovery
    1. 节点启动后先ping(这里的ping是 Elasticsearch 的一个RPC命令。如果 discovery.zen.ping.unicast.hosts 有设置,则ping设置中的host,否则尝试ping localhost 的几个端口, Elasticsearch 支持同一个主机启动多个节点)
    2. Ping的response会包含该节点的基本信息以及该节点认为的master节点
    3. 选举开始,先从各节点认为的master中选,规则很简单,按照id的字典序排序,取第一个
    4. 如果各节点都没有认为的master,则从所有节点中选择,规则同上。这里有个限制条件就是 discovery.zen.minimum_master_nodes,如果节点数达不到最小值的限制,则循环上述过程,直到节点数足够可以开始选举
    5. 最后选举结果是肯定能选举出一个master,如果只有一个local节点那就选出的是自己
    6. 如果当前节点是master,则开始等待节点数达到 minimum_master_nodes,然后提供服务, 如果当前节点不是master,则尝试加入master.
    7. ES支持任意数目的集群(1-N),所以不能像 Zookeeper/Etcd 那样限制节点必须是奇数,也就无法用投票的机制来选主,而是通过一个规则,只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是一致的. 但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题,大多数解决方案就是设置一个quorum值,要求可用节点必须大于quorum(一般是超过半数节点),才能对外提供服务。而 Elasticsearch 中,这个quorum的配置就是 discovery.zen.minimum_master_nodes 。
  • memcached
    1. 通过memecached协议来访问ES的接口,支持二进制和文本两种协议.通过一个名为transport-memcached插件提供
    2. Memcached命令会被映射到REST接口,并且会被同样的REST层处理,memcached命令列表包括:get/set/delete/quit
  • River : 代表es的一个数据源,也是其它存储方式(如:数据库)同步数据到es的一个方法。它是以插件方式存在的一个es服务,通过读取river中的数据并把它索引到es中,官方的river有couchDB的,RabbitMQ的,Twitter的,Wikipedia的,river这个功能将会在后面的文件中重点说到

分片示例


  • 启用一个既没有数据,也没有索引的单一节点, 如下图
  • 技术图片
  • 在空的单节点集群中上创建一个叫做 blogs 的索引,设置3个主分片和一组从分片(每个主分片有一个从分片对应),代码如下:
    技术图片
    PUT /blogs
    {
       "settings" : {
          "number_of_shards" : 3,
          "number_of_replicas" : 1
       }
    }
    技术图片
  •  集群示例图如下: (此时集群健康状态为: yellow   三个从分片还没有被分配到节点上)

  • 技术图片    技术图片
  • 主分片(primary shards) 启动并且运行了,这时集群已经可以成功的处理任意请求,但是 从分片(replica shards) 没有完全被激活。事实上,当前这三个从分片都处于 unassigned(未分配)的状态,它们还未被分配到节点上。在同一个节点上保存相同的数据副本是没有必要的,如果这个节点故障了,就等同于所有的数据副本也丢失了
  • 启动第二个节点,配置第二个节点与第一个节点的 cluster.name 相同(./config/elasticsearch.yml文件中的配置),它就能自动发现并加入到第一个节点的集群中,如下图:
  • 技术图片   技术图片
  • cluster-health 的状态为 green,这意味着所有的6个分片(三个主分片和三个从分片)都已激活,文档在主节点和从节点上都能被检索
  • 随着应用需求的增长,启动第三个节点进行横向扩展,集群内会自动重组,如图
  • 技术图片
  • 在 Node 1 和 Node 2 中分别会有一个分片被移动到 Node 3 上,这样一来,每个节点上就都只有两个分片了。这意味着每个节点的硬件资源(CPU、RAM、I/O)被更少的分片共享,所以每个分片就会有更好的性能表现
  • 一共有6个分片(3个主分片和3个从分片),因此最多可以扩展到6个节点,每个节点上有一个分片,这样每个分片都可以使用到所在节点100%的资源了
  • 主分片的数量在索引创建的时候就已经指定了,实际上,这个数字定义了能存储到索引中的数据最大量(具体的数量取决于你的数据,硬件的使用情况)。例如,读请求——搜索或者文档恢复就可以由主分片或者从分片来执行,所以当你拥有更多份数据的时候,你就拥有了更大的吞吐量
  • 从分片的数量可以在运行的集群中动态的调整,这样我们就可以根据实际需求扩展或者缩小规模。接下来,我们来增加一下从分片组的数量:
    PUT /blogs/_settings
    {
       "number_of_replicas" : 2
    }
  •  现在 blogs 的索引总共有9个分片:3个主分片和6个从分片, 又会变成一个节点一个分片的状态了,最终得到了三倍搜索性能的三节点集群

  • 技术图片
  • 说明:仅仅是在同样数量的节点上增加从分片的数量是根本不能提高性能的,因为每个分片都有访问系统资源的权限。你需要升级硬件配置以提高吞吐量。
  • 尝试一下,把第一个节点杀掉,我们的集群就会如下图所示:
  • 技术图片
  • 被杀掉的节点是主节点,主分片 1 和 2 在我们杀掉 Node 1 后就丢失了,我们的索引在丢失主节点的时候是不能正常工作的。如果我们在这个时候检查集群健康状态,将会显示 red:存在不可用的主节点
  • 而为了集群的正常工作必须需要一个主节点,所以首先进行的进程就是从各节点中选择了一个新的主节点:Node 2
  • 新的主节点所完成的第一件事情就是将这些在 Node 2 和 Node 3 上的从分片提升为主分片,然后集群的健康状态就变回至 yellow。这个提升的进程是瞬间完成了,就好像按了一下开关
  • 如果再次杀掉 Node 2 的时候,我们的程序依旧可以在没有丢失任何数据的情况下运行,因为 Node 3 中依旧拥有每个分片的备份
  • 如果我们重启 Node 1,集群就能够重新分配丢失的从分片,这样结果就会与三节点两从集群一致。如果 Node 1 依旧还有旧节点的内容,系统会尝试重新利用他们,并只会复制在故障期间的变更数据

以上是关于ES近实时搜索原理的主要内容,如果未能解决你的问题,请参考以下文章

ES: 架构及原理

ES: 架构及原理

ElasticSearch探索之路集群与分片:选举动态更新近实时搜索事务日志段合并

ElasticSearch探索之路集群与分片:选举动态更新近实时搜索事务日志段合并

ElasticSearch探索之路集群与分片:选举动态更新近实时搜索事务日志段合并

es分布式架构原理