浅述Elasticsearch开发规范指南(持续更新)

2,193 阅读13分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

Elasticsearch 是一款流行的分布式开源搜索和数据分析引擎,具备高性能、易扩展、容错性强等特点。它强化了 Apache Lucene 的搜索能力,把掌控海量数据索引和查询的方式提升到一个新的层次。

本文将根据ElasticSearch的特点和日常开发中的经验,整理了在日常使用ElasticSearch进行开发的一些最佳实践。

集群部署优化建议

选择合理的硬件配置:尽可能使用SSD

Elasticsearch 最大的瓶颈往往是磁盘读写性能,尤其是随机读取性能。使用SSD(PCI-E接口SSD卡/SATA接口SSD盘)通常比机械硬盘(SATA盘/SAS盘)查询速度快5~10倍,写入性能提升不明显。

对于文档检索类查询性能要求较高的场景,建议考虑 SSD 作为存储,同时按照 1:10 的比例配置内存和硬盘。

对于日志分析类查询并发要求较低的场景,可以考虑采用机械硬盘作为存储,同时按照 1:50 的比例配置内存和硬盘。

单节点存储数据建议在2TB以内,不要超过5TB,避免查询速度慢、系统不稳定。

给JVM配置机器一半的内存,但是不建议超过32G

修改 conf/jvm.options 配置,-Xms 和 -Xmx 设置为相同的值,推荐设置为机器内存的一半左右,剩余一半留给操作系统缓存使用。

JVM 内存建议不要低于 2G,否则有可能因为内存不足导致 ES 无法正常启动或内存溢出,JVM 建议不要超过 32G,否则 JVM 会禁用内存对象指针压缩技术,造成内存浪费。

机器内存大于 64G 内存时,推荐配置 -Xms30g -Xmx30g。

JVM 堆内存较大时,内存垃圾回收暂停时间比较长,建议配置 ZGC 或 G1 垃圾回收算法。

规模较大的集群配置专有主节点,避免脑裂问题

Elasticsearch 主节点负责集群元信息管理、index 的增删操作、节点的加入剔除,定期将最新的集群状态广播至各个节点。

在集群规模较大时,建议配置专有主节点只负责集群管理,不存储数据,不承担数据读写压力。

具体配置如下:

# 专有主节点配置(conf/elasticsearch.yml):
node.master: true
node.data: false
node.ingest: false

# 数据节点配置(conf/elasticsearch.yml):
node.master: false
node.data: true
node.ingest: true

Elasticsearch 默认每个节点既是候选主节点,又是数据节点。最小主节点数量参数 minimum_master_nodes 推荐配置为候选主节点数量一半以上,该配置告诉 Elasticsearch 当没有足够的 master 候选节点的时候,不进行 master 节点选举,等 master 节点足够了才进行选举。

例如对于 3 节点集群,最小主节点数量从默认值 1 改为 2。

# 最小主节点数量配置(conf/elasticsearch.yml):
discovery.zen.minimum_master_nodes: 2

Linux操作系统调优

关闭交换分区,防止内存置换降低性能。

具体配置如下:

# 将 /etc/fstab 文件中包含swap的行注释掉
sed -i '/swap/s/^/#/' /etc/fstab
swapoff -a

# 单用户可以打开的最大文件数量,可以设置为官方推荐的65536或更大些
echo "* - nofile 65536" >> /etc/security/limits.conf

# 单用户线程数调大
echo "* - nproc 131072" >> /etc/security/limits.conf

# 单进程可以使用的最大map内存区域数量
echo "vm.max_map_count = 655360" >> /etc/sysctl.conf

# 参数修改立即生效
sysctl -p

索引性能调优建议

严格设置存储和索引开关,不要将数据全部存储或者全部索引

Elasticsearch支持持久化和索引,但是Elasticsearch主要场景是全文检索,成本昂贵。建议根据业务只将需要索引的Field进行索引,不仅节省索引成本,而且在操作时速度更快、效率更高!

禁止使用多type索引 ,避免索引稀疏

Elasticsearch支持对同一个索引,划分不同的type进行读写,数据以type级别进行隔离。但是Elasticsearch多type索引共用_id,其version往往容易产生冲突,而且6.x版本开始逐渐转向单type索引的设计,7.x之后将不再支持多type索引。

分片数根据业务数据量和集群规模进行设置

Elasticsearch默认分片数是5, 不同的业务索引分片数应该根据实际情况进行设置,如果索引数据量特别小且未来的增量有限,可以适当减少分片数,因为分片作为Elasticsearch集群的元数据在分片数很多的情况下,集群的同步管理和故障恢复会存在一定的风险。

