Halo

A magic place for coding

0%

ElasticSearch 详解

ElasticSearch

   ElasticSearch(后文简称ES),是一个高可扩展的、开源的全文搜索和分析引擎,它允许存储、搜索、分析TB级别的数据,并且这个过程是近实时的。它在业界中被广泛应用,通常被用作底层引擎,为复杂的搜索功能提供动力。


Some Important Concepts

  我们先从几个ES最基本的概念开始入手,理解这些概念能够帮助你理解ES的工作过程,以及理解为何ES这么厉害。

近实时 Near-Real-Time

  这里是说ES的搜索是NRT的,我首先来说一下什么是Real-Time Search。我们定义搜索的时延为:某个新的文档从索引到可被搜索的时间。注意这里是说新的文档,也就是说,我们的文档集合新增了一篇文档后,需要多久的时间才能把它给搜索出来。如果是马上就能搜出来,那么就是实时的。而ES提供的是NRT,这里面的时间差默认是1s,让我们来仔细解读一下。

   在ES之前,搜索普遍的做法是按段搜索(per-segment search),但是速度非常慢,原因是磁盘的IO成为了瓶颈。新文档写入需要一个commit,commit一个新的segment到磁盘中需要一个fsync。而fsync的代价非常大,如果每写入一个文档就同步一次的话,性能就会大大地下降。

  现在要解决的问题就是:一个新的文档,怎么可以被更快地搜索到?思路很简单:Cache。在ES和磁盘之间,增加一层缓存,称为文件系统缓存(File System Cache)。在内存索引缓冲区(里面放着新文档的Lucene索引)中的文档会被写入到一个新的segment中,但是这个segment是放在Cache的,而不必马上commit。Lucene允许这个新的segment被写入和打开,使得segment包含的文档在未commit时已经对搜索可见。需要注意,从内存索引缓冲区写入到新segment,这一步并没有包含fsync到磁盘,所以代价大大地降低了。之后我们再使用fsync 同步到磁盘,这样就是开销比较大,也不影响文档的搜索效果,因为它已经在cache中了。

  但是别高兴的太早,这样的做法依然是有问题的。从内存索引缓冲区写入到segment这一步,通常称为refresh,尽管它的代价比fsync 小的多,但是还是会对性能造成影响,所以ES提供的默认refresh_interval是1秒,这也是为什么ES的搜索是近实时,因为有这1秒的延迟。

  当然我们可以在每新增一个文档的时候调用refresh的命令手动刷新,去确保每一个新的文档都可以被很快地搜索到,但是在生产环境中这个代价是非常大的。从工程的角度来看,我们应该接受ES的近实时性,毕竟在绝大多数场景下,用户是能够接受这个时延的。

集群 Cluster

  开篇的时候提到,ES是高可扩展的。可扩展一般有两方面,垂直扩展的是基于单个机器的性能及容量的提升,而这个是有限度的;水平扩展则是得益于机器的增加,这个理论上是无限的。ES的高扩展性是基于水平扩展的,使用集群的方式能够充分利用机器的增加去均衡搜索请求和文档存储,提供高性能的搜索服务。

  每一个ES集群包含1个或多个节点,用来存放数据,这些节点共同提供索引和搜索能力。区分集群的唯一标识是它们的名称,所以每一个节点是根据集群名称去判断它是属于哪一个集群的。

节点 Node

  一个运行中的ES实例称为一个节点,一个节点必须属于某一个集群。当节点进入或退出集群时,集群都将会重新平均分配所有的数据压力。

  一个集群内一定包含一个主节点,这个过程是通过选举产生。主节点管理集群范围内的所有变更,如索引、节点的增删。但是,主节点并不涉及到文档级别的变更和具体的搜索操作。所以,在拥有多个节点的集群中(1主多从),流量的增加不会使得主节点称为瓶颈。

  在集群中,所有的节点(包括主节点)都知道任意一篇文档所处的位置,并能够将请求转发到该文档所在的节点。同时,它也能从各个包含文档的节点中回收数据,返回给客户端,所以,对于用户来说,把搜索请求发送到任意一个节点都是可以的。

索引 Index

  简单来说,把ES的索引看作是关系型数据库的DB就很好理解了。索引是同类文档的集合,我们倾向于把比较相似的文档都放在一个索引。这个相似可以是内容特征的相似,也可以是时间的相近等。在一个集群中,索引的数量是可以任意的。

