004、ElasticSearch的工作原理篇
本文最后更新于 67 天前,其中的信息可能已经过时,如有错误请发送邮件到wuxianglongblog@163.com

ElasticSearch的工作原理篇

一.正排索引和倒排索引

1.提出问题:分片底层是如何工作的?

    分片是ElasticSearch最小的工作单元,但是究竟什么是一个分片,它是如何工作的?

    传统的数据库每个字段存储单个值,但这对全文检索并不够,文本字段的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值的能力。

    最好的支持是一个字段多个值需求的数据结构是倒排索引。

1.正排索引(forward index),正向索引

    正排索引也称为正向索引,我们以MySQL为例,用id字段存储文章编号,用context存储文章内容,如下表所示。

    我们可以将id字段设置为主键索引,这样可以根据id的编号快速查询到文章的内容。

    但是我们要查询文章内容中包含了"Jason Yin"的词汇,这个时候就比较麻烦了,因为我们需要对全表

    进行模糊查询,而且还要注意查询的大小写是否会影响查询的准确率。

1633769521942

2.倒排索引(inverted index),反向索引

    ElasticSearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。倒排索引也称为反向索引。

    所谓的倒排索引就是根据关键字过滤我们需要查询的文档编号,我们以上面的MySQL数据库为例,可以得到下表的对应关系。

    当我们查询"Jason Yin"的词汇,他会对查询的条件进行分词"Jason"和"Yin",而这两个分词对应的编号均为"1002",那么就会直接将该文档编号返回。

    综上所述,倒排索引已经将"表"的关系给弱化了,在Elasticsearch 7.x中,Type的概念已经被删除了,如果你非要说存在一个Type,则默认为"_doc"。

    在倒排索引中有三个专业术语:
        词条:
            指的是索引中最小的存储和查询单元。说白了就是你想要查询的关键字(词)。
            在英文当中通常之一个单词,在中文当中通常指的是一个词组。
        词典:
            又称为字典,它是词条的集合,底层通常基于Btree+和HASHMap来实现。
        倒排表:
            倒排表记录了词条出现在什么位置,出现的频率是什么。所以在倒排表中的每一条记录我们也称作为倒排项。

    倒排索引的搜索过程总结:
        (1)首先根据用户需要查询的词条进入到词典进行匹配,验证词条在词典中是否存在;
        (2)如果上一步搜索结果发现词条不在词典中,则本次搜索结束,如果在词典中,就需要去查看倒排表中的记录;
        (3)根据倒排表中记录的倒排项来定位数据在哪些文档中存在,而后根据这些文档的"_id"来获取指定的数据。

温馨提示:
    倒排索引是搜索引擎的核心,搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。
    ES中的倒排索引其实就是Lucene的倒排索引,区别于传统的正向索引,倒排索引会在存储数据时将关键
词和数据进行关联,保存到倒排表中,然后查询时,将查询内容进行分词后再倒排表中进行查询,最后匹配
数据即可。

二.集群节点角色类型

1.cluster state

什么是cluster state:
    集群相关的数据称为cluster state,会存储在每个节点中。

cluster state主要包含以下信息:
    (1)节点信息,比如节点名称,节点连接地址等;
    (2)索引信息,比如索引名称,索引配置信息(分片数量,副本数量)等;

2.master

(1)ES集群中只能有一个master节点,master节点用于控制整个集群的操作;
(2)master主要维护cluster state,当有新数据产生后,master会将最新的数据同步给其它node节点;
(3)master节点是通过选举产生的,可以通过"node.master: true"(默认值就是true)指定候选master节点;

温馨提示:
    当我们通过API创建索引"PUT /oldboyedu",Cluster state则会发生变化,由master同步至其它node节点。

3.data

(1)存储数据的节点即为data节点,默认节点是data类型,相关配置"node.data: true"(默认为true);
(2)当创建索引后,索引创建的数据存储至某个节点,能够存储数据节点,我们称之为data节点。

4.coordinating

(1)处理请求的节点即为coordinating节点,该节点为所有节点的默认角色,不能取消;
(2)coordinating节点主要将请求路由到正确的节点处理。比如创建索引会由coordinating路由到master节点处理;
(3)当配置"node.master: false","node.data:false"则为coordinating节点。

5.master-eligible

有资格参与投票被选为master节点。

6.Ingest

Ingest节点的基础原理是:
    节点接收到数据之后,根据请求参数中指定的管道流id,找到对应的已注册管道流,对数据进行处理,然后将处理过后的数据,按照Elasticsearch标准的indexing流程继续运行。

Ingest节点是Elasticsearch 5.0 新增的节点类型和功能。其开启方式为"node.ingest: true"

7.自定义配置说明

