清茶豆奶 · 2020年06月24日

Elasticsearch系列---性能调优最佳实践

概要

性能调优是系统架构里所有组件必不可少的话题,Elasticsearch也不例外,虽说Elasticsearch内的默认配置已经非常优秀,但这不表示它就是完美的,必要的一些实践我们还是需要了解一下。

开启慢查询日志

慢查询日志是性能诊断的重要利器,常规操作是设置慢查询的阀值,然后运维童鞋每天对慢日志进行例行巡查,有特别慢的查询,立即报备事件处理,其余的定期将慢日志的top n取出来进行优化。

慢日志的配置在elasticsearch 6.3.1版本下是通过命令配置的,读操作和写操作可以单独设置,阀值的定义可根据实际的需求和性能指标,有人觉得5秒慢,有人觉得3秒就不可接受,我们以3秒为例:

PUT /_all/_settings
{
"index.search.slowlog.threshold.query.warn":"3s",
"index.search.slowlog.threshold.query.info":"2s",
"index.search.slowlog.threshold.query.debug":"1s",
"index.search.slowlog.threshold.query.trace":"500ms",

"index.search.slowlog.threshold.fetch.warn":"1s",
"index.search.slowlog.threshold.fetch.info":"800ms",
"index.search.slowlog.threshold.fetch.debug":"500ms",
"index.search.slowlog.threshold.fetch.trace":"200ms",

"index.indexing.slowlog.threshold.index.warn":"3s",
"index.indexing.slowlog.threshold.index.info":"2s",
"index.indexing.slowlog.threshold.index.debug":"1s",
"index.indexing.slowlog.threshold.index.trace":"500ms",
"index.indexing.slowlog.level":"info",
"index.indexing.slowlog.source":"1000"
}

这三段分别表示query查询、fetch查询和index写入三类操作的慢日志输出阀值,_all表示对所有索引生效,也可以针对具体的索引。

同时在log4j2.properties配置文件中增加如下配置:

# 查询操作慢日志输出
appender.index_search_slowlog_rolling.type = RollingFile
appender.index_search_slowlog_rolling.name = index_search_slowlog_rolling
appender.index_search_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog.log
appender.index_search_slowlog_rolling.layout.type = PatternLayout
appender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %.10000m%n
appender.index_search_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog-%d{yyyy-MM-dd}.log
appender.index_search_slowlog_rolling.policies.type = Policies
appender.index_search_slowlog_rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.index_search_slowlog_rolling.policies.time.interval = 1
appender.index_search_slowlog_rolling.policies.time.modulate = true

logger.index_search_slowlog_rolling.name = index.search.slowlog
logger.index_search_slowlog_rolling.level = trace
logger.index_search_slowlog_rolling.appenderRef.index_search_slowlog_rolling.ref = index_search_slowlog_rolling
logger.index_search_slowlog_rolling.additivity = false

# 索引操作慢日志输出
appender.index_indexing_slowlog_rolling.type = RollingFile
appender.index_indexing_slowlog_rolling.name = index_indexing_slowlog_rolling
appender.index_indexing_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog.log
appender.index_indexing_slowlog_rolling.layout.type = PatternLayout
appender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.10000m%n
appender.index_indexing_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog-%d{yyyy-MM-dd}.log
appender.index_indexing_slowlog_rolling.policies.type = Policies
appender.index_indexing_slowlog_rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.index_indexing_slowlog_rolling.policies.time.interval = 1
appender.index_indexing_slowlog_rolling.policies.time.modulate = true

logger.index_indexing_slowlog.name = index.indexing.slowlog.index
logger.index_indexing_slowlog.level = trace
logger.index_indexing_slowlog.appenderRef.index_indexing_slowlog_rolling.ref = index_indexing_slowlog_rolling
logger.index_indexing_slowlog.additivity = false

重启elasticsearch实例后,就能在/home/esuser/esdata/log目录中看到生成的两个日志文件了。

优化实践建议

基本使用规范

  1. 搜索结果不要返回过大的结果集

过大的结果集会占用大量的IO资源和带宽,速度肯定快不了,Elasticsearch是一个搜索引擎,最理想的搜索是精准查询或次精准查询,最关心的是排在前面的少数结果,而不是所有结果,优化搜索条件,控制搜索结果数量是高性能的前提。

如果真有大批量的数据查询,建议使用scroll api。

  1. 避免超大的document

http.max_context_length的默认值是100mb,如果你一次document写入时,document的内容不能超过100mb,否则es就会拒绝写入。虽然你可以修改此配置,但不建议这么做,es底层的lucene引擎还是有一个2gb的最大限制。

过大的document会占用非常多的资源,从任何方面考虑都不建议,如果业务需求真有非常大的内容,如对书的内容搜索,建议按章节、按段落进行拆分存储。

  1. 避免稀疏的数据

