亿级 Elasticsearch 性能优化

亿级 Elasticsearch 性能优化

前言

最近一年使用 Elasticsearch 完成亿级别日志搜索平台「ELK」,亿级别的分布式跟踪系统。在设计这些系统的过程当中,底层都是采用 Elasticsearch 来作数据的存储,而且数据量都超过亿级别,甚至达到百亿级别。html

因此趁着有空,就花点时间整理一下具体怎么作 Elasticsearch 性能优化,但愿能对 Elasticsearch 感兴趣的同窗有所帮助。web

背景

Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful web 接口。Elasticsearch 是用 Java 开发的,并做为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,可以达到实时搜索,稳定,可靠,快速,安装使用方便。缓存

做为一个开箱即用的产品,在生产环境上线以后,咱们其实不必定能确保其的性能和稳定性。如何根据实际状况提升服务的性能,其实有不少技巧。性能优化

下面我就从三个方面分别来说解下优化服务的性能:服务器

  1. 索引效率优化
  2. 查询效率优化
  3. JVM 配置优化

索引效率优化

索引优化主要是在 Elasticsearch 插入层面优化,若是瓶颈不在这块,而是在产生数据部分,好比 DB 或者 Hadoop 上,那么优化方向就须要改变下。同时,Elasticsearch 自己索引速度其实仍是蛮快的,具体数据,咱们能够参考官方的 benchmark 数据。数据结构

批量提交

当有大量数据提交的时候,建议采用批量提交。架构

好比在作 ELK 过程当中 ,Logstash indexer 提交数据到 Elasticsearch 中 ,batch size 就能够做为一个优化功能点。可是优化 size 大小须要根据文档大小和服务器性能而定。并发

像 Logstash 中提交文档大小超过 20MB ,Logstash 会请一个批量请求切分为多个批量请求。elasticsearch

若是在提交过程当中,遇到 EsRejectedExecutionException 异常的话,则说明集群的索引性能已经达到极限了。这种状况,要么提升服务器集群的资源,要么根据业务规则,减小数据收集速度,好比只收集 Warn、Error 级别以上的日志。分布式

优化硬件

优化硬件设备一直是最快速有效的手段。

  1. 在经济压力能承受的范围下, 尽可能使用固态硬盘 SSD。SSD 相对于机器硬盘,不管随机写仍是顺序写,都较大的提高。
  2. 磁盘备份采用 RAID0。由于 Elasticsearch 在自身层面经过副本,已经提供了备份的功能,因此不须要利用磁盘的备份功能,同时若是使用磁盘备份功能的话,对写入速度有较大的影响。

增长 Refresh 时间间隔

为了提升索引性能,Elasticsearch 在写入数据时候,采用延迟写入的策略,即数据先写到内存中,当超过默认 1 秒 (index.refresh_interval)会进行一次写入操做,就是将内存中 segment 数据刷新到操做系统中,此时咱们才能将数据搜索出来,因此这就是为何 Elasticsearch 提供的是近实时搜索功能,而不是实时搜索功能。

固然像咱们的内部系统对数据延迟要求不高的话,咱们能够经过延长 refresh 时间间隔,能够有效的减小 segment 合并压力,提供索引速度。在作全链路跟踪的过程当中,咱们就将 index.refresh_interval 设置为 30s,减小 refresh 次数。

同时,在进行全量索引时,能够将 refresh 次数临时关闭,即 index.refresh_interval 设置为 -1,数据导入成功后再打开到正常模式,好比 30s。

减小副本数量

Elasticsearch 默认副本数量为 3 个,虽然这样会提升集群的可用性,增长搜索的并发数,可是同时也会影响写入索引的效率。

在索引过程当中,须要把更新的文档发到副本节点上,等副本节点生效后在进行返回结束。使用 Elasticsearch 作业务搜索的时候,建议副本数目仍是设置为 3 个,可是像内部 ELK 日志系统、分布式跟踪系统中,彻底能够将副本数目设置为 1 个。

查询效率优化

路由

当咱们查询文档的时候,Elasticsearch 如何知道一个文档应该存放到哪一个分片中呢?它实际上是经过下面这个公式来计算出来

shard = hash(routing) % number_of_primary_shards

