从10秒到2秒!ElasticSearch性能调优实践

2019 年 1 月 29 日 51CTO博客

“ELK”是 ElasticSearch、Logstash、Kibana 三门技术的简称,如今 ELK 技术栈在互联网行业数据开发领域使用率越来越高。


做过数据收集、数据开发、数据存储的同学相信对这个简称并不陌生,而 ElasticSearch(以下简称 ES)则在 ELK 栈中占着举足轻重的地位。


前一段时间,我亲身参与了一个 ES 集群的调优,今天把我所了解与用到的调优方法与大家分享,如有错误,请大家包涵与指正。


系统层面的调优


系统层面的调优主要是内存的设定与避免交换内存。ES 安装后默认设置的堆内存是 1GB,这很明显是不够的,那么接下来就会有一个问题出现:我们要设置多少内存给 ES 呢?


其实这是要看我们集群节点的内存大小,还取决于我们是否在服务器节点上还要部署其他服务。


如果内存相对很大,如 64G 及以上,并且我们不在 ES 集群上部署其他服务,那么我建议 ES 内存可以设置为 31G-32G,因为这里有一个 32G 性能瓶颈问题。


直白的说就是即使你给了 ES 集群大于 32G 的内存,其性能也不一定会更加优良,甚至会不如设置为 31G-32G 时候的性能。


以我调优的集群为例,我所调优的服务器节点内存为 64G,服务器节点上也基本不跑其他服务,所以我把 ES 集群内存大小设置为了 31G,以充分发挥集群性能。


设置 ES 集群内存的时候,还有一点就是确保堆内存最小值(Xms)与最大值(Xmx)的大小是相同的,防止程序在运行时改变堆内存大小,这是一个很耗系统资源的过程。


还有一点就是避免交换内存,可以在配置文件中对内存进行锁定,以避免交换内存(也可以在操作系统层面进行关闭内存交换)。


对应的参数:

bootstrap.mlockall: true


分片与副本


分片 (shard)


ES 是一个分布式的搜索引擎, 索引通常都会分解成不同部分, 分布在不同节点的部分数据就是分片。


ES 自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节。创建索引时默认的分片数为 5 个,并且一旦创建不能更改。


副本 (replica)


ES 默认创建一份副本,就是说在 5 个主分片的基础上,每个主分片都相应的有一个副本分片。


额外的副本有利有弊,有副本可以有更强的故障恢复能力,但也占了相应副本倍数的磁盘空间。


那我们在创建索引的时候,应该创建多少个分片与副本数呢?对于副本数,比较好确定,可以根据我们集群节点的多少与我们的存储空间决定。


我们的集群服务器多,并且有足够大多存储空间,可以多设置副本数,一般是 1-3 个副本数,如果集群服务器相对较少并且存储空间没有那么宽松,则可以只设定一份副本以保证容灾(副本数可以动态调整)。


对于分片数,是比较难确定的,因为一个索引分片数一旦确定,就不能更改。


所以我们在创建索引前,要充分的考虑到,以后我们创建的索引所存储的数据量,否则创建了不合适的分片数,会对我们的性能造成很大的影响。


对于分片数的大小,业界一致认为分片数的多少与内存挂钩,认为 1GB 堆内存对应 20-25 个分片,而一个分片的大小不要超过 50G,这样的配置有助于集群的健康。


但是我个人认为这样的配置方法过于死板,我个人在调优 ES 集群的过程中,根据总数据量的大小,设定了相应的分片,保证每一个分片的大小没有超过 50G(大概在 40G 左右),但是相比之前的分片数查询起来,效果并不明显。


之后又尝试了增加分片数,发现分片数增多之后,查询速度有了明显的提升,每一个分片的数据量控制在 10G 左右。


查询大量小分片使得每个分片处理数据速度更快了,那是不是分片数越多,我们的查询就越快,ES 性能就越好呢?


其实也不是,因为在查询过程中,有一个分片合并的过程,如果分片数不断的增加,合并的时间则会增加。


而且随着更多的任务需要按顺序排队和处理,更多的小分片不一定要比查询较小数量的更大的分片更快。如果有多个并发查询,则有很多小碎片也会降低查询吞吐量。


如果现在你的场景是分片数不合适了,但是又不知道如何调整,那么有一个好的解决方法就是按照时间创建索引,然后进行通配查询。


如果每天的数据量很大,则可以按天创建索引,如果是一个月积累起来导致数据量很大,则可以一个月创建一个索引。


如果要对现有索引进行重新分片,则需要重建索引,我会在文章的最后总结重建索引的过程。


参数调优


下面我会介绍一些 ES 关键参数的调优。有很多场景是,我们的 ES 集群占用了多大的 CPU 使用率,该如何调节呢?


CPU 使用率高,有可能是写入导致的,也有可能是查询导致的,那要怎么查看呢?