vi elasticsearch.yml 
cluster.name: oldboyedu-linux77
node.name: mysql106.oldboyedu.com
node.master: true
node.data: true
path.data: /oldboyedu/data/elasticsearch
path.logs: /oldboyedu/logs/elasticsearch
network.host: 10.0.0.106
http.port: 9200
discovery.seed_hosts: ["mysql106.oldboyedu.com","mysql107.oldboyedu.com","mysql108.oldboyedu.com"]
cluster.initial_master_nodes: ["mysql106.oldboyedu.com"]

温馨提示:
    生产环境中,假设一个集群为N台服务器,则master角色建议最少配置 (N / 2) + 1台.换句话说,就是集群半数以上节点为Master角色.

三.ES集群的基本介绍

1.使用ES集群的好处

    (1)elasticsearch有着强大的模糊搜索能力,底层采用了倒排索引的方式加快了查询效率。尤其是在
数据量达到TB级别以后,效果会越来越明显。
    (2)能够充分利用集群各节点资源,包括但不限于CPU,内存,磁盘,网卡等,集群可以支持PB级别数据的存储;
    (3)能够提高系统的可用性,尽管部分节点停止服务,整个集群依然可以正常服务;

2.ES如何组集群

    Elasticsearch集群由多个节点组成的,通过cluster.name设置集群名称,并且用于区分其它的集群,每个节点通过node.name指定节点的名称。

    在单节点中,可以部署ES多实例,但要求同一个ES集群中的"cluster.name"配置要相同,它就会自动发现集群并加入到其中。

    但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播(unicast)主机列表。之所以配置为使用单播发现,是为了防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。

温馨提示:
    discovery.zen.ping.unicast.hosts:
        ES 7.x 已经弃用了该参数,但为了兼容性,其已经是有效的。指定所有master角色的单播节点,
        如果不指定数据传输端口(transport.tcp.port),默认的端口为9300。
    discovery.seed_hosts:
        ES 7.x新增的配置,此设置以前称为discovery.zen.ping.unicast.hosts。
        它的旧名称已被弃用,但为了保持向后兼容性,它继续有效。对旧名称的支持将在以后的版本中删除。

3.elasticsearch的master选举流程

    (1)elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分。
    (2)对所有可以成为master的节点("node.master: true")根据nodeId字段排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。
    (3)如果对某个节点的投票数达到一定的值(可以成为master节点数" N / 2 + 1")并且该节点自己也选举自己,那么这个节点就是master。否则重新选举直到满足上述条件。

    温馨提示:
        master节点的职责主要包括集群,节点和索引的管理,不负责文档级别的管理,data节点可以关闭http功能。

4.故障转移

    当集群总只有一个节点在运行时,意味着会有一个单点故障问题,即没有冗余。幸运的是,ES集群可以防止数据丢失。

    ES集群实现的故障转移主要借助分布式集群中的副本分片和主分片,这一点我们在讲解分片和副本的概念时已经介绍过了。

    可以现场模拟故障案例,我们手动关掉一台ES实例,观察服务是否正常运行

5.水平扩容

    当我们对ES集群进行扩容时,为了分散现有集群的负载而对分片自动进行重新分配。

    水平扩容时,已创建索引的分片数量是不可修改的,但副本数量是可以修改的。

    动态修改副本数量方式如下:
        curl -X PUT http://elk101.oldboyedu.com:9200/shopping/_settings
            {
                "number_of_replicas": 0  // 标识临时动态的关闭副本,这在数据量大量写入的时候我们可以这样操作。
            }

温馨提示:
    分片的自动分配是可以关闭的哟,但不建议关闭,如果关闭后将有运维工程师手动进行分配,尽量别给自己找挖坑哈!

四.路由选择(理解后就知道为什么分片数一旦创建就不能被修改了)

1.路由选择概述

    当我们查询文档的时候,elasticsearch如何知道一个文档应该存放在哪一个分片中呢?它其实是可以通过下面的公式计算出来:
        shard = hash(routing) % number_of_primary_shards

    相关参数说明:
        routing:
            默认值是文档的"_id"字段,也可以采用自定义值,但并不推荐。
        hash:
            对routing计算唯一的hash值。而后与number_of_primary_shards取余。

2.不带routing查询

    不带routing查询的情况在查询的时候因为不知道要查询的数据具体在哪个分片上,因此需要把查询请求转发到整个集群,所以整个过程分为2个步骤,即分发和聚合。

    分发:
        请求到达协调节点(coordinating)后,协调节点将查询请求分发到每个节点上。

    聚合:
        协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。

3.带routing查询

    带routing查询的情况在查询的时候,可以直接根据routing信息定位到某个分片进行查询,不需要查询所有的分片,经过协调节点排序。

    向上自定义的用户查询,如果routing设置为userid的话,就可以直接查询数据来,效率提升很多。

五.文档的底层操作原理解析

