写给Java应用开发看的Elasticsearch调优手册

3,672 阅读11分钟

本篇文章主要参考: 掘金小册-Elasticsearch 从入门到实践

随着数据量的增长,MySQL在很多场景下,似乎已经无能为力了,所以有不少应用的数据从MySQL迁移到了Elasticsearch,我们的应用就在其中。由于开发时间紧张,所以仅仅只是了解了下Elasticsearch的基本知识,随便写了几个Demo,然后直接在正式的环境中使用Elasticsearch。因为给我们应用分配的Elasticsearch服务器足够强大,Elasticsearch本身也设计的足够好,当时数据量也没有那么海量,并发也没有那么高,所以一直没有问题,但是该来的总会来,随着公司的发展,数据量也在直线增长,并发越来越高,性能问题越来越明显,看着公司内,有越来越多的应用接入了Elasticsearch,看着公司外,有越来越的公司也引入了Elasticsearch,我意识到必须深入的了解下Elasticsearch。经过大半年的断断续续学习,终于对Elasticsearch有了一定的了解,但是我毕竟不是专门做搜索引擎的,更没有强大的算法、数据结构思维,所以对Elasticsearch的了解也仅仅是停留在“用的不这么差”的程度上,所以本篇博客主要是写给Java应用开发看的,如果是专门做搜索引擎的,就一笑而过吧。

优化漏斗

凡是涉及到某个组件的性能调优,我们在脑海中就要浮现出一张很经典的优化漏斗图:

image.png

左边就是很经典的漏斗图,漏斗分为四层,从上到下分别为:

  • 使用
  • 应用配置
  • JVM
  • OS

总的来说,层级在上面的,调优的效果越明显,由于我们的Elasticsearch是独立部署在服务器上的,所以服务器的配置也至关重要。对于Elasticsearch而言,硬件配置是基础,正确的使用姿势是核心,缺一不可。

本篇博客,我们就从这五个方面来对Elasticsearch进行调优。

硬件

Elasticsearch和普通应用不同,普通应用也许4C8G的配置就足够了,但是Elasticsearch需要比较好的配置,如果配置实在太低,就放弃Elasticsearch吧。

节点配置

Elasticsearch有三个常用的节点类型:Master节点、数据节点、协调节点,我们来看下这三种节点的职责以及需要什么样的配置。

  • 数据节点:主要负责保存数据,查询数据,所以这个节点的配置要求是最高的,不管是CPU、内存、磁盘,都要高配的,磁盘最好采用RAID0、SSD,为什么是RAID0?因为Elasticsearch本身就有冗余副本的存在,可以保证不丢失数据;
  • Master节点:一个Elasticsearch集群中只会有一个Master节点,Master主要负责管理集群变更、元数据的管理,该节点不需要太好的配置;
  • 协调节点:接受客户端的请求,并将请求路由到数据节点,然后将数据节点返回的内容进行处理,比如合并,排序等等,所以此节点对CPU、内存的要求是比较高的。

为了更加清晰,我给大家准备了一个表格: | 节点类型 | CPU | 内存 | 磁盘 | | --- | --- | | --- | --- | | 数据节点 | 高配置 | 高配置 | 高配置 | | Master节点 | 低配置 | 低配置 | 低配置 | | 协调节点 | 高配置 | 高配置 | 低配置 |

我们在做数据查询的时候,协调节点会把请求路由给多个数据节点,多个数据节点再把数据响应给协调节点,协调节点再对数据进行加工处理,返回给客户端,需要做多次数据搬运,而且数据可能都不小,所以低延迟的网络也是必需的。

节点的单一职责

编码的时候,为了代码的扩展性、可维护性,我们需要满足“单一职责”,在分配节点的时候,为了Elasticsearch的性能,也需要满足“单一职责”,即:一个节点只做一件事情。

想想也是,Master节点作为集群的老大,相当于公司管理,数据节点是大头兵,如果Matser节点既要做管理的事情,又要做大头兵的事情,那会出现什么局面?

