侧边栏壁纸
  • 累计撰写 48 篇文章
  • 累计创建 33 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Elasticsearch 最佳实践与优化指南

Angus
2023-12-18 / 0 评论 / 0 点赞 / 38 阅读 / 15965 字

1.容量规划

1.1节点角色规划

Elasticsearch 节点有角色之分,主要角色如下:

  • Master,主节点,主要负责集群元数据(Cluster State)管理和分发
  • Data,数据节点,主要负责数据存储和数据读写请求处理
  • Coordinate,协调节点,主要负责请求转发
  • Ingest,预处理节点,主要负责对数据进行处理转换

默认每个 Elasticsearch 节点拥有全部角色,分离节点角色可以让集群更加稳定,提升可用性,尤其是对核心业务集群。

1.1.1 使用独立的主节点

主节点与数据节点混用的问题如下:

  • 当节点由于网络问题、物理故障无法正常工作时,集群会同时失去一个主节点和数据节点,新选出的主节点要重新分配数据节点的数据,集群会在恢复时间段内处于 YELLOW 和 RED 状态。如果主节点是独立的,那么只要选出新的主节点就结束了,集群仍然是 GREEN 状态。
  • 数据节点要承载大量数据,需要占用大量内存 Heap 空间,引发更多的 GC,这也会影响混部的主节点工作。

如果集群需要很好的稳定性和很高的可用性,我们建议数据节点有 5 个以上时,应该独立部署3 个独立的主节点。

1.1.2 使用独立的协调节点

协调节点的工作之一是转发客户的查询请求到各个节点后,再在本地做一次 merge 后返回。当客户端发送一个海量数据聚合或者深度聚合类的请求时,在本地 merge 这一阶段会占用大量内存,导致节点严重 GC,会导致查询延迟有明显抖动。如果长时间 GC,节点甚至会被判不可用,离开集群,导致集群服务不稳定,集群状态变为 YELLOW 甚至 RED。 因此我们可以使用独立的协调节点,将本地 merge 的压力放到数据节点之外,即便协调节点挂了,只要将其快速重启即可,对集群状态运行影响很小。 另外如果要进一步提升集群稳定性,可以对读写请求做分离处理,即一部分独立协调节点处理读请求,另一部分处理写请求,减少彼此的影响。当然这样做的另一个好处时,可以随时根据需要切断集群的读或者写的入口,在集群不稳定时,做一个应急处理。  

1.1.3 使用独立的预处理节点

预处理节点提供了 ingest pipeline 机制,可以在数据写入前进行数据转换处理,一定程度上可以替代 Logstash。但是数据转换处理也是要占用 cpu、内存资源的,因此如果有大量的预处理需求,或者追求更好的稳定可用性,那么可以将 ingest 节点也独立处理来。  

1.1.4 节点硬件配置建议

每类节点的配置需要根据实际情况来决定,根据经验给出以下建议:

  • Master 节点:
    • CPU 4Core/Memory 8GB( Heap 4~6GB )/Disk 10GB
  • Data 节点:
    • 热节点(处理写请求的节点),CPU 16Core/Memory 64GB( Heap 30GB )/Disk 2~4TB
    • 冷节点(只处理读请求的节点),CPU 8Core/Memory 64GB( Heap 30GB )/Disk 6~10TB
    • 数据节点要考虑一个内存磁盘比,比如内存 64GB,磁盘 2TB,那么内存:磁盘=64:2048=1:32。内存磁盘比越大,意味着磁盘越小,存储的数据少,但更多的数据可以被 Cache 到内存中,读取性能更优。搜索类场景一般建议控制在 1:16 ,日志类场景热节点 1:32,冷节点可以 1:96。根据实际情况来调整。
    • 磁盘介质 SSD 好于 HDD,多块磁盘时建议做磁盘阵列,提升读写性能,阵列上RAID 0 > RAID50 > RAID5 > JBOD。

1.2.数据节点数规划

集群节点按照角色划分好后,下一步规划是需要确认数据节点的个数。可以从两个维度去考虑,一个是集群要承载的数据总量,一个是集群要承载最高写入速率。  

1.2.1以数据总量规划