1.文档的写流程

    新建,删除,更新操作我们都可以归纳为写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。

    如下图所示,下面是我们罗列的主分片和副本分片上成功新建,删除,更新一个文档必要的顺序步骤:
      (1)客户端可以请求任意集群节点(比如"elk101"),此时我们称该节点为"协调节点(coordinating)";
      (2)协调节点计算数据的存储路由,默认会根据文档的"_id"来计算,假设计算结果确定文档属于分片0,它转发请求到"elk103",分片0位于这个节点上;
      (3)"elk103"节点在主分片上执行写请求将数据进行保存;
      (4)主分片数据保存成功后,它会转发请求到相应副本分片节点,如下图所示,副本分片位于"elk101"节点上;
      (5)当所有的副本分片节点保存数据成功后,会告知"elk103"节点写请求执行成功;
      (6)"elk103"节点(主分片节点)在反馈给客户端写请求操作的执行结果;
      (7)客户端接收到成功响应的时候,文档的修改已经应用于主分片和所有的副本分片,你的修改生效了。

    综上所述,文档的写操作,主分片和副本分片拥有强一致性,即主分片和副本分片的数据是一致的。

    温馨提示:
        (1)我们可以通过修改consistency参数的值来改变上述写入的流程,其值为one,all和quorum(默认值)。
            one:
                只要主分片状态写入成功,就认为数据写入成功,直接返回给客户端,并不等待任意的副本分片同步数据。
            all:
                必须要主分片和所有的副本分片数据写入成功后才允许执行写操作。
            quorum:
                只要主分片和大部分副本分片(计算公式为: "int((primary + number_of_replicas)/2) + 1")写入成功就返回给客户端,并不需要等待所有的副本节点同步数据。这是默认的策略。生产环境中我建议大家就是用默认的策略即可。
        (2)如果没有足够的副本分片写入,ES会等待,希望更多的分片出现。默认情况下等待的超时时间为1分钟,如果你需要,可以使用timeout参数使它更早终止,比如修改为: 5s, 10s, 30s等。

1633943067038

2.文档的读流程-单个文档搜索

文档能够从主分片或者任意一个副本分片被检索。

如下图所示,我们罗列在主分片或者复制分片上检索一个文档必要的顺序步骤:
      (1)客户端给协调节点("elk103")发送get请求;
      (2)协调节点默认使用文档的"_id"确定文档属于分片"0",分片"0"对应的副本分片在"elk101"和"elk103"节点上都有,此时,它转发请求到"elk101";
      (3)"elk101"返回文档(document)给"elk103",然后由"elk103"返回给客户端;

对于读请求,为了平衡负载均衡,请求节点会为每个请求选择不同的分片,它会循环所有的分片副本。

可能的情况是,一个被索引的文档已经存在于主分片上却还没来得及同步到副本分片上。

这时副本分片会报告文档未找到,主分片会成功返回文档。一旦索引请求成功返回给用户,文档则在主分片和副本分片都是可用的。

我猜测有可能你会问:
      (1)为啥"elk103"节点不直接返回文档给客户端呢?
      (2)当"elk101"节点查询到数据后为啥不直接返回给客户端而是要将数据交给"elk103"节点呢?

参考答案:
      (1)当"elk103"节点负载比较高时,如果把文档的搜索工作还交给当前节点,无疑是雪上加霜,因为他消耗的不仅仅是CPU,内存,套接字等资源,而将任务交给"elk101"节点后,就无需为本次搜索承担额外的资源了,换句话说,就会减少本次查询对系统资源负载的消耗;
      (2)当"elk101"节点查询到数据后,并不会直接将数据返回给客户端,我猜想原因之一是不知道客户端的IP地址,原因之二就是如果知道了IP地址也得需要建立TCP连接才能完成传输,与其这样,还不如直接交由已经建立TCP连接的"elk103"节点进行数据的返回;

1633943104186

3.文档的读流程-全文搜索

       对于全文搜索而言,文档可能分散在各个节点上,那么在分布式的情况下,分为两个阶段,即搜索(query)和取回(fetch)这两个阶段。

       搜索(query)阶段包含以下三步:
             (1)客户端发送一个search(搜索)请求给"elk103","elk103"创建了一个长度为"from + size"(这两个参数大家并不陌生,在分页查询中我们有使用到)的空优先级队列;
             (2)"elk103"转发这个搜索请求中每个分片的主分片或者副本分片,每个分片在本地执行这个查询并将结果存储到一个大小为"from + size"的有序本地优先级队列里去;
            (3)每个分片返回文档(document)的ID和它优先级队列里的所有document的排序值给协调节点"elk103","elk103"把这些值合并到自己的优先队列里产生全局排序结果;

           取回(fetch)阶段包含以下三步:
             (1)协调节点辨别出哪个文档(document)需要取回,并且向相关分片发出GET请求;
             (2)每个分片加载document并且根据需要丰富(enrich)它们(因为query阶段拿到的是排序后的"_id",并没有真正拿到数据),然后再将document返回协调节点;
             (3)一旦所有的document都被取回,协调节点会将结果返回给客户端; 

1633943129130

