Elasticsearch - Cluster Intro PART1

分布式集群介绍

Node

本子上是一个JVM进程

  • Coordinating Node

    • 处理请求的节点,叫 Coordinating Node
    • 所有节点默认都是 Coordinating Node
    • 通过将其他类型设置成 False,使其成为 Dedicated Coordinating Node
  • Data Node

    • 节点启动后,默认就是数据节点。可以设置 node.data: false 禁⽌
    • 保存shard数据。(由 Master Node 决定如何把shard分发到数据节点上)
    • 增加数据节点,水平扩展
  • Master Node

    • 决定shard片被分配到哪个节点 / 负责索引的创建与删除

    • 维护cluster state

    • 防止单点

    • Master Eligible Nodes

      • ⽀持配置多个 Master Eligible 节点。这些节点可以在必要时(如 Master 节点出现故障,网络故障时)参与选主流程,成为 Master 节点
      • 当集群内第一个 Master eligible 节点启动时候,它会将⾃⼰选举成 Master 节点(preVoted)
      • 每个节点启动后,默认就是一个 Master eligible 节点

Cluster State

  • 所有的节点信息维护
  • shard的路由信息
  • 索引的mapping & setting信息等
  • 每个Node都保存了状态信息
  • 只有master(leader)才能修改集群信息,并广播同步

Coordinating - Leader Election

  • gossip like Nodes互ping,preVote & Vote 后,node id 低的成为被选举节点 - bully 算法

  • 非master node加入集群,不影响选举。leader master故障后重新选举

  • Split brain 问题

    • quorum-based 算法,只有在 Master eligible 节点数大于 quorum 时,才能进行选举
    • Quorum = (master 节点总数 /2) + 1 时是安全的

提高cluster可用性,需要Data/Coordinating/Master 多节点,减少故障带来的影响

Data容错和故障恢复

Primary and Replica Shard

除了Primary Shard,增加replica shard可以增加数据冗余,提高可用性

  • Primary Shard,可以将一份索引的数据,分散在多个 Data Node 上,实现存储的⽔平扩展

    • 过多的primary,导致数据聚合成本较高,影响性能
    • 过少的primary shard,如1个,缺少水平扩展能力
    • 需要根据业务场景balance
  • Primary Shard数在索引创建时候指定,后续默认不能修改,如要修改,需重建索引

  • 一旦主分片丢失,副本分片可以 Promote 成主分片。副本分片数可以动态调整

    • 过多的replica ,带来更多的写入负担,数据持久化安全性提升,需要balance
  • Replica shard 提升读取的吞吐量

文档路由

⽂档到分片的映射算法

  • 随机 / Round Robin。当查询⽂档 1,分片数很多,需要多次查询才可能查到 ⽂档 1

  • 维护文档到分片的映射关系,当文档数据量大的时候,维护成本⾼

  • 实时计算,通过文档 1,⾃动算出,需要去那个分片上获取⽂档

  • 余数算法

    • shard = hash(_routing) % number_of_primary_shards
  • 支持自定义routing规则

    • 默认doc_id

文档更新

流程

  • primary updated-> replica updated -> response

  • Index Immutable

    • Append Only 并发写减少lock使用
    • 容易被cached和压缩
    • 更新挑战,需要重建index
  • 并发写入update

    • ES 中的⽂档是不可变的。如果你更新⼀个文档,会将就文档标记为删除,同时增加一个全新的文档。同时将文档的 version 字段加 1
    • seq_no + primary_term
  • Lucene 中,单个倒排索引⽂件被称为Segment

    • Segment 是⾃包含的, 不可变更的。多个 Segments 汇总在一起,称为 Lucene 的Index,其对应的就是 ES 中的 Shard
    • 当有新文档写⼊时,会⽣成新 Segment,查询时会同时查询所有 Segments,并且对结果汇总
    • Lucene 中有一个文件,用来记录所有Segments 信息,叫做 Commit Point
    • 删除的文档,保存在.del文件中
  • Refresh 操作

    • 将 Index buffer 写入 Segment 的过程叫Refresh。 Refresh 不执行 fsync 操作,不保证持久化道磁盘
    • Refresh 频率:默认 1 秒发⽣生一次,可通过index.refresh_interval 配置。 Refresh 后,数据就可以被搜索到了
    • 短时间大量写入会有大量的segment
    • Index buffer 被占满时,会触发refresh
    • 可以定时或手动触发refresh
  • Flush 操作

    • 先执行refresh操作

    • 再调⽤ fsync,将缓存中的 Segments写入磁盘

    • 最后删除Transaction Log

    • 触发

      • Time-based 30 分钟
      • Size-based trans log 512MB
      • manual
  • Merge 操作

    • 减少 Segments / 删除已经删除的⽂档

    • 触发

      • 自动merge
      • 手动api
  • 为什么增加Trans log

    • Segment 写⼊磁盘的过程相对耗时,借助⽂件系统缓存, Refresh 时,先将segment 写⼊缓存以开放查询,提升性能
    • 未保证数据不丢,增加commit log - trans log
    • refresh 不影响tran slog
    • 故障恢复,即使segment丢失,translog sync 持久化,可以保证数据恢复
    • trans log结构比segment简单,写入性能有保证