document的设计会从根本上影响索引的性能,稀疏数据是一个典型的不良设计,浪费存储空间,影响读写性能。

下面有一些document结构设计的建议:

  • 避免将没有任何关联性的数据写入同一个索引

没有关联性的数据,意味着数据结构也不相同,硬生生放在同一个索引,会导致index数据非常稀疏,建议是将这些数据放在不同的索引中。

  • 对document的结构进行统一规范化

document的结构、命名尽可能统一规范处理,同样是创建时间字段,避免有的叫timestamp,有的叫create_time,尽可能统一。

  • 对某些field禁用norms和doc_values

如果一个field不需要考虑其相关度分数,那么可以禁用norms,如果不需要对一个field进行排序或者聚合,那么可以禁用doc_values字段。

服务器层级

硬件资源是性能最硬核的部分,硬件好,起点就高。

  1. 用更快的硬件资源

在预算范围内,能用SSD固态硬盘就不要选用机械硬盘;

CPU主频、核数当然是强大到预算上限;

内存单机上限64GB,加机器加到没钱为止;

尽量使用本地存储系统,不要用NFS等网络存储,毕竟硬盘便宜。

  1. 给filesystem cache更多的内存

Elasticsearch的搜索严重依赖于底层的filesystem cache,如果所有的数据都能够存放在filesystem cache中,那么搜索基本上是秒级。

由于实际情况的限制,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半。

要达到最佳情况有两个办法:一个是砸钱,买更多机器,加更大内存;另一种是精简document数据,只把需要搜索的field放进es内,filesystem cache就能存下更多的document,可以提高内存的利用率。剩余的其他字段,可以放在redis/mysql/hbase/hapdoop做二级加载。

  1. 禁止swapping交换内存

将swapping禁止掉,如果es jvm内存交换到磁盘,再交换回内存,会造成大量磁盘IO,性能很差。

Elasticsearch层级

  1. index buffer

在高并发写入场景,我们可以将index buffer调大一些,indices.memory.index_buffer_size,这个可以调节大一些,这个值默认是jvm heap的10%,这个index buffer大小,是所有的shard公用的,这个值除以shard数量,算出来平均每个shard可以使用的内存大小,一般建议对于每个shard最多给512mb。

  1. 禁止_all field

_all field会将document中所有field的值都合并在一起进行索引,很占用磁盘空空间,实际上用处却不大,生产环境最好禁用_all field。

  1. 使用best_compression

_source field和其他field很占用磁盘空间,建议对其使用best_compression进行压缩。

  1. 用最小的最合适的数字类型

es支持4种数字类型:byte,short,integer,long。如果最小的类型就合适,那么就用最小的类型,节省磁盘空间。

  1. 禁用不需要的功能

对于需要进行聚合和排序的field,我们才建立正排索引;
对于需要进行检索的field,我们才建立倒排索引;
对于不关心doc分数的field,我们可以禁用掉norm;
对于不需要执行phrase query近似匹配的field,那么可以禁用位置这个属性;

  1. 不要用默认的动态string类型映射

默认的动态string类型映射会将string类型的field同时映射为text类型以及keyword类型,大多数情况我们只需要使用其中一种,剩下的都是浪费磁盘空间,例如,id field这种字段可能只需要keyword,而body field可能只需要text field。

所以是使用keyword和text在设计时就应该区分清楚,而不是全盘保存。

  1. 预热filesystem cache

如果我们重启了Elasticsearch,那么filesystem cache是空的,每次数据查询时再加载数据进filesystem cache,我们可以先对一些数据进行查询,提前将一些常用数据加载到内存,待真实客户使用时,可以直接使用内存数据,响应就很快了。

代码研发层级

  1. 多使用bulk做写入

我们使用Java作为客户端时,写入操作全部利用bulk api来完成。

  1. 使用多线程将数据写入
  2. document使用自动生成的id

手动给document设置一个id,那么es需要每次都去确认一下那个id是否存在,这个过程是比较耗费时间的。如果我们使用自动生成的id,那么es就可以跳过这个步骤,写入性能会更好。

对于关系型数据库中的表id,可以作为es document的一个field存入。

  1. 重视document结构设计

业务研发的重中之重,好的document结构会带来非常优秀的性能表现。

  1. 避免使用script脚本
  2. 充分利用缓存

时间查询时,不要使用now这种函数,应该在客户端把时间转换成规范的格式,再到Elasticsearch里查询,这样能提高缓存的使用率。

小结

本篇介绍了Elasticsearch性能调优的常见实践方法,从服务器、实例再到代码层级,可以作为参考,但性能调优没有约定俗成的方法,需要反复的验证,仅供参考,谢谢

专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术
Java架构社区

推荐阅读
关注数
1
内容数
14
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息