数据总量是指集群需要存储的数据总大小,比如 10TB。我们之所以要按照数据总量来规划,是因为单个节点可以承载的数据量是有上限的。这个上限不是由于磁盘空间的限制,而是由于 JVM 内存限制。ES 需要将一部分数据常驻在 JVM 内存中,比如倒排索引等,这类数据无法被 GC,也就意味着单节点存储的数据越多,占用的内存就越多。在磁盘资源到达极限前,JVM 内存使用量已经到达极限了。 那么单个节点可以存储多少数据量哪? 按照我们的经验,一般 1TB 的数据大约占用 2GB 的 JVM 内存。当然实际情况也和数据字段数目、类型等相关,可以导入样例数据到集群中,通过如下 api 观察实际 1TB 数据会占用的内存大小,其中 segments.memory 即为无法被回收的内存,我们称其为段内存。  

curl -X GET "localhost:9200/_cat/nodes?v&h=name,segments.memory"

  那么我们按照 1TB 数据占用 2GB 的段内存来计算,单个 JVM 的内存不建议超过 30GB,而这 30GB 不可能都分配给段内存,默认新生代占用1/3,大约 10GB,老年代占用 2/3,大约 20GB。段内存最终都会在老年代内,而这 20GB 里面我们分配一半给段内存已经很多了。因为除去段内存,其他像写缓冲(Indexing Buffer)、读缓存(Query Cache)、聚合计算等也都需要内存资源,如果都分配给段内存,那必定会导致严重的 GC,甚至 OOM 的发生。那么按照 10GB 的段内存计算,可以承载的总数据量为 10GB/2GB*1TB=5TB。

因此单节点可以承载的数据量大概在 5TB 左右。

现在我们知道如何计算单节点承载数据量上线的计算方法了,也就更容易理解前面硬件配置一节提到控制内存磁盘比的原因了。

我们再来考虑另一个问题,1TB 是指 ES 中存储数据的大小,但通常我们能拿到的是原始数据大小。 客户可以统计待收集数据每天新增 500GB,要求留存 1 个月。此时我们需要估算该数据存入 ES 后的数据量,一般可以按照如下公式估算:

500GB(每天新增数据量)*1.3(索引膨胀率)*2(副本)*30(留存时长)*1.3(磁盘预留 30% 空间)=50700GB=50TB

此处没有考虑 ES 对数据压缩的处理,估算值偏大。建议通过导入样例数据来实际测量数据在 ES 的用量情况。 假设我们按照单个节点 16Core/64GB/2TB 计算,那么需要的数据节点数为 50TB/2TB=25。当涉及到冷热架构时,再结合数据冷热划分和冷热节点的配置,也可以快速估算出冷热数据节点的个数。  

1.2.2 以数据写入速率规划

数据写入速率是指每秒集群可以处理的数据写入条数,一般单位为 eps, events per second,即每秒事件数。当业务具有短时间的骤增现象时,比如餐饮行业会集中在中午和晚上的用餐高峰,此时集群规划需要按照支撑高峰时刻的写入速率来设计,不能按照实际的数据量,因为数据量在时间维度的分布上是严重不均匀的,可能 80%的数据量只集中在高峰时间的 3 个小时内。如果不按照支撑高峰时段的写入速率来设计,会导致集群在高峰时段无法正常处理数据写入请求,出现请求 reject 情况,甚至集群由于负载过高,导致崩溃的情况出现。 按照数据写入速率评估只需要知道如下两个指标就可以了:

  • 需要支撑的最大数据写入速率是多少
  • 单节点可以支撑的最大数据写入速率是多少

假设需要支撑的最大数据写入速率是 50 万 eps,单节点可以支撑的最大数据写入速率是 4 万 eps,那么所需的数据节点数目=50/4=13。 这里的难点是在于如何获得单节点可以支撑的最大数据写入速率。这个可以通过压测工具以实际数据来对集群进行压力测试来获得,此处不展开讲解。

1.2.3 ES 节点架构图

 一般 ES 集群的架构图如下图所示: image.png

2.数据建模

数据建模是指为数据合理地设计索引名、分片数和 mapping(字段名和字段类型)等,我们接下来从如下三个方面来看一下如何进行合理的数据建模。

2.1索引设计