搜索查询机制

Query

从shards 检索出数据,根据score进行排序,获得文档id+score list

  • coordinating node 向primary shard 发送请求

  • 被选中的shard执⾏查询, 进⾏排序。然后,每 个分⽚都会返回 From + Size 个排序后的文 档 Id 和排序值 给 Coordinating 节点

    Fetch

通过👆的文档ids fetch获取文档data

  • Coordinating Node 会将 Query 阶段,从每个shard获取的排序后的⽂档 Id 列表,重新进⾏排序(可编程排序函数),选取 From 到 From + Size 个⽂档的 Id
  • 以 multi get 请求的⽅式,到相应的shard获取详细的⽂档数据

性能问题

  • 每个shard都查from+size条数据
  • 每个shard 单独算分,文档少分片多的情况下,算分不准
  • 合并成本
  • 深度分页查询

如何解决?

  • 数据量不大的时候,可以将主分⽚数设置为 1

  • 数据量大时,保证文档均匀分布在shard

  • 使用DFS Query then fetch

    • 性能差,需要注意
  • 深度分页,使用Search After

    • latest doc id

结果排序

Text 类型排序

  • sort 指定排序是针对字段原始内容进行的。 倒排索引⽆法发挥作⽤

  • 需要⽤到正排索引。通过文档 Id 和字段快速得到字段原始内容

    • Fielddata,可利用cache,数据量大时,内存开销大,默认关闭
    • Doc Values(列存储,对text类型无效) ,利用磁盘文件

Bucket & Metric 聚合分析

  • bucket 满足条件的文档

    • term

    • 数字类型

      • range
      • histogram
  • Metric 聚合分析

    • 单值分析 min max avg sum cardinality
    • 多值分析 stats top hits percentile
POST employees/_search
{
  "size": 0,
  "aggs": {
    "Job_gender_stats": {
      "terms": {
        "field": "job.keyword"
      },
      "aggs": {
        "gender_stats": {
          "terms": {
            "field": "gender"
          },
          "aggs": {
            "salary_stats": {
              "stats": {
                "field": "salary"
              }
            }
          }
        }
      }
    }
  }
}

👆代码 表示 根据工作类型分桶,然后按照性别分桶,计算工资的统计信息

数据建模

  • 确认字段类型
  • 是否在搜索范围及分词
  • 是否需要聚合和排序
  • 是否需要额外的存储

设置多字段类型

  • 默认会为文本类型设置成 text,并且设置一个 keyword 的⼦字段

  • 在处理理人类语⾔言时,通过增加“英文”,“拼音”和“标准”分词器,提高搜索结构

  • 结构化数据

    • 贴近自身数据类型 日期 布尔
    • 枚举=>keyword
    • 数字能用byte 不使用long
  • 不需要搜索的字段,显示设置index = false,如url

  • 不需要聚合的字段,显示设置enable = false

  • 避免过多的字段,仅索引需要检索的内容

  • 避免正则查询 😄这个大多数场景都需要避免

    • filter过滤替代
  • 避免null

    • 使用null_value

参考

深入理解Elasticsearch PDF