[转载] Elasticsearch 调优篇 05 - Elasticsearch 搜索层面最全优化

1、尽量少的字段

elasticsearch 的搜索引擎严重依赖于底层的 filesystem cache,你如果给 filesystem cache 更多的内存,尽量让内存可以容纳所有的 index segment file 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。

比如说,你的 es 节点有 3 台机器,每台机器 64G,总内存 64*3。每台机器给 es jvm heap 是 32G,那么剩下来留给 filesystem cache 的就是每台机器才 32g,总共集群里给 filesystem cache的就是 32 * 3 = 96gb 内存。如果此时你整个磁盘上索引数据文件,在 3 台机器上一共占用了 1T 的磁盘容量,es 数据量是 1t。filesystem cache 的内存才100g,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。

归根结底,你要让 es 性能好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半 。比如说,你一共要在 es 中存储 1T 的数据,那么你的多台机器留给 filesystem cache 的内存加起来综合至少要到 512G,至少半数的情况下,搜索是走内存的,性能一般可以到几十毫秒。

如果最佳的情况下,我们自己的生产环境实践经验,最好是用 es 就存少量的数据,就是你要用来搜索的那些索引,内存留给 filesystem cache 的就 100G,那么你就控制在 100gb 以内,相当于你的数据几乎全部走内存来搜索,性能非常之高,一般可以在 10ms 以内。

所以在 es 里就存储必须用来搜索的数据,比如说你现在有一份数据,有 100 个字段,其实用来搜索的只有 10 个字段,建议是将 10 个字段的数据存入 es,剩下 90 个字段的数据,可以放 mysql,hadoop,hbase 等都可以。 这样的话,es 数据量很少,10 个字段的数据,都可以放内存,就用来搜索,搜索出来一些 id,通过 id 去 mysql,hbase 里面去查询明细的数据。

2、document 模型设计

document 模型设计是非常重要的,不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。

es 能支持的操作就是那么多,不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作,尽量在 document 模型设计的时候,写入的时候就完成。

另外对于一些太复杂的操作,比如 join,nested,parent-child 搜索都要尽量避免,性能都很差的。

在搜索/查询的时候,要执行一些业务强相关的特别复杂的操作:

  1. 在写入数据的时候,就设计好模型,加几个字段,把处理好的数据写入加的字段里面
  2. 自己用 java 程序封装,e s能做的,用 es 来做,搜索出来的数据,在 java 程序里面去做,比如说我们基于 es 用 java 封装一些特别复杂的操作

3、预索引数据

例如电商平台,有很多 range aggregation 操作,但有很多其范围是可以确定下来的,那么在索引的时候就增加相关的字段。

例如,要做一个按照价格区间进行分类统计的,但是这个价格区间基本上是固定的,形如 0 ~ 100,100 ~ 200, 200 ~ 500 ……,那么我们再索引的时候针对价格就可以明确确定下来该商品到底是属于哪个价格区间的,打上对应的标签,这样再聚合和查询的时候可以直接通过该字段来快速的进行定位,提升搜索性能。

4、日期查询

这里针对日期查询单独提出来,是因为日期查询写的不规范时会导致性能急剧下降。

尽量不要使用 now 这种内置函数来执行日期查询,因为默认 now 是到毫秒级的,是无法缓存结果,尽量使用一个阶段范围。

比如 now/m,就是到分钟级,那么如果一分钟内,都执行这个查询,是可以取用查询缓存的

5、禁用动态类型映射

默认的动态 string 类型映射会将 string 类型的 field 同时映射为 text 类型以及 keyword 类型,这会浪费磁盘空间,因为我们不一定两种都需要。

通常来说,id field 这种字段可能只需要 keyword 映射,而 body field 可能只需要 text field。

映射一个content,content: text,content.内置字段: keyword

可以通过手动设置 mappings 映射来避免字符串类型的 field 被自动映射为 text 和 keyword:

PUT index
{
 "mappings": {
   "type": {
     "dynamic_templates": [
       {
         "strings": {
           "match_mapping_type": "string",
           "mapping": {
              "type":"keyword"
           }
         }
       }
     ]
    }
  }
}

6、局部预热

如果我们重启了 es,或者重建了一个新的集群,那么 filesystem cache 就会变为空壳了,就需要不断的查询才能重新让 filesystem cache 热起来。

我们可以先手动对一些数据进行查询。比如说,你本来一个查询,要用户点击以后才执行,才能从磁盘加载到 filesystem cache 里,第一次执行要 1s,以后每次就几十毫秒。