4.文档的更新流程

 部分更新一个文档结合了先前说明的读取和写入流程。

 部分更新一个文档的步骤如下:
       (1)客户单向协调节点("elk103")发送更新请求;
       (2)协调节点通过计算,发现更新请求的主分片在"elk102"上,因此将请求转发到主分片所在的"elk102";
       (3)"elk102"从主分片检索文档,修改"_source"字段中的JSON,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改(该文档底层对应的文件就会被上"锁"),它会重试步骤3,超过retry_on_conflict次后放弃;
       (4)如果"elk102"成功更新文档,它将新版本的文档并行转发到其它的副本分片,重新建立索引;
       (5)一旦所有副本分片都返回成功,"elk102"向协调节点也返回成功,协调节点向客户端返回成功;

温馨提示:
      (1)当主分片把更改转发到副本分片时,它不会转发更新请求。相反,它转发完整文档的新版本。
      (2)请记住,这些更改将会异步转发到副本分片,并且不能保证他们以发送他们相同的顺序到达。如果elasticsearch仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。

1633945432739

5.多文档的批量操作流程

    mget和bulk API的模式类似于单文档模式。

    区别在于协调节点知道每个文档存在于哪个分片中。它将整个文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点。

    协调节点一旦收到来自节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端。

1633945416674

6.近实时搜索,并非实时搜索

    随着按段(pre-segment)搜索的发展,一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被搜索,但这样还是不够快,磁盘在这里成为了瓶颈。
    提交(commiting)一个新的段到磁盘需要一个fsync来确保被物理性的写入磁盘,这样在断电的时候就不会丢失数据。
    是fsync操作代价很大:如果每次索引一个文档都去执行一次的话会造成很大的性能问题。

    我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着fsync要从整个过程中被移除。

    在ES和磁盘之间是文件系统缓存,像之前描述的一样,在内存索引缓冲区中的文档会被写入到一个新的段中。

    但是这里新段会被先写入到文件系统缓存(这一步代价会比较低),稍后再被刷新到磁盘(这一步代价比较高)。不过只要文件已经在缓存中,就可以像其它文件一样被打开和读取了。

7.translogs的作用

      为了保证性能,插入ES的数据并不会立刻落盘,而是首先存放在内存当中,等到条件成熟后触发flush操作,内存中的数据才会被写入到磁盘当中。如果ES服务器突然断电,那么停留在内存中还没来得及写入磁盘中的数据是不是就丢失了呢?

      还好translog保留了这些数据的操作日志,在ES服务重启的时候,会读取translog,恢复这部分数据。 

      ES 中translog是存储于磁盘上的文件,每个ES分片都会一个translog,所以translog的存储路径就位于分片数据目录下。

      translog中存储了ES的操作记录,具体的说是ES还没落盘的数据的操作记录。因此不难看出translog的作用就是保证ES数据不丢失。

8.translogs的细节说明

事务日志存储在哪里?
    在索引分片目录下,取名方式如下:
    translog-N.tlog:
        真正的日志文件,N表示generation(代)的意思,通过它跟索引文件关联
    tranlog.ckp:
        日志的元数据文件,长度总是20个字节,记录3个信息:偏移量 & 事务操作数量 & 当前代

什么时候删事务日志:
    在flush的时候,translog文件会被清空。实际的过程是先删掉老文件,再创建一个新文件,取名时,序号加1,比如图2中,flush后你只会看到 translog-2.tlog,原来的translog-1.tlog已被删除。

为什么要删?
    因为数据修改已经写入磁盘了,之前的旧的日志就无用武之地了,留着只能白嫖存储空间。

推荐阅读:
    https://blog.csdn.net/oZhenDe/article/details/85870818

1633948474499

六.文档搜索的细节剖析(了解即可)

1.倒排索引被写入磁盘后是不可改变的

    早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。

    倒排索引被写入磁盘后是不可改变的:它永远不会修改。

    不变性有重要的价值:
        (1)不需要锁,如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题;
        (2)一旦索引被读入内核的文件系统缓存,便会留在那里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升;
        (3)其它缓存(像filter缓存),在索引的生命周期内始终有效。它不需要在每次数据修改时被重建,因为数据不会变化;
        (4)写入单个大的倒排索引允许数据被压缩,减少磁盘I/O和需要被缓存到内存中索引的使用量;
        (5)有助于快照备份工作的开展;

    当然,一个不变的索引也有不好的地方,主要事实是它不可变的!你不能修改它。如果你需要让一个新的文档可被搜索,你需要重新构建整个索引。
        (1)对一个索引所能包含的数据量造成了很大的限制;
        (2)对一个索引可被更新的频率造成了很大的限制;

    那为了解决上面提到的两个问题,那如何在保留不变性的前提下实现倒排索引的更新呢?我们可以接着往下看。

2.ES决定使用更多的索引来解决

    ES决定使用更多的索引来解决上面的问题。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。

    每个索引都会被轮流查询到,从最早的索引开始查询完后再对结果进行合并。

    ES基于Lucene,这个JAVA库引入了按段搜索的概念,每一段本身就是一个倒排索引,但索引再Lucene中除表示所有段的集合外,还增加了提交点(一个列出了所有已知段的文件)的概念。

