• ES之1:基本概念及原理


    一、ES 架构

    1、ElasticSearch
    ElasticSearch是个开源的分布式的搜索引擎,它可以近乎实时的存储、检索数据;

    Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

    ElasticSearch提供javaAPI,使用者可以通过javaAPI调用,但是7.0以后不会提供普通javaAPI,需要使用高级APIrest-high-level调用。

    2、Lucene

    说到ES就不得不提Lucene,Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的搜索引擎,而且一个全文搜索引擎的架构。

    Lucene之所以比传统的关系型数据库检索的效率高,因为其使用的事倒排索引,这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。

     3、elasticsearch的基本概念:

    NRT:Near Realtime,近实时,有两个层面的含义,一是从写入一条数据到这条数据可以被搜索,有一段非常小的延迟(大约1秒左右),二是基于Elasticsearch的搜索和分析操作,耗时可以达到秒级。

    集群(cluster):代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。

    分片(shard):代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。ES自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节.

    副本(replica):索引副本,完全拷贝shard的内容,shard与replica的关系可以是一对多,同一个shard可以有一个或多个replica,并且同一个shard下的replica数据完全一样,replica作为shard的数据拷贝,承担以下三个任务:

    1. shard故障或宕机时,其中一个replica可以升级成shard。
    2. replica保证数据不丢失(冗余机制),保证高可用。
    3. replica可以分担搜索请求,提升整个集群的吞吐量和性能。额外的副本能给带来更大的容量, 更高的呑吐能力及更强的故障恢复能力。

    shard的全称叫primary shard,replica全称叫replica shard,primary shard数量在创建索引时指定,后期不能修改,replica shard后期可以修改。默认每个索引的primary shard值为5,replica shard值为1,含义是5个primary shard,5个replica shard,共10个shard。
    因此Elasticsearch最小的高可用配置是2台服务器。

    Recovery:代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

    节点(node): 单个 ElasticSearch 实例. 通常一个节点运行在一个隔离的容器或虚拟机中

    索引(index): 索引,具有相同结构的文档集合,类似于关系型数据库的数据库实例(6.0.0版本type废弃后,索引的概念下降到等同于数据库表的级别)。一个集群里可以定义多个索引,如客户信息索引、商品分类索引、商品索引、订单索引、评论索引等等,分别定义自己的数据结构。
    索引命名要求全部使用小写,建立索引、搜索、更新、删除操作都需要用到索引名称。

    type:类型,原本是在索引(Index)内进行的逻辑细分,但后来发现企业研发为了增强可阅读性和可维护性,制订的规范约束,同一个索引下很少还会再使用type进行逻辑拆分(如同一个索引下既有订单数据,又有评论数据),因而在6.0.0版本之后,此定义废弃。

    Document:文档,Elasticsearch最小的数据存储单元,JSON数据格式,类似于关系型数据库的表记录(一行数据),结构定义多样化,同一个索引下的document,结构尽可能相同。

    gateway:-代表es索引的持久化存储方式,es默认是先把索引放到内存中,当内存满了的时候再持久化到硬盘。当这个es集群关闭或者重启时就会从gateway中读取索引数据。gateway支持多种存储,默认本地磁盘文件,hadoop 中hdfs 等

    discovery.zen:-代表es自动发现同网段节点机制,它先通过广播需要存在的节点,再通过多播协议进行节点之间通信,同时支持点对点的交互。

    -如果集群不同网段节点,禁用自动发现机制 discovery.zen.ping.multicast.enabled:false

    设置新节点在启动时能够被发现的主节点列表。 discovery.zen.ping.multicast.unicast.hosts:["192.169.1.1","192.169.1.2"]

    transport:-代表内部节点或集群与客户端的交互方式,默认内部使用的TCP协议进行交互。同时支持https(json),merchand,kafka,zeroMq等

    settings修改索引库默认配置

    mapping索引库的索引字段名称和数据进行定义

        如上图,有集群两个节点,并使用了默认的分片配置. ES自动把这5个主分片分配到2个节点上, 而它们分别对应的副本则在完全不同的节点上。其中 node1 有某个索引的分片1、2、3和副本分片4、5,node2 有该索引的分片4、5和副本分片1、2、3。

    当数据被写入分片时,它会定期发布到磁盘上的不可变的 Lucene 分段中用于查询。随着分段数量的增长,这些分段会定期合并为更大的分段。 此过程称为合并。 由于所有分段都是不可变的,这意味着所使用的磁盘空间通常会在索引期间波动,因为需要在删除替换分段之前创建新的合并分段。 合并可能非常耗费资源,特别是在磁盘I / O方面。

        分片是 Elasticsearch 集群分发数据的单元。 Elasticsearch 在重新平衡数据时可以移动分片的速度,例如发生故障后,将取决于分片的大小和数量以及网络和磁盘性能。

        注1:避免使用非常大的分片,因为这会对群集从故障中恢复的能力产生负面影响。 对分片的大小没有固定的限制,但是通常情况下很多场景限制在 50GB 的分片大小以内。

        注2:当在ElasticSearch集群中配置好你的索引后, 你要明白在集群运行中你无法调整分片设置. 既便以后你发现需要调整分片数量, 你也只能新建创建并对数据进行重新索引(reindex)(虽然reindex会比较耗时, 但至少能保证你不会停机).
        主分片的配置与硬盘分区很类似, 在对一块空的硬盘空间进行分区时, 会要求用户先进行数据备份, 然后配置新的分区, 最后把数据写到新的分区上。

        注3:尽可能使用基于时间的索引来管理数据保留期。 根据保留期将数据分组到索引中。 基于时间的索引还可以轻松地随时间改变主分片和副本的数量,因为可以更改下一个要生成的索引。

    节点类型

    1. 候选主节点(Master-eligible node)
      一个节点启动后,就会使用Zen Discovery机制去寻找集群中的其他节点,并与之建立连接。集群中会从候选主节点中选举出一个主节点,主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。Elasticsearch中的主节点的工作量相对较轻,用户的请求可以发往任何一个节点,由该节点负责分发和返回结果,而不需要经过主节点转发。
      正常情况下,集群中的所有节点,应该对主节点的选择是一致的,即一个集群中只有一个选举出来的主节点。然而,在某些情况下,比如网络通信出现问题、主节点因为负载过大停止响应等等,就会导致重新选举主节点,此时可能会出现集群中有多个主节点的现象,即节点对集群状态的认知不一致,称之为脑裂现象。为了尽量避免此种情况的出现,可以通过discovery.zen.minimum_master_nodes来设置最少可工作的候选主节点个数,建议设置为(候选主节点数 / 2) + 1, 比如,当有三个候选主节点时,该配置项的值为(3/2)+1=2,也就是保证集群中有半数以上的候选主节点。
      候选主节点的设置方法是设置node.mater为true,默认情况下,node.mater和node.data的值都为true,即该节点既可以做候选主节点也可以做数据节点。由于数据节点承载了数据的操作,负载通常都很高,所以随着集群的扩大,建议将二者分离,设置专用的候选主节点。当我们设置node.data为false,就将节点设置为专用的候选主节点了。

    node.master = true
    node.data = false
    2. 数据节点(Data node)
      数据节点负责数据的存储和相关具体操作,比如CRUD、搜索、聚合。所以,数据节点对机器配置要求比较高,首先需要有足够的磁盘空间来存储数据,其次数据操作对系统CPU、Memory和IO的性能消耗都很大。通常随着集群的扩大,需要增加更多的数据节点来提高可用性。
      前面提到默认情况下节点既可以做候选主节点也可以做数据节点,但是数据节点的负载较重,所以需要考虑将二者分离开,设置专用的数据节点,避免因数据节点负载重导致主节点不响应。

    node.master = false
    node.data = true
    3. 客户端节点(Client node)
      按照官方的介绍,客户端节点就是既不做候选主节点也不做数据节点的节点,只负责请求的分发、汇总等等,也就是下面要说到的协调节点的角色。这样的工作,其实任何一个节点都可以完成,单独增加这样的节点更多是为了负载均衡。

    node.master = false
    node.data = false
    4. 协调节点(Coordinating node)
      协调节点,是一种角色,而不是真实的Elasticsearch的节点,你没有办法通过配置项来配置哪个节点为协调节点。集群中的任何节点,都可以充当协调节点的角色。当一个节点A收到用户的查询请求后,会把查询子句分发到其它的节点,然后合并各个节点返回的查询结果,最后返回一个完整的数据集给用户。在这个过程中,节点A扮演的就是协调节点的角色。毫无疑问,协调节点会对CPU、Memory要求比较高。

    工作原理

    以几个场景介绍一下Elasticsearch的工作原理。

    启动过程

    当Elasticsearch的node启动时,默认使用广播寻找集群中的其他node,并与之建立连接,如果集群已经存在,其中一个节点角色特殊一些,叫coordinate node(协调者,也叫master节点),负责管理集群node的状态,有新的node加入时,会更新集群拓扑信息。如果当前集群不存在,那么启动的node就自己成为coordinate node。
    node加入集群过程

    应用程序与集群通信过程

    虽然Elasticsearch设置了Coordinate Node用来管理集群,但这种设置对客户端(应用程序)来说是透明的,客户端可以请求任何一个它已知的node,如果该node是集群当前的Coordinate,那么它会将请求转发到相应的Node上进行处理,如果该node不是Coordinate,那么该node会先将请求转交给Coordinate Node,再由Coordinate进行转发,搓着各node返回的数据全部交由Coordinate Node进行汇总,最后返回给客户端。
    应用程序与Elasticsearch集群通信过程

    集群内node有效性检测

    正常工作时,Coordinate Node会定期与拓扑结构中的Node进行通信,检测实例是否正常工作,如果在指定的时间周期内,Node无响应,那么集群会认为该Node已经宕机。集群会重新进行均衡:

      1. 重新分配宕机的Node,其他Node中有该Node的replica shard,选出一个replica shard,升级成为primary shard。
      2. 重新安置新的shard。
      3. 拓扑更新,分发给该Node的请求重新映射到目前正常的Node上。

    分片与集群状态
      分片(Shard),是Elasticsearch中的最小存储单元。一个索引(Index)中的数据通常会分散存储在多个分片中,而这些分片可能在同一台机器,也可能分散在多台机器中。这样做的优势是有利于水平扩展,解决单台机器磁盘空间和性能有限的问题,试想一下如果有几十TB的数据都存储同一台机器,那么存储空间和访问时的性能消耗都是问题。

    前面提到,中心节点(master)是从一组候选人(master-eligible)中选举出来的,那设置多少个候选人是合理的?原则是要保证任何时候系统只有一个确定的master节点。考虑到一致性,只有被半数以上候选节点都认可的节点才能成为master节点,否则就会出现多主的情况。只有1个候选节点显然不能保证高可用;有2个时,半数以上(n/2+1)的个数也是2,任何一个出现故障就无法继续工作了;有3个时,半数以上的值仍然是2,恰好可以保证master故障或网络故障时系统可以继续工作。因此,3个dedicated master-eligible节点是最小配置,也是目前业界标配。

    通过API( http://localhost:9200/_cluster/health?pretty )可以查看集群的状态,通常集群的状态分为三种:

    Red,表示有主分片没有分配,某些数据不可用。
    Yellow,表示主分片都已分配,数据都可用,但是有复制分片没有分配。
    Green,表示主分片和复制分片都已分配,一切正常。

     Elasticsearch以REST API形式对外提供服务,数据写入与查询都会发送HTTP(S)请求到服务端,由负载均衡将请求分发到集群中的某个节点上(任何非dedicated master-eligible节点)。如下图所示,节点1收到请求后,会根据相关的元信息将请求分发到shard所在的节点(2和3)上进行处理,处理完成后,节点2和3会将结果返回给节点1,由节点1合并整理后返回给客户端。这里的节点1扮演着协调者的角色,称为coordinate节点,任何节点在收到请求后就开始发挥协调者的角色,直到请求结束。在实际使用中,可以根据需要增加一些专用的coordinate节点,用于性能调优。

     数据写入:

    1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点)
    2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)
    3)实际的node上的primary shard处理请求,然后将数据同步到replica node
    4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端。

    Elasticsearch写数据的底层原理

    1)先写入buffer,在buffer里的时候数据是搜索不到的;同时将数据写入translog日志文件。

    2)如果buffer快满了,或者到一定时间,就会将buffer数据refresh到一个新的segment file中,但是此时数据不是直接进入segment file的磁盘文件的,而是先进入os cache的。这个过程就是refresh。

    每隔1秒钟,es将buffer中的数据写入一个新的segment file,每秒钟会产生一个新的磁盘文件,segment file,这个segment file中就存储最近1秒内buffer中写入的数据。

    但是如果buffer里面此时没有数据,那当然不会执行refresh操作咯,每秒创建换一个空的segment file,如果buffer里面有数据,默认1秒钟执行一次refresh操作,刷入一个新的segment file中。

    操作系统里面,磁盘文件其实都有一个东西,叫做os cache,操作系统缓存,就是说数据写入磁盘文件之前,会先进入os cache,先进入操作系统级别的一个内存缓存中去。

    只要buffer中的数据被refresh操作,刷入os cache中,就代表这个数据就可以被搜索到了。

    为什么叫es是准实时的?NRT,near real-time,准实时。默认是每隔1秒refresh一次的,所以es是准实时的,因为写入的数据1秒之后才能被看到。

    可以通过es的restful api或者java api,手动执行一次refresh操作,就是手动将buffer中的数据刷入os cache中,让数据立马就可以被搜索到。

    只要数据被输入os cache中,buffer就会被清空了,因为不需要保留buffer了,数据在translog里面已经持久化到磁盘去一份了。

     

    数据读取

    Elasticsearch读取数据的过程

    1)客户端发送请求到任意一个node,成为coordinate node
    2)coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡
    3)接收请求的node返回document给coordinate node
    4)coordinate node返回document给客户端

    1.写入document时,每个document会自动分配一个全局唯一的id即doc id,同时也是根据doc id进行hash路由到对应的primary shard上。也可以手动指定doc id,比如用订单id,用户id。
    
    2.读取document时,你可以通过doc id来查询,然后会根据doc id进行hash,判断出来当时把doc id分配到了哪个shard上面去,从那个shard去查询

    Elasticsearch搜索数据过程

    es最强大的是做全文检索

    1)客户端发送请求到一个coordinate node
    2)协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard也可以
    3)query phase:每个shard将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果
    4)fetch phase:接着由协调节点,根据doc id去各个节点上拉取实际的document数据,最终返回给客户端

    搜索的底层原理:倒排索引

    三、分片大小如何影响性能

        在Elasticsearch中,每个查询在每个分片的单个线程中执行。 但是,可以并行处理多个分片,对同一分片也可以进行多个查询和聚合。
        这意味着,如果不涉及缓存,则最小查询延迟将取决于数据、查询类型以及分片的大小。 查询大量小的分片将使每个分片的处理速度更快,但是需要按顺序排队和处理更多的任务,它不一定比查询较少数量的较大分片更快。 如果存在多个并发查询,则拥有大量小分片也会降低查询吞吐量。
        从查询性能角度确定最大分片大小的最佳方法是使用实际数据和查询进行基准测试。 始终以查询和加载索引的节点在生产中需要处理的内容基准,因为优化单个查询可能会产生误导性结果。

    大规模以及日益增长的数据场景

    对大数据集, 我们非常鼓励你为索引多分配些分片--当然也要在合理范围内. 上面讲到的每个分片最好不超过30GB的原则依然使用.

    不过, 你最好还是能描述出每个节点上只放一个索引分片的必要性. 在开始阶段, 一个好的方案是根据你的节点数量按照1.5~3倍的原则来创建分片. 例如,如果你有3个节点, 则推荐你创建的分片数最多不超过9(3x3)个.

    随着数据量的增加,如果你通过集群状态API发现了问题,或者遭遇了性能退化,则只需要增加额外的节点即可. ES会自动帮你完成分片在不同节点上的分布平衡.

    再强调一次, 虽然这里我们暂未涉及副本节点的介绍, 但上面的指导原则依然使用: 是否有必要在每个节点上只分配一个索引的分片. 另外, 如果给每个分片分配1个副本, 你所需的节点数将加倍. 如果需要为每个分片分配2个副本, 则需要3倍的节点数. 更多详情可以参考基于副本的集群.

    四、如何管理分片大小

        当使用基于时间的索引时,通常每个索引都与固定的时间段相关联。 每天的索引非常常见,通常用于保存保留期短的或每日量大的数据。 这些允许以合适的粒度管理保留期,并且可以轻松调整日常基础量。 具有较长保留期的数据,特别是如果每日的量不能保证使用每天的索引,通常使用每周或每月的索引以保证分片大小。 这减少了随着时间的推移需要存储在集群中的索引和分片的数量。
        :如果使用基于时间的索引,这个时间是某个固定的时间段,那么需要根据数据的保留期限和预期的数据量来调整每个索引所覆盖的时间段,以达到目标分片的大小。也就是说,如果我们要确定最终分片的大小,则需要根据我们的数据保存的期限以及预估预期的数据量来调整我们索引需要按照天还是周还是月的时间来进行评估。
        当数据量可以合理预测并且变化缓慢时,具有固定时间间隔的基于时间的索引很有效。 如果索引快速变化,则很难保持统一的目标分片大小。为了能够更好地处理这种类型的场景,引入了 Rollover and Shrink API (https://www.jianshu.com/writer#/notebooks/27738831/notes/31623194) 。 这些为索引和分片的管理方式增加了很多灵活性,特别是对于基于时间的索引。
         Rollover and Shrink API 可以指定应包含的文档和索引的数量和/或应该向其写入最大期限的文档。 一旦超出其中一个标准,Elasticsearch 就可以触发创建新索引,无需停机即可完成写入。 可以切换到特定大小的新索引,而不是让每个索引覆盖特定的时间段,这使得可以更容易地为所有索引实现均匀的分片大小。如果需要更新数据,在使用此API时,事件的时间戳与其所处的索引之间不再存在明显的链接,这可能会使更新效率大大降低,因为每次更新都需要在搜索之前进行。
        :如果我们有基于时间的不可变数据,其中数据量可能会随时间发生显著变化,就可以考虑使用 Rollover API,通过动态更改每个索引所涵盖的时间段来实现最佳目标分片大小。 这提供了极大的灵活性,并且可以帮助避免在数据量不可预测时具有太大或太小的分片。
         Shrink API 允许我们将现有索引缩小为具有较少主分片的新索引。 如果在索引期间需要跨节点均匀扩展分片,但这会导致分片太小,一旦索引不再被索引,此 API 可用于减少主分片的数量。 这将生成更大的分片,更适合长期存储数据。
         如果需要让每个索引覆盖特定的时间段,并且希望能够在大量节点上扩展索引,请考虑使用 Shrink API 在索引不再编入索引时减少主分片的数量。 如果最初配置了太多分片,此 API 还可用于减少分片数量。






    参考:https://www.jianshu.com/p/297e13045605

    https://blog.csdn.net/zwgdft/java/article/details/54585644

    参考:https://blog.csdn.net/zwgdft/java/article/details/83619905

    参考:https://www.cnblogs.com/huangying2124/archive/2019/12/05/11986897.html

  • 相关阅读:
    requests库之处理响应
    requests库之自定义request
    requests库之请求异常处理
    requests库之带参请求
    BFS广度优先
    分治模板
    递归模板
    数据机构与算法学习(三)- 数组
    消息队列学习笔记
    数据结构与算法学习(二)
  • 原文地址:https://www.cnblogs.com/duanxz/p/10108296.html
Copyright © 2020-2023  润新知