索引设计主要是确认索引组织方式,如是统一使用一个索引还是拆分索引,如果拆分,那么是按照时间维度拆分还是业务字段拆分。 建议如下: 针对时序数据,可以尽量按照时间拆分索引,按小时、日、周、月甚至年都可以,这种比较适合日志类和指标类场景。这样做的好处如下:

  1. 一个好处是时序类数据通常只会查询最近的数据,按照时间拆分后,可以减少实际参与查询的分片数,提高查询效率。
  2. 另一个好处是方便索引生命周期管理,比如按时间对索引做冷热迁移、备份、删除等操作。

  针对非时序数据,避免大索引,按业务属性或者部门做索引拆分,尤其是数据间毫无关联的情况下。 另外可以按需使用如下特性:

  1. 自定义路由 通过自定义路由值,可以在索引阶段将特定数据路由到特定的分片上,这样在后续查询时可以减少查询的分片数,提升查询效率。
  2. 使用别名 别名类似一个软链接,可以同时指向多个索引,在具体实践时应该尽量使用别名,它可以极大地提升维护的灵活性。比如在某索引数据量暴增,需要重新设计 shard 数时,只要将别名指向新设计的索引就可以了。比如 mapping 字段设计错误时,只要将别名指向新修正的索引就可以了。
  3. 使用滚动索引管理Rollover 滚动索引管理,也就是 Rollover,可以很好地控制分片大小。当无法确定每日的新增数据量时,可以采用 Rollover 机制,弹性地去适配数据量的增长。数据过小时,就新建 1 个索引,过大时,就新建多个索引,而且可以保证每个索引的分片都在合理范围内。

  

2.2 分片设计

分片设计主要是确定索引的主分片个数和副本数。主要从如下几个方面考虑:

2.2.1 分片大小

官方最佳实践建议单个分片大小控制在 50GB 以内。日志场景建议控制在 30GB左右,搜索场景建议控制在 10GB 左右。原因如下:

  • 获得更快的恢复和迁移速度。小分片相对于大分片可以更快地读取到内存中,完成加载恢复。小分片也更便于迁移,利于集群做数据再均衡 rebalance 操作。
  • 减少段合并(Segement Merge)所需的资源。分片越大,意味着其中的段(Segment)更多更大。段合并过程中需要用到 2 倍的磁盘资源,待合并的段越大,耗费的时间越长,对磁盘 IO 带来的压力也会越长。
  • 提升更新(Update)类操作的性能。更新类操作底层是先查找,再删除,再更新的机制。分片越大,待查找的 segment 也会越多,响应延迟也会越慢。

2.2.2 查询性能

当索引只有 1 个分片时,所有的查询都会落到 1 个分片所在的节点上,单个节点的处理能力是有限的。这个时候可以通过增加分片数的方式,将查询压力分摊到多个节点,然后再合并处理。当然分片也不是越多越好,分片越多,并发查询的请求也越多,性能反而会下降。具体需要根据实际的数据和集群配置进行压测,选择适合自己业务需求的分片数。

2.2.3 写入性能

写入操作过程需要生成索引数据,这个过程需要大量的 CPU 资源,单个节点的性能是有限的,我们可以通过增加分片数的方式,将写入压力分散到多个节点上,提升集群整体的写入能力。  

2.2.4 副本设计

副本可以提高集群数据的高可用性,但也不是越多越好。每增加一个副本,相当于增加了一次写请求处理和一份数据存储,会降低集群整体有效的写入性能和存储空间,一般设置为 1 即可,只有少数写少读多的场景,可以适当调大。

2.2.5 设计实例 

在设计分片数时,还要考虑数据节点数,一般将分片数设置为节点倍数利于平均分片到各个节点,保证各个节点负载一直。 假设数据节点数为 10,某索引数据总量为 1TB,那么我们以每个分片 30GB 计算,主分片数为 1024GB/30GB=35。考虑数据节点数为 10,那么最终确认主分片数为 40。 如前文所述,rollover 滚动索引管理是一个很好的控制索引分片大小的机制,如果使用 rollover 机制,那么上面的分片数,我们可以设置为 10,然后 rollover 的条件是索引大小超过 300GB。这样做的好处是减少单个节点并发写入的分片数,可以降低 cpu 负载,由于分片的写入是并发的,也不会导致写入性能的损失。  

2.3 Mapping 设计

Mapping 的设计主要设计字段名、字段类型及属性的设定,建议如下:

2.3.1 字符串类型合理选择 text 和 keyword