如果索引数据量特别大,需要根据数据量和集群规模进行设置,一个分片建议在10亿级别,最大数据量建议是40亿。同时分片数也受集群规模影响,集群节点数太少不宜设置过多的分片数,会导致同一个索引的多个分片存在于一个Elasticsearch节点中,出现故障恢复较慢。

您可以在集群节点上保存的分片数量与您可用的堆内存大小成正比,但这在Elasticsearch中没有的固定限制。 一个很好的经验法则是:确保每个节点的分片数量保持在低于每1GB堆内存对应集群的分片在20-25之间。 因此,具有30GB堆内存的节点最多可以有600-750个分片,但是进一步低于此限制,您可以保持更好。 这通常会帮助群体保持处于健康状态。

副本数不大于3

Elasticsearch默认副本数是1,即存在1个primary和1个replica共两份数据,主备形式同步,为了提升数据的安全性,避免数据丢失,可以设置副本数为2,即存在1个primary和两个replica共三份数据。但是没有必要设置过大的副本数,Elasticsearch的复制会对集群性能产生影响,特别是refresh_interval设置比较小的集群。

禁止自动创建索引

Elasticsearch的索引默认可以自动创建,必须禁用掉。

自动创建索引无法避免误操作带来的元数据治理问题,设置action.auto_create_indexfalse

创建完索引,禁用掉自动类型mapping

Elasticsearch的索引默认可自动类型mapping,必须禁用掉。自动创建mapping会带来类型与实际类型不匹配的问题,设置index.mapper.dynamicfalse

字符串mapping设置,优先使用keyword

Elasticsearch的索引默认支持keyword和text,text用于分词场景,keyword不支持分词,不使用分词时,需要明确指定类型为keyword。

mapping设置,慎用嵌套类型

Elasticsearch的索引默认支持mapping嵌套,正常情况下慎用嵌套类型,如果使用嵌套类型,建议深度不要超过2,嵌套类型文档内部更新无法原子更新。

join设计,慎用nested和parent-child

Elasticsearch的索引默认不支持join,建议应用程序自身处理。如果需要在父子两个文档都需要进行过滤,或者需要返回父子两个文档的field,建议使用nested,其他场景建议使用parent-child。

通常情况下,nested比parent-child查询快5-10倍,但parent-child更新文档比较方便。

索引字段数不要太多,字段类型占用内存越少越好

Elasticsearch的索引最多默认支持1000个字段,Elasticsearch的字段数越多,对于集群缓存的消耗越严重,通常建议不要超过50个字段。

建议设计过程中,非索引和聚合字段不要存放到Elasticsearch中,可以使用其他存储引擎,比如mysql、hbase等。

禁用掉_all和_source

Elasticsearch的索引默认开启_all和_source,除了正常的存储和索引之外,还存放着原始写入记录、其他的元数据信息,这些信息可以直接使用正常的存储和索引filed进行替代,禁用掉可以提升集群性能,也可以在进行查询和搜索时进行exclude或者include设置。

设置合理延迟分片平衡时间

Elasticsearch的集群对网络的稳定性有很大的依赖,默认延迟分片平衡时间是1m,即副本在1分钟内恢复到集群,则不会进行重新分片。如果运维过程中,断网时间较长,建议修改该参数,避免来回创建分片、移动分片,为运维和节点异常恢复提供缓冲时间。

查询性能调优建议

在查询中提供明确的超时

几乎所有的Elasticsearch API都允许用户指定超时。

找出并摆脱耗时长的操作,节省相关资源,建立稳定的服务,这将对你的应用程序和Elasticsearch集群都有帮助。

谨慎使用Scan和Scroll

Elasticsearch默认只支持查询最多10000条记录,from+size不能大于10000。这种深度分页会随着请求的页次增加,所消耗的内存和时间的增长也是成比例的增加。

深分页场景可以使用Scan或者Scroll,但是谨慎使用Scan和Scroll,因为滚动上下文代价很高,建议不要将其用于实时用户请求,对于大索引,频繁的Scan和Scroll对Elasticsearch集群的稳定性影响非常大,可能会导致jvm假死等甚至宕机等恶劣结果发生。

  • 针对实时场景:禁止掉
  • 针对离线场景:谨慎使用

使用size:0和includes/excludes限定字段返回

Elasticsearch在添加size:0子句前后会带来显著的性能差异 。

除非业务需要,才返回必要字段,否则,无需返回的字段通过includes和excludes控制。

API调用使用rest接口,慎用transport方式