3.按段搜索流程

    按段搜索流程如下:
        (1)新文档被收集到内存索引缓存;
        (2)缓存会不时被提交:
            1)生成一个新的段,一个追加的倒排索引,将被写入磁盘;
            2)一个新的包含新段名字的提交点将被写入磁盘;
            3)磁盘进行同步,所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件;
        (3)新的段被开启,让他包含的文档可见以被搜索;
        (4)内存缓存被清空,等待接收新的文档;

    当一个查询被触发,所有已知的段按顺序被查询,词项统计会对所有段的结果进行聚合,以保证每个词和每个文档都被准确计算。这种计算方式可以用相对较低的成本将新文档添加到索引。 

4.文档的删除流程

    段是不可更改的,所以既不能从把文档从旧的段移除,也不能修改旧的段来进行反映文档的更新。取而代之的是,每个提交点会包含一个".del"文件,文件中会列出这些被删除文档的段信息。

    当用户对一个文档发起"删除"操作时,它实际上只是在".del"文件中被标记删除。一个被标记删除的文档仍然可以被匹配到,但它会在最终结果被返回前从结果集中移除被标记删除的文档。

    那么问题来了,如果只是被标记删除,存储空间并没有释放,那随着数据的积累占用的存储空间就会越多,该如何解决呢?

    ES解决的方式就是合并段文件(Segment file),随着段文件不断的增多,ES会将这些段文件进行合并,合并后会顺带将标记为删除的文档进行物理删除。这个工作由ES自行完成,我们运维人员就无需手动合并文件了,了解即可。

5.文档的更新流程

    文档更新也是类似的方式,当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到新的段中。

    可能两个版本的文档都会被查询匹配到,但被删除的那个旧版本文档在搜索结果集返回前就已经被移除。

七.文档处理

1.文档冲突

    当我们使用index API更新文档,可以一次性读取原始文档,做我们的修改,然后重新索引整个文档。最近的索引请求将获胜:无论最后哪一个文档被索引,都将被唯一存储在Elasticsearch中。如果有其它同时更改这个文档,他们的更改将丢失。

    很多时候这是没有问题的。也许我们的主数据存储是一个关系型数据库,我们只是将数据复制到elasticsearch中并使其可被搜索。也许两个人同时更改相同的文档几率很小。或者对于我们的业务来说偶尔丢失更改并不是很严重的问题。

    但有时丢失了一个变更就是非常严重的。试想我们使用ES存储我们网上商城商品库的存的数量,每次我们卖一个商品的时候,我们在ElasticSearch中将存储量减少。有一天,管理层决定做一次促销。突然的,我们一秒要卖好几个商品。假设有两个web程序并行运行,每个都同时处理所有商品的销售。

    要解决上述的问题,在数据库领域中,有两种方法通常被用来并发更新时变更不会丢失:
        悲观并发控制:
            这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。一个典型的例子时读取一行数据之前像将其锁住,确保只有放置锁的线程能够对这行数据进行修改。
        乐观锁并发控制:
            ES中使用的这种方法假设冲突是不可能发生的,并且不会阻塞正在尝试的操作。然而,如果源数据在读写当中被修改,更新将会失败。应用程序接下来该如果解决冲突。例如,可以重试更新,使用新的数据,或者将相关情况报告给用户。

2.ES使用乐观锁并发控制

    ES是分布式的,当文档创建,更新或删除时,新版本的文档必须复制到集群中的其它节点。ES也是异步和并发的,这意味着这些复制请求被并行发送,并且到目的地时也许顺序是乱的。ES需要一种方法确保文档的旧版本不会覆盖新的版本。

    当我们之前讨论index,GET和DELETE请求时,我们指出每个文档都有一个"_version(版本)"好,当文档被修改时版本号递增。ES使用这个version号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。

    我们可以利用version号来确保应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的version号来达到这个目的。如果该版本不是当前版本号,我们的请求将会失败。

    如下图所示,老的版本ES使用version,但新版本不支持了,会报下面的错误,提示我们使用`if_seq_no` 和`if_primary_term` 。

3.ES锁机制演示

(1)查看文档的"_seq_no"和 "_primary_term"对应的版本号:
curl -X GET http://elk101.oldboyedu.com:9200/shopping/_doc/10010

(2)基于上一步查出来"_seq_no"和 "_primary_term"对应的版本号来修改数据
curl -X POST http://elk101.oldboyedu.com:9200/shopping/_update/10010?if_seq_no=1&if_primary_term=1
{
    "doc":{
        "price": 6000.00,
        "weight": "2.0kg"
    }
}

温馨提示:
    如果"_seq_no"和 "_primary_term"对应的版本号不是最新版本,则会抛出如下所示的错误哟~