可以先通过 GET _nodes/{node}/hot_threads 查看线程栈,查看是哪个线程占用 CPU 高:

  • 如果是 elasticsearch[{node}][search][T#10] 则是查询导致的。

  • 如果是 elasticsearch[{node}][bulk][T#1] 则是数据写入导致的。 


我在实际调优中,CPU 使用率很高,如果不是 SSD,建议把 index.merge.scheduler.max_thread_count: 1 索引 merge 最大线程数设置为 1 个,该参数可以有效调节写入的性能。


因为在存储介质上并发写,由于寻址的原因,写入性能不会提升,只会降低。


还有几个重要参数可以进行设置,各位同学可以视自己的集群情况与数据情况而定。


index.refresh_interval:这个参数的意思是数据写入后几秒可以被搜索到,默认是 1s。


每次索引的 refresh 会产生一个新的 lucene 段, 这会导致频繁的合并行为,如果业务需求对实时性要求没那么高,可以将此参数调大,实际调优告诉我,该参数确实很给力,CPU 使用率直线下降。


indices.memory.index_buffer_size:如果我们要进行非常重的高并发写入操作,那么最好将 indices.memory.index_buffer_size 调大一些。


index buffer 的大小是所有的 shard 公用的,一般建议(看的大牛博客),对于每个 shard 来说,最多给 512mb,因为再大性能就没什么提升了。


ES 会将这个设置作为每个 shard 共享的 index buffer,那些特别活跃的 shard 会更多的使用这个 buffer。默认这个参数的值是 10%,也就是 jvm heap 的 10%。


translog:ES 为了保证数据不丢失,每次 index、bulk、delete、update 完成的时候,一定会触发刷新 translog 到磁盘上。


在提高数据安全性的同时当然也降低了一点性能。如果你不在意这点可能性,还是希望性能优先,可以设置如下参数:

"index.translog": {
            "sync_interval""120s",     --sync间隔调高
            "durability""async",       -– 异步更新
            "flush_threshold_size":"1g"  --log文件大小
        }


这样设定的意思是开启异步写入磁盘,并设定写入的时间间隔与大小,有助于写入性能的提升。


还有一些超时参数的设置:

  • discovery.zen.ping_timeout 判断 master 选举过程中,发现其他 node 存活的超时设置。

  • discovery.zen.fd.ping_interval 节点被 ping 的频率,检测节点是否存活。

  • discovery.zen.fd.ping_timeout 节点存活响应的时间,默认为 30s,如果网络可能存在隐患,可以适当调大。

  • discovery.zen.fd.ping_retries ping 失败/超时多少导致节点被视为失败,默认为 3。


其他建议


还有一些零碎的优化建议如下:


插入索引自动生成 id:当写入端使用特定的 id 将数据写入 ES 时,ES 会检查对应的索引下是否存在相同的 id。


这个操作会随着文档数量的增加使消耗越来越大,所以如果业务上没有硬性需求,建议使用 ES 自动生成的 id,加快写入速率。


避免稀疏索引:索引稀疏之后,会导致索引文件增大。ES 的 keyword,数组类型采用 doc_values 结构。


即使字段是空值,每个文档也会占用一定的空间,所以稀疏索引会造成磁盘增大,导致查询和写入效率降低。


我的调优


下面说一说我的调优:主要是重建索引,更改了现有索引的分片数量,经过不断的测试,找到了一个最佳的分片数量。


重建索引的时间是漫长的,在此期间,又对 ES 的写入进行了相应的调优,使 CPU 使用率降低下来。


附上我的调优参数:

index.merge.scheduler.max_thread_count:1 # 索引 merge 最大线程数
indices.memory.index_buffer_size:30%     # 内存
index.translog.durability:async # 这个可以异步写硬盘,增大写的速度
index.translog.sync_interval:120s #translog 间隔时间
discovery.zen.ping_timeout:120s # 心跳超时时间
discovery.zen.fd.ping_interval:120s     # 节点检测时间
discovery.zen.fd.ping_timeout:120s     #ping 超时时间
discovery.zen.fd.ping_retries:6     # 心跳重试次数
thread_pool.bulk.size:20 # 写入线程个数 由于我们查询线程都是在代码里设定好的,我这里只调节了写入的线程数
thread_pool.bulk.queue_size:1000 # 写入线程队列大小
index.refresh_interval:300s #index 刷新间隔复制代码


关于重建索引


在重建索引之前,首先要考虑一下重建索引的必要性,因为重建索引是非常耗时的。


ES 的 reindex api 不会去尝试设置目标索引,不会复制源索引的设置,所以我们应该在运行_reindex 操作之前设置目标索引,包括设置映射(mapping),分片,副本等。


第一步,和创建普通索引一样创建新索引


当数据量很大的时候,需要设置刷新时间间隔,把 refresh_intervals 设置为 -1,即不刷新。


number_of_replicas 副本数设置为 0(因为副本数可以动态调整,这样有助于提升速度)。

{
    "settings": {

        "number_of_shards""50",
        "number_of_replicas""0",
        "index": {
            "refresh_interval""-1"
        }
    }
    "mappings": {
    }
}


第二步,调用 reindex 接口


建议加上 wait_for_completion=false 的参数条件,这样 reindex 将直接返回 taskId。

POST _reindex?wait_for_completion=false

{
  "source": {
    "index""old_index",   //原有索引
    "size"5000            //一个批次处理的数据量
  },
  "dest": {
    "index""new_index",   //目标索引
  }
}


第三步:等待


可以通过 GET _tasks?detailed=true&actions=*reindex 来查询重建的进度。如果要取消 task 则调用_tasks/node_id:task_id/_cancel。


第四步:删除旧索引,释放磁盘空间


更多细节可以查看 ES 官网的 reindex api。那么有的同学可能会问,如果我此刻 ES 是实时写入的,那咋办呀?


这个时候,我们就要重建索引的时候,在参数里加上上一次重建索引的时间戳。


直白的说就是,比如我们的数据是 100G,这时候我们重建索引了,但是这个 100G 在增加,那么我们重建索引的时候,需要记录好重建索引的时间戳。


记录时间戳的目的是下一次重建索引跑任务的时候不用全部重建,只需要在此时间戳之后的重建就可以,如此迭代,直到新老索引数据量基本一致,把数据流向切换到新索引的名字。

POST /_reindex
{
    "conflicts""proceed",          //意思是冲突以旧索引为准,直接跳过冲突,否则会抛出异常,停止task
    "source": {
        "index""old_index"         //旧索引
        "query": {
            "constant_score" : {
                "filter" : {
                    "range" : {
                        "data_update_time" : {
                            "gte" : 123456789   //reindex开始时刻前的毫秒时间戳
                            }
                        }
                    }
                }
            }
        },
    "dest": {
        "index""new_index",       //新索引
        "version_type""external"  //以旧索引的数据为准
        }
}


以上就是我在 ES 调优上的一点总结,希望能够帮助到对 ES 性能有困惑的同学们,谢谢大家。


作者:皮蛋二哥

编辑:陶家龙、孙淑娟

出处:转载自微信公众号:创宇前端(ID:KnownsecFED)

精彩文章推荐:

一文说尽MySQL事务及ACID特性的实现原理

日均5亿查询量,京东到家订单中心ES架构演进

有赞官宣996工作制:平衡不好可以离婚!

登录查看更多
0

相关内容

ELK = Elasticsearch, Logstash, Kibana 是一套实时数据收集,存储,索引,检索,统计分析及可视化的解决方案。最新版本已经改名为Elastic Stack,并新增了Beats项目。
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
161+阅读 · 2020年5月14日
【图神经网络(GNN)结构化数据分析】
专知会员服务
115+阅读 · 2020年3月22日
【2020新书】Kafka实战:Kafka in Action,209页pdf
专知会员服务
67+阅读 · 2020年3月9日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
【LinkedIn报告】深度自然语言处理的搜索系统,211页pdf
专知会员服务
106+阅读 · 2019年6月21日
在K8S上运行Kafka合适吗?会遇到哪些陷阱?
DBAplus社群
9+阅读 · 2019年9月4日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
硬核实践经验 - 企鹅辅导 RN 迁移及优化总结
IMWeb前端社区
5+阅读 · 2019年5月6日
介绍高维超参数调整 - 优化ML模型的最佳实践
AI研习社
7+阅读 · 2019年4月17日
亿级订单数据的访问与存储,怎么实现与优化?
码农翻身
16+阅读 · 2019年4月17日
基于 Storm 的实时数据处理方案
开源中国
4+阅读 · 2018年3月15日
优化哈希策略
ImportNew
5+阅读 · 2018年1月17日
一篇文章读懂阿里企业级数据库最佳实践
阿里巴巴数据库技术
5+阅读 · 2017年12月20日
机器学习(26)之K-Means实战与调优详解
机器学习算法与Python学习
4+阅读 · 2017年11月19日
Heterogeneous Graph Transformer
Arxiv
27+阅读 · 2020年3月3日
Arxiv
23+阅读 · 2018年8月3日
Arxiv
8+阅读 · 2018年2月23日
VIP会员
相关资讯
在K8S上运行Kafka合适吗?会遇到哪些陷阱?
DBAplus社群
9+阅读 · 2019年9月4日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
硬核实践经验 - 企鹅辅导 RN 迁移及优化总结
IMWeb前端社区
5+阅读 · 2019年5月6日
介绍高维超参数调整 - 优化ML模型的最佳实践
AI研习社
7+阅读 · 2019年4月17日
亿级订单数据的访问与存储,怎么实现与优化?
码农翻身
16+阅读 · 2019年4月17日
基于 Storm 的实时数据处理方案
开源中国
4+阅读 · 2018年3月15日
优化哈希策略
ImportNew
5+阅读 · 2018年1月17日
一篇文章读懂阿里企业级数据库最佳实践
阿里巴巴数据库技术
5+阅读 · 2017年12月20日
机器学习(26)之K-Means实战与调优详解
机器学习算法与Python学习
4+阅读 · 2017年11月19日
Top
微信扫码咨询专知VIP会员