Elasticsearch的客户端api,native-api支持transport方式访问集群,该方式跟集群内部通信采用统一的基于netty方式通信,持续性写入系统可以采用transport方式。正常的客户端进行索引的查询,使用基于dsl方式的restful接口即可,httpclient访问效率更高。

避免前缀模糊匹配

Elasticsearch 默认支持通过 *? 正则表达式来做模糊匹配,如果在一个数据量较大规模的索引上执行模糊匹配,尤其是前缀模糊匹配,通常耗时会比较长,甚至可能导致内存溢出。尽量避免在高并发查询请求的生产环境执行这类操作。

某客户需要对车牌号进行模糊查询,通过查询请求  "车牌号:A8848"  查询时,往往导致整个集群负载较高。通过对数据预处理,增加冗余字段 "车牌号.keyword",并事先将所有车牌号按照1元、2元、3元...7元分词后存储至该字段,字段存储内容示例:沪,A,8,4,沪A,A8,88,84,48,沪A8...沪A88488。通过查询"车牌号.keyword:A8848"即可解决原来的性能问题。

避免查询深度翻页

Elasticsearch 默认只允许查看排序前 10000 条的结果,当翻页查看排序靠后的记录时,响应耗时一般较长。使用 search_after 方式查询会更轻量级,如果每次只需要返回 10 条结果,则每个 shard 只需要返回 search_after 之后的 10 个结果即可,返回的总数据量只是和 shard 个数以及本次需要的个数有关,和历史已读取的个数无关。

使用search_after、from&size、scroll的一些说明:

  • 使用search_after时 from设置为0,-1或者直接不加from
  • 使用search_after 进行分页 相比from&size的方式要更加高效,而且在不断有新数据入库的时候仅仅使用from和size分页会有重复的情况
  • 相比使用scroll分页,search_after可以进行实时的查询
  • search_after不适合跳跃式的分页

配置查询聚合节点

查询聚合节点可以发送粒子查询请求到其他节点,收集和合并结果,以及响应发出查询的客户端。通过给查询聚合节点配置更高规格的 CPU 和内存,可以加快查询运算速度、提升缓存命中率。

某客户使用 25 台 8 核 CPU32G 内存节点 Elasticsearch 集群,查询 QPS 在 4000 左右。增加 6 台 16 核 CPU32G 内存节点作为查询聚合节点,观察服务器 CPU、JVM 堆内存使用情况,并调整缓存、分片、副本参数,查询 QPS 达到 12000。

具体配置如下

# 查询聚合节点配置(conf/elasticsearch.yml):
node.master: false
node.data: false
node.ingest: false

配置合适的分词器

Elasticsearch 内置了很多分词器,包括 standard、cjk、nGram 等,也可以安装自研/开源分词器。根据业务场景选择合适的分词器,避免全部采用默认 standard 分词器。

常用的分词器如下:

  • standard:默认分词,英文按空格切分,中文按照单个汉字切分。
  • cjk:根据二元索引对中日韩文分词,可以保证查全率。
  • nGram:可以将英文按照字母切分,结合ES的短语搜索(match_phrase)使用。
  • IK:比较热门的中文分词,能按照中文语义切分,可以自定义词典。
  • pinyin:可以让用户输入拼音,就能查找到相关的关键词。
  • aliws:阿里巴巴自研分词,支持多种模型和分词算法,词库丰富,分词结果准确,适用于电商等对查准要求高的场景。

其他优化建议

不同的业务场景进行集群级别隔离

不要将不同业务场景的数据都放到一个Elasticsearch集群中,建议实时业务和离线业务分开部署。

因为Elasticsearch集群的读写参数配置是所有索引共享的,高峰期会存在相互影响。

建议根据读写频率和实时性要求不同,划分不同的集群进行隔离,根据不同的业务场景设置集群参数!

禁止存放大文本或者二进制数据

Elasticsearch支持大文本和二进制数据,但是大文本和二进制数据会占用集群的内存和磁盘空间,影响集群的磁盘io和缓存效率。

建议将大文本或者二进制数据单独存储在其他的文件系统或者存储系统,然后通过Elasticsearch存放大文本或者二进制数据的存放位置。

命名规范

Elasticsearch的索引命名禁止使用.开始的索引名称,默认Elasticsearch内部或者stack相关组件使用.开始的索引名称。

字段名称禁止使用_开始的索引名称,默认Elasticsearch内部使用的字段名称使用_开始。

索引名称不区分大小写,建议全部由字母、数字组成,字母开头。

同时,索引名称字符长度不要超过32位,字段名称字符长度不要超过32位。

参考文档