4.ES支持外部系统版本控制

    一个常见的设置是使用其它数据库作为主要的数据存储,使用ElasticSearch做数据检索,这意味着主数据库的所有更改发生时都需要被复制到ElsticSearch,如果多个进程负责这一数据同步,你可能遇到类似于之前描述的并发问题。

    如果你的主数据库已经有了版本号或一个能作为版本号的字段值比如timestamp,那么你就可以在ES中通过增加version_type=external到查询字符串的方式重用这些相同的版本号,版本号必须时大于零的整数,且小于9.2E+18(一个Java中long类型的正值)。

    测试案例如下:
        curl -X POST http://elk101.oldboyedu.com:9200/shopping/_doc/10010?version=3&version_type=external
            {
                "doc":{
                    "price": 8888.00,
                    "weight": "2.0kg"
                }
            }

温馨提示:
    注意哈,此处我指定的版本号"version"必须大于当前最新文档的版本号,否则会抛出如下图所示的错误。

八.有关副本和分片相关的面试题

1.增加节点能否提高已创建索引的容量

问题:
    目前一个有3个ES节点,如果此时增加一个新节点是否能提高已经创建的索引(shard为3,)数据容量呢?

参考答案:
    不能,因为已经创建的索引只有3个分片,已经分布在3个节点上,那么新增的第四个节点对于已创建索引而言是无法使用到的。所以也无法带来数据容量的提升。

2.增加节点副本能否提高读性能

问题:
    目前一共有3个ES节点,如果增加副本数是否能提高已创建索引的读吞吐量。

参考答案:
    不能,原理和上面的分片类似,因为副本分片已经分配了,新增的节点并没有已创建的索引副本,因此当访问该索引的副本分片时还是会请求到原来已经分配的节点,和新增的节点无关。

3.生产环境中如何设置分片

    分片和副本的设计为Elasticsearch提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且索引的分片完成分片后由于索引的路由机制,我们是不能重新修改分片数的。

    可能有人会说,我不知道这个索引将来会变得多大,并且过后我也不能更改索引的大小,所以为了保险起见,还是给他设为1000个分片吧,但这需要知道的是,一个分片并不是没有代价的。

    在正是说合理设置分配数之前,我们需要了解以下几点:
        (1)一个分片的底层对应一个Lucene索引,会消耗一定的文件句柄,内存,以及CPU运转;
        (2)每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好,但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了;
        (3)用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度;

    一个业务索引具体需要分配多少分片可能需要架构师和技术人员对业务的增长有个预先的判断,横向扩展应当分阶段进行。为下一阶段准备好足够的资源。

    只有当你进入到下一个阶段,你才有时间思考需要作出哪些改变达到这个阶段,一般说,我们遵循一些原则:
        (1)控制每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G,参考下文的JVM设置原则),因此,如果索引的总容量在500G左右,那分片在16个左右即可;
        (2)考虑一下node数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了1个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以,一般都设置分片数不超过节点数的3倍;
        (3)主分片,副本和节点最大数之间数量,我们分配的时候可以参考一下关系:"节点数 <= 主分片数量 * (副本数 + 1)";

    推荐阅读:
        https://qbox.io/blog/optimizing-elasticsearch-how-many-shards-per-index

    翻译文章:
        https://www.jianshu.com/p/297e13045605
        https://segmentfault.com/a/1190000008868585

4.如何推迟分片分配(在重启集群时很有效)

    对于节点瞬时中断的问题,默认情况,集群会等待一分钟来查看节点是否会重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配。

    这就可以减少ES在自动再平衡可用分片时所带来的极大开销。

    通过修改参数"delayed_timeout",可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改。

    如下所示,我对"http://oldboyedu:9200/_all/_settings"的URL发起PUT请求,并发送以下数据:
        {
            "settings":{
                "index.unassigned.node_left.delayed_timeout": "5m"
            }
        }

    温馨提示:
        (1)推迟分片分配不仅仅可以防止网络波动导致故障,也可以在我们重启ES集群的时候起到很大的作用。尽管生产环境中重启集群是滚动重启,但短时间内也会和集群失去联系,当适当调大该值可以避免分片的自动分配;
        (2)当然,我们在重启集群的时候,也可以临时关闭自动分配分片的功能。我们对"http://elk101.oldboyedu.com:9200/_cluster/settings"的URL发起PUT请求,并发送以下数据:
            {
                "transient" : {
                    "cluster.routing.allocation.enable" : "none"
                }
            }

其它有关ES的集群shard平衡的参数说明(了解即可):
cluster.routing.allocation.enable:
    启用或禁用特定种类的分片的分配:
    all(默认值):
        允许为所有类型的分片分配分片。
    primaries :
        仅允许分配主分片的分片。
    new_primaries :
        仅允许为新索引的主分片分配分片。
    none :
        任何索引都不允许任何类型的分片。