对于字符串类型的字段,es 默认会创建 text 类型字段和一个名为 “keyword” 的 keyword 类型的字段。 Text 类型是指这里的字符串会进行分词,即分成多个 term,用于全文检索,比如博客文章、快递地址之类的信息。 Keyword 类型是指这里的字符串作为一个 term 来处理,主要用于精确匹配的数据,比如状态码、标签等。 es 的默认处理虽然同时支持了全文检索和精确匹配,但同时也导致存储成本急剧增加。因此针对字符串类型,不建议采用默认配置,而要明确指定其类型。如果无法明确确定索引的所有字符串类型的字段,那么可以结合 es 动态模板的配置,对字符串类型进行默认设定,如下所示:

PUT my_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

  另外对于一些枚举类型的字段,即便其是数值,需要设置为 keyword 类型,比如 http status code、id、邮政编码、数据库主键等。对于这类可以枚举有限的数值,倒排索引的效率远高于数值类型。如果设定为 number 类型,会导致查询效率极低。

2.3.2 index 参数设定

默认所有字段都会被索引,index 参数为 true。如果某字段不需要被搜索,只需要在结果里面返回,那么就不必为其创建倒排索引,此时可以将其 index 参数设置为 false,节省存储空间,提升索引性能。

2.3.3 store 参数设定

默认 _source 字段存储了原始文档,如果你想自定义字段存储,可以将其 store 参数设置为 true,一般此时也会同时将 _source 设置为 false。这样可以节省磁盘空间,提升索引性能。

2.3.4 enabled 参数设定

对于不需要查询,也不需要单独存储的字段,可以直接设定 enabled 为 false,类似下面:

"session_data": { 
    "enabled": false
}

等同于

"session_data": { 
    "type": "text",
    "index": false,
    "store": false
}

2.3.5 doc_values 参数设定

对于不需要聚合计算和排序的字段,可以将其 doc_values 设置为 false,节省磁盘空间,提升索引性能。

2.3.6 norms 参数设定

如果一个 text 类型的字段只需要全文检索,不需要归一化的因子,比如字段提权(field boost)、字段值长度因子等,可以将 norms 设为 false,节省磁盘空间,提升索引性能。 norms 含义可参考如下文档 http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/search/similarities/TFIDFSimilarity.html#formula_norm 

2.3.7 index_options 参数设定

index_options 用于控制倒排索引记录的内容,有如下 4 种配置

  • docs 只记录 doc id
  • freqs 记录 doc id 和 term frequencies
  • positions 记录 doc id、term frequencies 和 term position
  • offsets 记录 doc id、term frequencies、term position 和 character offsets

text 类型默认配置为 positions,其他默认为 docs,记录内容越多,占用空间越大  

2.3.9 null_value 参数设定

null 值不会被索引,也就不能被检索,可以通过该设定给字段一个默认值

2.3.10 eager_global_ordinals 参数设定

keyword 类型的 terms 聚合分析 会依赖 global ordinals,而其加载是在 terms 聚合查询的时候进行。通过设定 eager_global_ordinals 参数可以将 global ordinal 的加载更新提前到数据写入时,从而牺牲写入的效率以提升查询的效率。

2.4 Mapping 设定流程图

总结 Mapping 设定流程图如下: image.png

3.写入优化

3.1 写入优化的目标

  • 目标是增大写吞吐量-EPS(Events Per Second),越高越好
  • 优化方案
    • 客户端:多线程写,批量写
    • ES端:首先保证最优的数据建模,然后在 refresh、translog 等其他地方做优化

3.2 refresh 优化

  • 目标为降低 refresh 的频率
    • 增大 refresh_interval ,降低实时性,以增大一次 refresh 处理的文档数,降低 segment 生成的数量。默认是1s,设置为 -1 直接禁止自动 refresh
    • 增大 index buffer size,参数为 indices.memory.index_buffer_size(静态参数,需要设定在 elasticsearch.yml 中),默认为 10%。可以适当增大来提高写处理能力,按照一个 shard 512MB 来评估,比如 heap 是 30GB,那么 index_buffer_size 是 3GB,那么比较好的并发写入 shard 数为 3 * 1024MB/512MB = 6

3.3 translog 优化

  • 目标是降低 translog 写磁盘的频率,从而提高写效率,但会降低容灾能力
    • index.translog.durability 设置为 asyncindex.translog.sync_interval 设置需要的大小,比如120s,那么 translog 会改为每120s 写一次磁盘 
    • **index.translog.flush_threshold_size **默认为512mb,即 translog 超过该大小时会触发一次 flush,那么调大该值可以降低 flush 发生的频率