类型 Type

  把类型理解为关系型数据库的Table,类型是逻辑上的分区。

文档 Document

  文档理解为关系型数据库的一条record,一般以JSON格式存放。特别注意,尽管Document在物理上是存放在一个Index中,但是文档必须有一个Type!

分片(Shard)与副本(Replica)

  为什么要有分片?想一下这种情况,一个索引的存储量超过了一个节点的存储能力,我们就需要多个节点去存储,这也是前面提到的水平扩展能力。为了解决这个问题,ES提供了sharding的能力,允许用户把一个Index切分成多个shard,特别注意,shard是逻辑上的,真正物理上的只有Node。在创建Index的时候,可自定义个shard数量,每一个shard都是功能独立且完全的,能够分配到任意一个节点中。

  分片带来的好处有两个:

  1. 允许水平分割文档,支持分布式存储;
  2. 在多个节点中提供查询性能和吞吐量,提高整体搜索性能。

  为什么要有副本?和其他许多场景一样,副本最大的作用就是故障恢复。在网络环境中,有可能某个分片和节点无法访问,也有可能故障数据丢失,这个时候就需要用副本来恢复。副本这个概念是针对分片的,也就是说我们能够为一个分片创建一个或多个副本。原来的分片叫做主分片,copy出来的叫副分片。

  副本带来的优势:

  1. 故障恢复能力。因为主、副分片是不会存储在同一个节点中的,所以当某个节点或分片丢失后,ES能够继续使用;
  2. 在正常情况下,对于一个搜索请求,它能够搜索所有的副本,这样大大提高了搜索的性能。

   在创建Index时,用户能够指定Shard数量和Replica数量。当Index创建后,用户可以调整Replica的数量,但不能改变分片的数量。需要注意,分片数量限制了水平扩容的能力,如果某个索引只有1个分片,那么增加Node是无法扩容的,此时唯一的办法就是重建索引,而这个耗时是非常巨大的,所以在一开始设计的时候就要预估清楚。

  当然分片数量也不是越多越好,如果分片过多,集群中需要管理的云数据信息增多,master节点的压力就会加大,有可能成为瓶颈;同时集群中小文件过多,内存以及文件句柄的占用率很高,查询速度随之下降。因此,合理地根据业务需求建立Index,预估shard和replica的数量才是最佳实践方式。


Details

节点类型

  ES节点有多种类型:

  1. 主节点 master node

    在上面提到过,主节点的作用就是管理集群

  2. 数据节点 data node

    数据节点的作用是保存数据和执行相关的数据操作,如文档的增删查改、搜索、聚合结果

  3. 客户端节点 client node

    响应客户请求,专注于routing。

  4. 部落节点 tribe node

    特殊的客户端节点,可以连接多个集群,在所有连接的集群上执行搜索操作。

上述四种节点并不是完全互斥的,比如说,数据节点也可以是master节点或client节点,这样就身兼两职。但是在规模较大的集群中,master和client节点在一些峰值情况下有可能会崩溃,如果与data放在一起,则会使得共存的data也故障。data的故障恢复需要一定的时间和资源消耗,会造成短时间的延迟。所以最好的策略还是把client和master与data分离开来。一旦出现问题,master和client的恢复是瞬间的,用户几乎感知不到。同时把data分离出来,master对data资源的消耗也更加准确,便于做容量管理。

master节点选举

  在一个集群中需要有一个master节点,该节点的产生是通过选举产生的。基本的策略是:从具有master资格的节点中选id最小的节点作为master。

  选举节点的时机也很关键。当集群启动的时候,后台启动线程去ping集群中的节点,按照上述策略从具有master资格的节点中选举出最小的。当集群中现有的master离开集群,后台一直有一个线程定时去ping master节点,如果超过一定次数ping失败,就会重新进行master的选举。

避免集群脑裂

  在集群采用master-slave模式来管理分布式集群的时候,我们通常需要关注脑裂的问题。ES避免脑裂的策略是:过半原则。ES的配置中有一个参数discovery.zen.minimum_master_nodes,这个参数决定了在选举master过程中需要至少有几个节点在通信。我们通常设置$N/2+ 1$,其中$N$是集群中的节点数量。这样只要过半数的节点处于正常的通信状态,这个master才是有效的。


Reference

  1. ElasticSearch Near Real Time Search
  2. ES避免脑裂

Welcome to my other publishing channels