cluster.routing.rebalance.enable
    为特定类型的分片启用或禁用重新平衡:
    all(默认值):
        允许各种分片的分片平衡。
    primaries:
        仅允许主分片的分片平衡。
    replicas:
        仅允许对副本分片进行分片平衡。
    none:
        任何索引都不允许任何类型的分片平衡。

cluster.routing.allocation.allow_rebalance
    始终允许重新平衡。
    indices_primaries_active:
        仅在所有主分片激活时。
    indices_all_active:
        仅当所有分片都激活时。

cluster.routing.allocation.cluster_concurrent_rebalance:
    允许控制群集范围内允许的并发分片重新平衡数。 默认为2。
    请注意,此设置仅控制由于群集中的不平衡而导致的并发分片重定位数。 此设置不会因分配过滤或强制感知而限制分片重定位。

5.主分片(primary shards)和副本分片(replica shards)

    为了将数据添加到Elasticsearch集群,我们需要索引(Index),一个存储关联数据的地方。实际上索引只是一个或多个分片(shards)的逻辑命名空间("logical namespace")。

    关于主分片(primary shards)和副本分片(replica shards)可以总结以下几点:
        (1)一个分片(shard)是一个最小级别"工作单元(worder unit)",它只是保证了索引中所有数据的一部分;
        (2)我们需要知道分片就是一个Lucene实例,并且它是本身就是一个完整的搜索引擎。应用程序不会和它直接通信;
        (3)分片可以是主分片(primary shard)或者是副本分片(replica shard);
        (4)索引中的每个文档属于一个单独的主分片,所以主分片的数量决定了索引最多能存储多少数据;
        (5)副本分片(replica shard)只是主分片(primary shard)的一个副本,它可以防止硬件故障导致的数据丢失,同时可以提供读请求,比如搜索或者从别的shard取回文档;
        (6)当索引创建完成后,主分片的数量就固定了,但是副本分片(replica shard)的数量可以随时调整;

九.ES相关的面试题(了解即可)

1.elasticsearch更新和删除文档的流程

    (1)删除和更新也都是写操作,但是elasticseach中的文档是不可变的,因此不能被删除或者改动以展示其变更;

    (2)磁盘上的每个段都有一个响应的".del"文件。当删除请求发送后,文档并没有真的被删除,而是在".del"文件中标记为删除;该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在".del"文件中被标记为删除的文档将不会被写入新段;

    (3)在新的文档被创建时,elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在".del"文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。

2.elasticsearch索引文档的流程

    (1)协调节点默认使用文档的"_id"参与计算(也支持通过routing),以便为路由提供合适的分片:
        shard = hash(document_id) % (num_of_primary_shards)

    (2)当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默认是每间隔1秒)写入到Filesystem Cache,这个从Memory Buffer到Filesystem Cache的过程叫做refresh;

    (3)当然在某些情况下,存在Memory Buffer和Filesystem Cache的数据可能会丢失,ES是通过translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做flush;

    (4)在flush过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交点,并将内容刷写到磁盘,旧的translog将被删除并开始一个新的translog。

    (5)flush触发的时机时定时触发(默认30分钟)或者translog变得太大(默认512M)时;

3.elasticsearch集群脑裂问题原因及解决方案

    "脑裂"问题可能的原因:
        (1)网络问题:
            集群间的网络延迟导致一些节点访问不到master,认为master挂掉从而选举出新的master,并对master上的分片和副本标红,分配新的主分片。
        (2)节点负载:
            主节点的角色即为master又为data,访问量较大时可能会导致ES停止响应造成大面积延迟,此时其它节点得不到主节点的响应认为主节点挂掉了,会重新选举主节点。
        (3)内存回收:
            data节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应。

    "脑裂"问题解决方案:
        (1)减少误判:
            节点的响应时间,默认为3秒。可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。
            生产环境中我们可以调大该参数,比如将其调大为10(即"discovery.zen.ping_timeut:10"),可以适当减少误判。
        (2)选举触发:
            "discovery.zen.minimum_master_nodes"设置在选举master节点时需要参与的最少候选主节点数,默认为1。如果使用默认值,则当网络不稳定时有可能会出现脑裂。
            假设集群的候选主节点数量为N,合理的数字通常设置为"N / 2 + 1"。
        (3)角色分离
            即master节点与data节点分离,限制角色。
                master节点配置为:
                    node.master: true
                    node.data: false
                data节点配置为:
                    node.master: false
                    node.data: true