3.4 其他优化

  • 尽量不指定 id,让 es 自动生成文档 id
  • 更新频繁的场景要控制 Shard 大小,Shard 越大,更新的速度越慢,可以根据实际压测效果来决定 Shard 大小,比如 10GB
  • 使用 Bulk Request 批量写入,每次写入的数据量控制在 10~20MB 左右
  • 初始化数据导入可以临时关闭 refresh_interval 和取消副本,在导入完毕后再恢复相关配置
  • 使用更好的磁盘和阵列方式,比如 SSD Raid0
  • 使用 rollover 的方式来处理大索引,设计分片数与节点数相同,降低 cpu 负载,然后当分片大小超过 30GB 时,rollover 到新的索引
  • 合理地设计 shard 数,并保证 shard 均匀地分配在所有 node 上,充分利用所有 node 的资源
    • index.routing.allocation.total_shards_per_node 限定每个索引在每个 node 上可分配的总主副分片数
    • 5个 node,某索引有10个主分片,1个副本,上述值应该设置为多少?
      • (10+10)/5=4
      • 实际要设置为5个,防止在某个 node 下线时,分片迁移失败的问题

4.读取优化

4.1 读取优化的目标

目标如下:

  • 提高每秒查询数(QPS, Query Per Second),越高越好
  • 降低查询延迟(Latency)

读取性能主要受以下几方面影响:

  • 数据模型是否符合业务模型?
  • 数据规模是否过大?
  • 索引配置是否优化?
  • 查询语句是否优化?

4.2 数据建模优化

  • 字符串类型自主指定,避免默认多字段模式,可使用动态模板
  • 枚举/id 类型要使用 keyword
  • 避免使用 nested 和 parent/child 类型
  • 不需要检索 index -> false
  • 不需要排序/聚合/script 获取 doc_values->false 
  • 只是获取该字段的值 enabled -> false
  • 不算分,只过滤和聚合分析 norms->false

4.3 查询语句调优

  • 查询语句调优主要有以下几种常见手段:
    • 尽量使用  Filter 上下文,减少算分的场景,由于 Filter 有缓存机制,可以极大提升查询性能
    • 尽量不使用 Script 进行字段计算或者算分排序等,只建议测试时使用,一旦验证通过,则固化为具体字段
    • 结合 profile、explain API 分析慢查询语句的症结所在,然后再去优化数据模型

4.4 索引时预处理字段

该策略为以空间换时间的方法,在某些情况下,可以通过将查询需求预处理为固定字段的方式提升读取性能,举例如下: 有如下的 range 范围查询需求,且范围固定为三个。 image.png 那么我们可以按照如下方式提前创建一个 price_range 的字段,然后数值类型的计算转换为 terms  分桶计算,提升性能。  image.png

4.5 Date 类型搜索优化

  • 避免使用 now,增加 Cache 命中率
  • 使用 Date Match 中的 round 技巧,增加 Cache 命中率

image.png Date Math 的使用方法可以参见如下文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/date-math-index-names.html

4.6 Force Merge

  • 业务低峰期针对索引做 merge 操作,减少 segment 数,提升查询效率
  • 单个 segment 可以 2GB 来计算,减少大 segment 数量,提升 merge 速率
  • 在更新比较频繁的场景,建议只针对 read-only 的索引或在写操作低峰期做此操作
POST /twitter/_forcemerge

4.7 Terms 聚合查询优化

  • 如果针对某些字段大量使用 terms 聚合分析,可以采用预加载 global ordinal 的方式来提升查询效率

image.png

4.8 Preference 优化

  • 在 user 或者 session 级别设定分片优先访问规则,提高 Cache 命中率
  • 缺点是如果自定义值不好,会导致查询请求分布不均,出现查询热点

image.png

可以参见如下文档: https://www.elastic.co/guide/en/elasticsearch/reference/6.4/search-request-preference.html

4.9 减少查询字段数

  • 同时查询的字段越多,响应越慢
  • 可以使用 copy_to 减少同时查询的字段数

4.10 使用自适应副本选择

  • Adaptive Replica Selection
  • 6.x 默认关闭,7.x 默认打开

image.png

4.11 减少查询的分片数

  • 可以通过自定义路由规则来减少查询时的分片数
  • 通过合理的规划索引划分规则来减少查询的分片数,如按照日期、地区来划分索引等

 

0

评论区