Elasticsearch ES进阶总结

核心概念

  • 索引 Index

    • 一个索引就是一个拥有几分相似特征的文档的集合。一个索引由一个名字来标识(必须全部是小写字母),在一个集群中,可以定义任意多的索引
    • 新华字典前面的目录就是索引的意思,目录可以提高查询速度。
    • elasticsearch 索引的精髓:一切设计都是为了提高搜索的性能。
  • 类型 Type

    • 在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型
    • 7.x 默认不再支持自定义索引类型 默认类型_doc
  • 文档 Document

    • 可被索引的基础信息单元,也是就是一条数据。 文档以 JSON(Javascript Object Notation)格式来表示,而 JSON 是一个 到处存在的互联网数据交互格式。
    • 一个 index/type 里面,你可以存储任意多的文档
  • 字段 Field

    • 数据表的字段
  • 映射 Mapping

    • mapping 是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值、

      分析器、是否被索引等等。

  • 分片 Shards

    • 可以想像成分表,可以分成男士表 和 女士表
    • 一个 Lucene 索引 我们在 Elasticsearch 称作 分片 。 一个 Elasticsearch 索引 是分片的集合。 当 Elasticsearch 在索引中搜索的时候, 他发送查询 到每一个属于索引的分片(Lucene 索引),然后合并每个分片的结果到一个全局的结果集。
    • Elasticsearch 提供了将索引划分成多份的能力, 每一份就称之为分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点 上。
    • 分片的好处
      • 允许你水平分割 / 扩展你的内容容量。
      • 允许你在分片之上进行分布式的、并行的操作,进而提高性能/吞吐量。
  • 副本 Replicas

    • Elasticsearch 允许你创建分片的一份或多份拷贝,这些拷贝叫做复 制分片(副本)。

    • 副本的好处

      • 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
      • 扩展你的搜索量/吞吐量,因为搜索可以在所有的副本上并行运行。/
    • {
        "settings" : {
           "number_of_shards" : 3,
           "number_of_replicas" : 1
        }
      }
      
  • 分配 Allocation

    • 将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。

系统框架

  • 一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者 从集群中移除节点时,集群将会重新平均分布所有的数据。
  • 当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更,例如增加、 删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操 作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节 点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。
  • 作为用户,我们可以将请求发送到集群中的任何节点 ,包括主节点。 每个节点都知道 任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论 我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将
  • 最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。

进阶

  • 集群健康值:yellow 表示当前集群的全部主分片都正常运行,但是副本分片没有全部处在正常状态

扩容

  • 主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够存储 的最大数据量。(实际大小取决于你的数据、硬件和使用场景。)

  • 但是,读操作—— 搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片 时,也将拥有越高的吞吐量

  • 在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把 副本数从默认的 1 增加到 2

路由计算

  • shard = hash(routing) % number_of_primary_shards
  • routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。 routing 通过hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数
  • 这就是我们要在创建索引的时候就确定好主分片的数量
  • 所有的文档 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一 个叫做 routing 的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。一个自定 义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被 存储到同一个分片中。

分片控制

  • 用户可以访问任何一个节点获取数据,这个节点称为协调节点
  • 当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点

写流程

  1. 客户端向 集群节点(任意) —— 协调节点 发送新建、索引或者删除请求。

  2. 节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的

    主分片目前被分配在 Node 3 上。

  3. Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。

在这里插入图片描述

读流程

  1. 客户端向 Node 1 (协调节点)发送获取请求。

  2. 节点使用文档的 _id 来确定文档属于分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2(为了负载均衡) 。

  3. Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端。

  • 在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。