4.elasticsearch在部署时,对Linux的设置有哪些优化方法

    (1)64GB内存的机器是非常理想的,但是32GB和16GB机器也是很常见的,少于8GB会适得其反;
    (2)如果你要在更快的CPUs和更多的核心之间选择,选择更多的核心更好,多个内核提供的额外并发远胜过稍微快一点点的时钟频率;
    (3)如果你负担得起SSD,它将远远超出任何旋转介质。基于SSD的节点,查询和索引性能都有提升。如果你负担得其,SSD是一个好的选择;
    (4)即使数据中心我们近在咫尺,也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理位置;
    (5)请确保运行你的应用程序的JVM和服务器的JVM是完全一样的。在Elasticsearch的几个地方,使用Java的本地序列化;
    (6)通过设置"gateway.recover_after_nodes","gateway.expected_nodes","gateway.recover_after_time"可以在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个小时缩短为几秒钟;
    (7)Elasticseach默认被配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。最好使用单播代替组播;
    (8)不要随意修改垃圾回收器(CMS)和各个线程池的大小;
    (9)把你内存的(少于)一般给Lucene(但不要超过32GB),通过"ES_HEAP_SIZE"环境变量设置;
    (10)内存交换到磁盘对服务器性能来说是致命的。如果内存交换到磁盘上,一个100微妙的操作可能变成10毫秒。在想想那么多10毫秒的操作延迟累加起来,不难看出swapping对于是多么可怕;
    (11)Lucene使用了大量的文件。同时,Elasticseach在节点和HTTP客户端之间进行通信也使用了大量的套接字。所有这一期都需要足够的文件描述符。你应该增加你的文件描述符,设置一个很大的值,如"64000"。

    温馨提示:
        索引阶段性能提升方法如下:
            (1)使用批量请求并调整其大小:
                每次批量数据5-15MB大是个不错的起始点;
            (2)存储:
                使用SSD;
            (3)段和合并:
                Elasticsearch默认值是20MB/s,对机械磁盘应该是个不错的设置。如果你用的是SSD,
                可以考虑提高到100-200MB/s。如果你在批量导入,完全不在意搜索,你可以彻底关掉合并限流。
                另外,还可以增加"index.translog.flush_threshold_size"设置,从默认的512MB到更大一些的值,比如1GB,这可以在一次清空触发的时候在事务日志里积累出更大的段。
            (4)如果你的搜索结果不需要近实时的准确度,考虑把这个索引的"index.refresh_interval"改到30s。
            (5)如果你在做大批量导入,考虑通过设置"index.number_of_replicas: 0"关闭副本。

5.GC方面,在使用Elasticsearch时要注意什么

    (1)倒排词典的索引需要常驻内存,无法GC,需要监控data node上segment memory增长趋势;
    (2)各类缓存,field cache,filter cache,indexing cache,bulk queue等等,要设置合理的大小。并且要应该根据最坏的情况来看heap是否够用,也就是各类缓存全部占满的时候,还有heap空间可以分配给其它认为吗?避免clear cache等"自欺欺人"的方式来释放内存。
    (3)避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景,可以采用scan & scroll api来实现;
    (4)cluster stats主流内存并无法水平扩展,超大规模集群可以考虑拆分多个集群通过tribe node连接;
    (5)想知道heap够不够,必须结合实际应用场景,并对集群的heap使用情况做持续的监控。

6.在并发情况下,elasticsearch如何保证读写一致

    (1)可以提供版本号使用乐观并发控制,以确保新版不会被旧版本覆盖,由应用层来处理具体的冲突;
    (2)另外对于写操作,一致性级别支持"quorum/one/all",默认为"quorum",即只有当大多数分片可用时才允许写操作;
        但即使大多数可用,也可能存在因为网络等原因导致副本失败,这样该副本被认为故障,分片将会在一个不同的节点重新创建。
    (3)对于读操作,可用设置replicaton为sync(默认),这使得操作在主分片和副本分片都完成后才会返回;

    如果设置replication为async时,也可以通过设置搜素请求数"_preference"为primary来查询主分片,确保文档时最新版本。

7.如何监控Elasticsearch集群状态

(1)通过elasticsearch-head插件;
(2)通过kibana监控elasticsearch,你可用实时查看你的集群健康状态和性能,也可以分析过去的集群,索引和节点指标;

8.Elasticsearch中集群,节点,索引,文档,类型是什么

集群:
    是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨越所有节点的联合索引和搜索功能。
    集群由唯一名称标识,默认情况下为"elasticsearch",此名称很重要,因为如果节点设置为按名称加入集群,则该节点只能是集群的一部分。

节点:
    是属于集群一部分的单个服务器,它存储数据并参与索引和搜索功能;

索引:
    就像关系型数据库中的"数据库",它有一个定义多种类型的映射,所以是逻辑名称空间,映射到一个或多个主分片,并且可用有零个或多个副本分片。

文档:
    类似于关系型数据库中的一行。不同之处在于索引中每个文档都具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。

类型:
    是索引的逻辑类别/分区,其语义完全取决于用户。

9.Elasticsearch中的倒排索引是什么

    倒排索引是搜索引擎的核心,搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。
    ES中的倒排索引其实就是Lucene的倒排索引,区别于传统的正向索引,倒排索引会在存储数据时将关键词和数据进行关联,保存到倒排表中,然后查询是,将查询内容进行分词后再倒排表中进行查询,最后匹配数据即可。
谨此笔记,记录过往。凭君阅览,如能收益,莫大奢望。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