社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Elasticsearch

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

51CTO技术栈 • 5 年前 • 423 次点击  

“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工作制:平衡不好可以离婚!


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/AU1D2ypVMc
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/28440
 
423 次点击