routing 默认值是文档的 id,也能够采用自定义值,好比用户 id。

不带 routing 查询

在查询的时候由于不知道要查询的数据具体在哪一个分片上,因此整个过程分为 2 个步骤

  • 分发:请求到达协调节点后,协调节点将查询请求分发到每一个分片上。
  • 聚合: 协调节点搜集到每一个分片上查询结果,在将查询的结果进行排序,以后给用户返回结果。

带 routing 查询

查询的时候,能够直接根据 routing 信息定位到某个分配查询,不须要查询全部的分配,通过协调节点排序。

向上面自定义的用户查询,若是 routing 设置为 userid 的话,就能够直接查询出数据来,效率提高不少。

Filter VS Query

Ebay 曾经分享过他们使用 Elasticsearch 的经验中说到:

Use filter context instead of query context if possible.
尽量使用过滤器上下文(Filter)替代查询上下文(Query

  • Query:此文档与此查询子句的匹配程度如何?
  • Filter:此文档和查询子句匹配吗?

Elasticsearch 针对 Filter 查询只须要回答「是」或者「否」,不须要像 Query 查询一下计算相关性分数,同时 Filter 结果能够缓存。

大翻页

在使用 Elasticsearch 过程当中,应尽可能避免大翻页的出现。

正常翻页查询都是从 From 开始 Size 条数据,这样就须要在每一个分片中查询打分排名在前面的 From + Size 条数据。协同节点收集每一个分配的前 From + Size 条数据。协同节点一共会受到 N * ( From + Size )条数据,而后进行排序,再将其中 From 到 From + Size 条数据返回出去。

若是 From 或者 Size 很大的话,致使参加排序的数量会同步扩大不少,最终会致使 CPU 资源消耗增大。

能够经过使用 Elasticsearch scroll 和 scroll-scan 高效滚动的方式来解决这样的问题。具体写法,能够参考 Elasticsearch: 权威指南 - scroll 查询

JVM 设置

32G 现象

Elasticsearch 默认安装后设置的堆内存是 1 GB。 对于任何一个业务部署来讲, 这个设置都过小了。

好比机器有 64G 内存,那么咱们是否是设置的越大越好呢?

其实不是的。

主要 Elasticsearch 底层使用 Lucene。Lucene 被设计为能够利用操做系统底层机制来缓存内存数据结构。 Lucene 的段是分别存储到单个文件中的。由于段是不可变的,这些文件也都不会变化,这是对缓存友好的,同时操做系统也会把这些段文件缓存起来,以便更快的访问。

若是你把全部的内存都分配给 Elasticsearch 的堆内存,那将不会有剩余的内存交给 Lucene。 这将严重地影响全文检索的性能。

标准的建议是把 50% 的可用内存做为 Elasticsearch 的堆内存,保留剩下的 50%。固然它也不会被浪费,Lucene 会很乐意利用起余下的内存。

同时了解过 ES 的同窗都听过过「不要超过 32G」的说法吧。

其实主要缘由是 :JVM 在内存小于 32 GB 的时候会采用一个内存对象指针压缩技术。

在 Java 中,全部的对象都分配在堆上,并经过一个指针进行引用。 普通对象指针(OOP)指向这些对象,一般为 CPU 字长 的大小:32 位或 64 位,取决于你的处理器。指针引用的就是这个 OOP 值的字节位置。

对于 32 位的系统,意味着堆内存大小最大为 4 GB。对于 64 位的系统, 可使用更大的内存,可是 64 位的指针意味着更大的浪费,由于你的指针自己大了。更糟糕的是, 更大的指针在主内存和各级缓存(例如 LLC,L1 等)之间移动数据的时候,会占用更多的带宽.

因此最终咱们都会采用 31 G 设置

-Xms 31g
-Xmx 31g

假设你有个机器有 128 GB 的内存,你能够建立两个节点,每一个节点内存分配不超过 32 GB。 也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene

参考

题外话

我在51CTO博客开了一个专栏——《带你玩转高可用》但愿能够帮助有必定分布式系统架构知识,在架构方面力求进阶的分布式系统架构从业人员,提升架构可用性,实现高可用目标。

《带你玩转高可用带你玩转高可用》

亿级 Elasticsearch 性能优化

相关文章
相关标签/搜索