所以在条件允许的情况下,一个节点只做一件事情。

分片数(每个分片的大小控制在30-50G,3-5个分片可以满足大部分场景)

每个分片的大小控制在30-50G,Elasticsearch是严重依赖PageCache的,最好保证分片中的数据有1/3都在PageCache中。

如果你不想计算到底需要多少个分片,可以无脑配置3-5个分片,这满足大部分场景,毕竟一般的公司,你申请Elasticsearch集群,没有特殊的说明,运维小伙伴也是直接给你一个默认配置下的Elasticsearch集群,也不会特意问你数据的规模,如果你查询一下,会发现就是3-5个分片。

副本数(一个副本就够)

一个副本就够了,如果是数据迁移,为了追求迁移的速度,甚至可以把副本关闭了,等数据迁移完毕,再开启副本。

OS配置

禁用/少用swap

OS在内存不够用的时候,会将部分数据交换到swap空间(磁盘)上,但是磁盘的速度哪比得上内存的速度,所以会导致性能直线下降,所以应该关闭swap。

如果完全禁用swap,当内存不够用,OS可能会kill进程,所以为了可用性,也有一些人认为不要完全禁用swap,可以将swap设置成一个很小的数值,这样当性能直线下降,开发、运维都会有感知,可以给开发、运维处理的时间。

文件描述符设置为65535或更大

关闭atime

每次读取文件,Linux都会更新atime,没有实际用处,可以关闭。

设置最少的线程数量

Elasticsearch运行时会创建多种线程池来执行各种各样的操作,所以必须保证Elasticsearch可以创建足够的线程,而这个数量最少为4096。

减少TCP重传次数

Elasticsearch集群各个节点间的通信是使用TCP的,但是在丢包的情况下,Linux最多重传15次,而这15次重传会耗时900秒以上。这意味着系统发现网络分区故障需要非常长的时间,所以可以适当降低TCP重传次数。

JVM配置

最小堆内存和最大堆内存必须一样

如果最小堆内存、最大堆内存不一样,在运行的过程中,会频繁的进行伸缩。

最大堆内存不要超过服务器内存的一半

Elasticsearch是严重依赖PageCache的,最好保证分片中的数据有1/3都在PageCache中,所以最大堆内存不要超过服务器内存的一半,剩下的给OS使用。

最大堆内存不要超过32G(为了保险,可以设置30G)

最大堆内存一旦超过32G,JVM就会禁用指针压缩技术,这个时候,可能需要40-50G的内存才可以达到原先32G的效果,所以最大堆内存不要超过32G。32G是一个估算值,有些时候,可能不到32G就已经禁用了指针压缩技术,所以为了保险,最大堆内存可以设置30G。

垃圾收集器选用G1

对于大内存来说,G1比CMS给力,所以可以采用G1垃圾收集器,还需要调整两个参数,如果G1清理失败了,会转为单线程垃圾收集,这就太恐怖了,而G1默认的InitiatingHeapOccupancyPercent设置太乐观了,可能导致清理失败,所以需要适当降低InitiatingHeapOccupancyPercent。

-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=30

应用配置

调整刷写频率

将文档写入Elasticsearch,会先写到buffer中,然后定时将buffer中的数据刷写到segment中,默认是1秒,可以适当调高此配置,当然buffer写满了,也会刷写到segment,但是大部分场景下,都是满足了时间的条件才触写刷新的。

配置项如下:

index.refresh_interval //默认是1秒,可以适当调高此配置
indices.memory.index_buffer_size //既可以配置字节数,又可以配置比例,这个参数是节点内所有分片共享的

写translog改为异步形式

translog是用来保证数据不丢失的,类似于MySQL的binlog,默认写translog是同步的,可以改为异步的形式。

配置项如下:

index.translog.durability: async //默认是request
index.translog.sync_interval: 60s

使用

尽可能使用系统自动生成的Id

