4.3.2.Elasticsearch 开发人员最佳实践指南
创作人:铭毅天下
几个月以来,我一直在记录自己开发 Elasticsearch 应用程序的最佳实践。本文梳理的内容试
图传达 Java 的某些思想,我相信其同样适用于其他编程语言。我尝试尽量避免重复教程和
Elasticsearch 官方文档中已经介绍的内容。
本文梳理的内容都是从线上实践问题和个人总结的经验汇总得来的。
文章从以下几个维度展开讲解:
·映射(Mapping)
·设置(Setting)
·查询方式(Querying)
·实战技巧(Strategy)
映射(Mapping)
避免使用 Nested 类型
每个 Elasticsearch 文档都对应一个 Lucene 文档。
Nested 类型是个例外,对于 nested 类型,每个字段都作为单独的文档存储与父 Lucene 的关
联。
其影响是:
·Nested 与父文档中的字段相比,查询字段的速度较慢。
·检索匹配 Nested 字段会降低检索速度。1595
· 一旦更新了包含 Nested 字段的文档的任何字段(与是否更新嵌套字段无关,则所有基础
Lucene 文档(父级及其所有 Nested 子级)都需要标记为已删除并重写)。除了降低更新
速度外,此类操作还会产生大量垃圾文件,直到通过段合才能进行清理。
在某些情况下,你可以将 Nested 字段展平。
例如,给定以下文档:
{ "attributes": [ {"key": "color", "val": "green"}, {"key": "color", "val": "blue"}, {"key": "size", "val": "medium"} ] }
展平如下:
{ "attributes": { "color": ["green", "blue"], "size": "medium" } }
Mapping 设置 strict
实际业务中,如果不明确设定字段类型,Elasticsearch 有动态映射机制,会根据插入数据自动
匹配对应的类型。
假定本来准备插入浮点型数据,但由于第一个插入数据为整形,Elasticsearch 自定会判定为
long 类型,虽然后续数据也能写入,但很明显“浮点类型”只阉割保留了整形部分。
铭毅给个 Demo 一探究竟:
POST my_index03/_doc/1 { "tvalue":35 } POST my_index03/_doc/2 { "tvalue":3.1415 } GET my_index03/_mapping GET my_index03/_search { "query": { "term": { "tvalue": { "value": 3.1415 } } } }
注意:term 查询是不会返回结果的。
所以,实战环境中,Mapping 设定要注意如下节点:
·显示的指定字段类型
·尽量避免使用动态模板(dynamic-templates)
·禁用日期检测 (date_detection),默认情况下处于启用状态。“strict” 实践举例:
PUT my_index { "mappings": { "dynamic": "strict", "properties": { "user": { "properties": { "name": { "type": "text" },"social_networks": { "dynamic": "strict", "properties": { "network_id": { "type": "keyword" },"network_name": { "type": "keyword" } } } } } } } }
合理的设置 string 类型
Elasticsearch 5.X 之后,String 被分成两种类型,text 和 keyword。两者的区别:
· text:适用分词全文检索场景
· keyword:适用字符串的精准匹配场景
默认,如果不显示指定字段类型,字符串类型自定映射后的 Mapping 如下所示:
"cont" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } }
而公司实战的业务场景,通常会面临:
·需不需要分词,不需要的话仅保留 keyword 即可。
·需要用什么分词?英文分词还是中文分词?
·分词后是否还需要排序和聚合,即 fielddata 是否需要开启。
·是否需要精准匹配,即是否需要保留 keyword。
所以,回答了如上几个问题,再有针对的显示设定 string 类型的 Mapping 方为上策
设置(Setting)
在这里我分享了 Elasticsearch 集群设置相关的技巧。
避免过度分片
分片是 Elasticsearch 的最大优势之一,即将数据分散到多个节点以实施并行化。关于这个主
题有过很多讨论。
但请注意,索引的主分片一旦设置便无法更改(除非重建索引或者 reindex)。对于新来者来
说,过度分片是一个非常普遍的陷阱。在做出任何决定之前,请确保先通读官方的这篇博文:
我在 Elasticsearch 集群内应该设置多少个分片?
https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster
铭毅提示:
·主分片数过多:
○ 批量写入或者查询请求被分割成过多的子写入、子查询,导致索引的写入、查询拒绝率上
升。
·主分片数过少:
○ 尤其对于数据量非常庞大的索引,若分片数过少或者就 1 个分片,会导致无法利用集群多
节点资源(也就是分布式特性),造成资源利用率不高或者不均衡,影响写入或者查询效
率。
○ 并且,一旦该大的主分片出现问题,恢复起来耗时会非常长。
取消学习任何段合并的技巧
从本质上讲,Elasticsearch 是另一种分布式 Lucene 产品,就像 Solr 一样 。在底层,大多
数时候,每个 Elasticsearch 文档都对应一个 Lucene 文档(
nested 除外)。在 Lucene 中,文档存储在 segment 中。后台的 Elasticsearch 通过以下两种模式连续维护这些 Lucene 段:
·在 Lucene 中,当你删除或更新文档时,旧文档被标记为已删除,而新文档被创建。 Elast
icsearch 会跟踪这些标记为 deleted 的文档,适时对其段合并。
·新添加的文档可能会产生大小不平衡的段。Elasticsearch 可能会出于优化目的而决定将它们
合并为更大的段。
实战中一定要注意:段合并是高度受磁盘 I/O 和 CPU 约束的操作。作为用户,我们不想让段
合并破坏 Elasticsearch 的查询性能。
事实上,在某些情况下可以完全避免使用它们:一次构建索引,不再更改它。尽管在许多应用
场景中可能很难满足此条件。一旦开始插入新文档或更新现有文档,段合并就成为不可避免的
一部分。
正在进行的段合并可能会严重破坏集群的总体查询性能。在 Google 上进行随机搜索,你会发
现许多人发帖求助求助:“在段合并中减少对性能的影响的配置“,还有许多人共享某些适用
于他们的配置。但很多配置都是早期 1.X,2.X 版本的设置,新版本已经废弃。
综上我进行段合并的经验法则如下:
·取消学习任何段合并的技巧。早期版本的段合并配置是与 Elasticsearch 的内部紧密耦合的
操作,新版本一般不再兼容。几乎没有“神秘”的底层配置修改可以使它运行得更快。
· 找到 translog flush 的最优配置 。尝试调整 index.translog.sync_interval 和 index.transl
og.flush_threshold_size 设置。
详见:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-tra
nslog.html
· 动态调整 index.refresh_interval 以满足业务需求。如果实时性要求不高,可以调大刷新频
率(默认是 1s,可以调到 30s 甚至更大)。
PUT /twitter/_settings { "index" : { "refresh_interval" : "30s" } }
注意 JVM 内存设置
Elasticsearch 可以根据两个主要内存设置产生引人注目的性能特征:
· JVM 堆空间——主要用途:缓存(节点缓存、分片请求缓存、Field data 缓存以及索引缓存)
· 堆外内存空间—— Lucene 段文件缓存
提醒你不要根据过去的非 Elasticsearch JVM 应用程序经验来盲目设置 Elasticsearch JVM堆大小。
详见官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
《Elastic Stack 实战手册》——四、应用实践——4.3 性能优化场景——4.3.2.Elasticsearch 开发人员最佳实践指南(2): https://developer.aliyun.com/article/1225222spm=a2c6h.13148508.setting.15.438f4f0e18NXNE