你完全可以起一个程序执行那个查询,预热,数据就加载到 filesystem cahce,程序执行的时候是 1s,以后用户真的来看的时候就才几十毫秒。

7、适当增大副本

一般副本量大会增加搜索的吞吐量,但是也会降低索引性能,所以在实际场景中会根据不同的业务场景来设置合理的副本数量,一般我们设置副本数量常为 2。

如果更新不是很频繁而查询量比较大的时候,可以适当增大副本数量,例如增大的 3、4、5 等。

如果更新比较频繁,而查询的 qps 不高的话,可以将副本数量设置为 1。

8、用性能更好的硬件设备

可以使用性能更好的 SSD 磁盘来替代 机械磁盘,尤其是这种随机读取的性能按照厂商给到的数据会比机械磁盘快 100 倍。

9、避免稀疏数据

lucene 的内核结构,跟稠密的数据配合起来,性能会更好。

举个例子,比如有 100 个 document,每个 document 都有 20 个 field,20 个 field 都有值,这就是稠密的数据。

但是如果 100 个 document,每个 document 的 field 都不一样,有的 document 有 2 个 field,有的 document 有 50 个 field,这就是稀疏的数据。

原因就是,lucene 在内部会通过 doc id 来唯一标识一个 document,这个 doc id 是 integer 类型,范围在 0 到索引中含有的 document 数量之间。

这些 doc id 是用来在 lucene 内部的 api 之间进行通信的,比如说,对一个 term 用一个 match query 来进行搜索,就会产生一个 doc ids 集合,然后这些 doc ids 会用来获取对应的 norm 值,以用来计算每个 doc 的相关度分数。

而根据 doc id 查找 norm 的过程,是通过每个document的每个 field 保留一个字节来进行的一个算法,这个过程叫做 norm 查找,norm 就是每个 document 的每个 field 保留的一个字节。

对于每个 doc id 对应的那个 norm 值,可以通过读取 es 一个内置索引,叫做 doc_id 的索引中的一个字节来获取。这个过程是性能很高的,而且可以帮助 lucene 快速的定位到每个 document 的 norm 值,但是同时这样的话 document 本身就不需要存储这一个字节的 norm 值了。

在实际运行过程中,这就意味着,如果一个索引有 100 个 document,对于每个 field,就需要 100 个字节来存储 norm 值,即使 100 个 document 中只有 10 个 document 含有某个 field,但是对那个 field 来说,还是要 100 个字节来存储 norm 值。这就会对存储产生更大的开销,存储空间被浪费的一个问题,而且也会影响读写性能。

下面有一些避免稀疏数据的办法:

  1. 避免将没有任何关联性的数据写入同一个索引

  我们必须避免将结构完全不一样的数据写入同一个索引中,因为结构完全不一样的数据,fieldC是完全不一样的,会导致 index 数据非常稀疏。

  最好将这种数据写入不同的索引中,如果这种索引数据量比较少,那么可以考虑给其很少的primary shard,比如 1 个,避免资源浪费。

  1. 对 document 的结构进行规范化/标准化

  即使我们真的要将不同类型的 document 写入相同的索引中,还是有办法可以避免稀疏性,那就是对不同类型的 document 进行标准化。

  比如说,如果所有的 document 都有一个时间戳 field,不过有的叫做 timestamp,有的叫做 creation_date,那么可以将不同 document 的这个 field 重命名为相同的字段,尽量让 documment 的结构相同。

  另外一个,就是比如有的 document 有一个字段,叫做 goods_type,但是有的 document 没有这个字段,此时可以对没有这个字段的 document,补充一个 goods_type 给一个默认值,比如 default。

  1. 避免使用多个 types 存储不一样结构的 document

  很多人会很喜欢在一个 index 中放很多个 types 来存储不同类型的数据。

  但是其实不是这样的,最好不要这么干,如果你在一个 index 中有多个 type,但是这些 type 的数据结构不太一样,那么这些 type 实际上底层都是写到这个索引中的,还是会导致稀疏性。

  如果多个 type 的结构不太一样,最好放入不同的索引中,不要写入一个索引中。

  1. 对稀疏的 field 禁用 norms 和 doc_values

  如果上面的步骤都没法做,那么只能对那种稀疏的 field,禁止 norms 和 doc_values 字段,因为这两个字段的存储机制类似,都是每个 field 有一个全量的存储,对存储浪费很大。

  如果一个 field 不需要考虑其相关度分数,那么可以禁用 norms,如果不需要对一个 field 进行排序或者聚合,那么可以禁用 doc_values 字段。

作者:星火燎原智勇
原文链接:www.cnblogs.com/liang1101/p/131889...

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!