更新流程

  1. 客户端向 Node 1 发送更新请求。
  2. 它将请求转发到主分片所在的 Node 3 。
  3. Node 3 从主分片检索文档,修改 _source 字段中的 JSON ,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤 3 ,超retry_on_conflict 次后放弃。(多次尝试
  4. 如果 Node 3 成功地更新文档,它将新版本的文档并行转发到 Node 1 和 Node 2 的副本分片,重新建立索引。一旦所有副本分片都返回成功, Node 3 向协调节点也返回 成功,协调节点向客户端返回成功。

分片原理

  • 分片是 Elasticsearch 最小的工作单元
  • Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索、
  • 分词器:对中文和英文的分词不同,
    • keyword 不可分词 text可分词
    • ik_max_word 能拆就拆 最细粒度
    • ik_smart 最粗粒度

倒排索引

  • 词条:索引中最小存储和查询单元 单词、词组

  • 词典:字典,词条的集合 ,B+,HashMap

  • 倒排表:关键词出现的位置和次数

  • 文档不变性有重要的价值:

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

动态更新索引

  • 用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并
  • Elasticsearch 基于 Lucene, 这个 java 库引入了按段搜索的概念。 每段本身都是一 个倒排索引, 但索引在 Lucene 中除表示所有段的集合外, 还增加了提交点的概念
  • 当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以 保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添 加到索引。
  • 段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档 的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档 的段信息。
  • 当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除

近实时搜索

  • 随着按段(per-segment)搜索的发展,一个新的文档从索引到可被搜索的延迟显著降低
  • 提交 (Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘
  • 在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分 片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化 并不是立即对搜索可见,但会在一秒之内变为可见。
  • 这些行为可能会对新用户造成困惑: 他们索引了一个文档然后尝试搜索它,但却没有搜到。 这个问题的解决办法是用 refresh API 执行一次手动刷新: /users/_refresh
  • 延时:主分片的延时 + 并行写入副本的最大延时
  • Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行 操作时均进行了日志记录
  • 执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush。分片每 30 分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新
  • ES先写入内存,再写日志,和数据库不太一样,数据必须刷写入磁盘后才能被搜索
  • Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大 的段再被合并到更大的段。 段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的 旧版本)不会被拷贝到新的大段中。

在这里插入图片描述

文档分析

  • 将一块文本分成适合于倒排索引的独立的 词条

  • 将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall

  • 字符过滤器,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个 字符过滤器可以用来去掉 HTML,或者将 & 转化成 and。

  • 分词器,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候, 可能会将文本拆分成词条。

  • Token 过滤器,改变词条(例如,小写化 Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

  • GET /_analyze
    {
      "analyzer": "standard",
      "text": "Text to analyze"
    }
    
    {
      "tokens" : [
        {
          "token" : "text", //实际存储到索引中的词条
          "start_offset" : 0,  //原来起始的位置
          "end_offset" : 4,
          "type" : "<ALPHANUM>",
          "position" : 0 //原始文本中出现的位置
        },
        {
          "token" : "to",
          "start_offset" : 5,
          "end_offset" : 7,
          "type" : "<ALPHANUM>",
          "position" : 1
        },
        {
          "token" : "analyze",
          "start_offset" : 8,
          "end_offset" : 15,
          "type" : "<ALPHANUM>",
          "position" : 2
        }
      ]
    }
    
    

文档冲突

  • 当我们之前讨论 index ,GET 和 delete 请求时,我们指出每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 version 号来确保变更 以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略
  • 我们可以利用 version 号来确保 应用中相互冲突的变更不会导致数据丢失。我们通过 指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请 求将会失败。
  • 老的版本 es 使用 version,但是新版本不支持了,会报下面的错误,提示我们用 if_seq_no 和 if_primary_term

ES优化

硬件选择

  • 使用 SSD。就像其他地方提过的, 他们比机械磁盘优秀多了。
  • 使用 RAID 0。条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就故障了。不要 使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。
  • 另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。
  • 不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。

分片策略

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

  • 分片的代价

    • 一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。
    • 每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
    • 用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。
  • 分片的原则

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

    • PUT /_all/_settings
      {
        "settings": {
         "index.unassigned.node_left.delayed_timeout": "5m"
      	} 
      }
      

路由选择

  • shard = hash(routing) % number_of_primary_shards
  • routing 默认值是文档的 id,也可以采用自定义值,比如用户 id。

写入速度优化

  • 针对于搜索性能要求不高,但是对写入要求较高的场景,提升写索引的性能:

    • 加大 Translog Flush ,目的是降低 Iops、Writeblock。
    • 增加 Index Refresh 间隔,目的是减少 Segment Merge 的次数。
    • 调整 Bulk 线程池和队列。
    • 优化节点间的任务分布。
    • 优化 Lucene 层的索引建立,目的是降低 CPU 及 IO。
  • Bulk 批量数据提交

    • Bulk 默认设置批量提交的数据量不能超过 100M。数据条数一般是根据文档的大小和服务器性能而定的,但是单次批处理的数据大小应从 5MB~15MB 逐渐 增加,当性能没有提升时,把这个数据量作为最大值。
  • 优化存储设备 ssd

  • 合理使用合并

    • Lucene 以段的形式存储数据。当有新的数据写入索引时,Lucene 就会自动创建一个新的段。随着数据量的变化,段的数量会越来越多,消耗的多文件句柄数及 CPU 就越多,查询效率就会下降
    • ES 默认采用较保守的策略,让后台定期进行段合并
  • 减少Refresh的次数

    • Lucene 在新增数据时,采用了延迟写入的策略,默认情况下索引的 refresh_interval 为1 秒。 Lucene 将待写入的数据先写到内存中,超过 1 秒(默认)时就会触发一次 Refresh,然后 Refresh 会把内存中的的数据刷新到操作系统的文件缓存系统中
    • 如果我们对搜索的实效性要求不高,可以将 Refresh 周期延长,例如 30 秒。 这样还可以有效地减少段刷新次数,但这同时意味着需要消耗更多的 Heap 内存。
  • 加大Flush设置

    • Flush 的主要目的是把文件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到512MB 或者 30 分钟时,会触发一次 Flush。index.translog.flush_threshold_size 参数的默认值是 512MB,我们进行修改
  • 减少副本的数量

    • 如果我们需要大批量进行写入操作,可以先禁止 Replica 复制,设置index.number_of_replicas: 0 关闭副本。在写入完成后,Replica 修改回正常的状态。

内存设置

  • 不要超过物理内存的 50%:Lucene 的设计目的是把底层 OS 里的数据缓存到内存中。

  • Lucene 的段是分别存储到单个文件中的,这些文件都是不会变化的,所以很利于缓存,同时操作系统也会把这些段文件缓存起来,以便更快的访问。

  • 如果我们设置的堆内存过大,Lucene 可用的内存将会减少(剩余的操作系统的空间),就会严重影响降低 Lucene 的全文本查 询性能。

  • 堆内存的大小最好不要超过 32GB: 在 Java 中,所有对象都分配在堆上,然后有一个 Klass Pointer 指 针指向它的类元数据。这个指针在 64 位的操作系统上为 64 位,64 位的操作系统可以使用更多的内存(2^64)。指针在主内存和缓存器(例如 LLC, L1 等)之间移动数据的时候,会占用更多的带宽。

  • 最终我们都会采用 31 G 设置

    -Xms 31g

    -Xmx 31g

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值