如果不使用系统自动生成的Id,Elasticsearch会检查给定的Id是否重复,从而产生IO,如果可以的话,尽可能使用系统自动生成的Id。

批量写入

经验值是一次性写入5000到10000条数据。

以前我一直觉得批量写入几乎只有在数据迁移的时候才可能实现,平时几乎都是单条写入的,其实如果业务允许,可以先将请求用MQ中转下,Consumer消费MQ数据,将数据放入阻塞队列,另外一个线程用Google Guava提供的方法批量从阻塞队列中获取数据,最终批量写入Elasticsearch。

示例代码如下:

image.png

当然这样就增加代码的复杂度了,还要处理各种的异常场景,我这里仅仅是突然想到一个方法,和大家分享下。

mapping

字段越少越好,合适的数据类型

使用Keyword类型

对于等值查询而言,keyword比数值类型性能更好,所以如果不需要范围查询,可以使用Keyword类型。 不需要分词的文本,最好使用keyword,不然既影响写入性能,又占用空间。

如果不需要被索引,设置index为no

有不需要进行搜索的字段,可以设置其不需要索引,不然既影响写入性能,又占用空间。

如果不需要聚合、排序,设置doc_values为false

Doc values是基于列式存储的结构,聚合、排序非常高效,默认开启,占用空间。

扁平化(尽可能避免nested、parent-child)

尽可能避免nested、parent-child,不然既影响写入性能,又影响查询性能。

如果一个字段不需要算分,可以关闭Norms

如果当前字段不需要计算评分,设置false可以节省大量的磁盘空间,有助于提升性能。

不要使用Dynamic Mapping

如果使用Dynamic Mapping,那默认情况下,所有字段都是需要被索引的,而且都会创建doc_values,这肯定不是我们希望的,所以不要使用Dynamic Mapping,要自己严格定义mapping。

不要对text类型的字段进行排序

预索引

商品价格区间的搜索,比如可以查看价格100-200的商品,200-500的商品,500-1000的商品,那索引中必然含有price字段,我们可以通过范围查询搜索出对应的商品,但是范围查询的性能必然没有等值查询好,那我们可以在索引中再加一个字段:price_type(当price为100-200,price_type=1,当price为200-500,price_type=2,以此类推),这样我们就可以使用等值查询了。

查询某个月的数据,那索引中必然含有time字段,我们可以通过范围查询搜索出对应的数据,如果我们加一个month字段(yyyyMM),就可以采用等值查询了。

使用索引模板,对数据进行分片

相当于MySQL的分库分表,但是更加方便,只要定义一个模板,推送的数据满足相应的条件,就可以自动将索引创建出来。

避免深度分页,使用scroll和search_after分页

HBase和Elasticsearch搭配更香

Elasticsearch严重依赖PageCache,最好不要把太多字段放入Elasticsearch,会占用大量PageCache,查询会走到硬盘,严重影响查询性能,可以把核心字段、条件字段放Elasticsearch,其余数据放到HBase,通过_id去HBase查询更为明细的数据,如果HBase不可用,再查询MySQL,提高可用性。

使用Routing

默认情况: 插入数据是根据_id进行路由,选择某个分片:

  • 优点:使用简单,每个分片的数据量比较均匀;
  • 缺点:查询会扫描所有分片,开销比较大,性能取决于最慢的分片。

使用routing: 插入数据是根据给定的routing进行路由,写入到某个分片,查询的时候,也可以指定查询某个分片。

  • 优点:性能更好;
  • 缺点:使用比较复杂,需要自己控制写入的分片和查询的分片。

以上就是常用的Elasticsearch调优技巧,当然Elasticsearch的调优技巧远不止这些,比如比较高端的_sourse、store设置等等。

大家有没有发现,其中好多调优技巧,都是通用的,比如合适的数据类型,字段越少越好,预索引,比如增加文件描述符数量、关闭atime等等。

本文正在参加「金石计划 . 瓜分6万现金大奖」