文章目录
- 引言
- 硬件设置优化
- 禁用swap
- 给系统留足够的内存
- JVM配置
- 使用更快的硬件,使用 SSD
- 参数优化
- 分片设计
- 增加Buffer大小(增加吞吐量)
- 预热文件系统 cache
- es查询设计优化
- 预索引数据
- 使用filter代替query
- 字段映射
- 避免使用脚本
- 优化日期搜索
- 为只读索引执行 force-merge
- 预热全局序号(global ordinals)
- 深度优先还是广度优先
- 限制搜索请求的分片数
- 利用自适应副本选择(ARS)提升 ES响应速度
- 调节搜索请求中的 batched\_reduce\_size
- 慢日志设置、跟踪
- 慢日志设置
- 查询分析工具
- IO分析
- 安装 `iotop`
- 基本用法
- 常用选项
- 示例
- 注意事项
- 生产必要关注配置
- 数据、日志路径设置(重要)
- 集群名称
- 节点名称
- 主机网络
- 发现设置
- 堆大小
- 堆储存路径
- GC 日志
- 临时文件
- dokcer容器资源控制
- 推荐Java开发快速上手es集成框架
- 遗留问题
引言
从多个维度:硬件、软件参数、查询等,收集的官方以及互联网上的别人实战的博客进行汇总。在实际中官方其实已经给了很多优化建议,很多人可能没去读过,建议过一遍官方文档。
硬件设置优化
禁用swap
大多数操作系统尝试将尽可能多的内存用于文件系统缓存,并急切地换掉未使用的应用程序内存。这可能导致部分 JVM 堆甚至其可执行页面被换出到磁盘。
查看当前的交换使用情况:
sudo swapon --show
关闭所有交换空间:
sudo swapoff -a
永久禁用交换:
编辑/etc/fstab文件,注释掉所有与swap相关的行。
创建一个新文件/etc/sysctl.d/99-elasticsearch.conf
,并添加以下内容:
vm.swappiness = 1
应用更改:
sudo sysctl --system
给系统留足够的内存
Lucene的数据的fsync是发生在OS cache的,要给OS cache预留足够的内存大小。
搜索操作很依赖对系统cache的命中,标准的建议是把50%的可用内存作为ES的堆内存,为Lucene 保留剩下的50%,用作系统 cache。
export ES_HEAP_SIZE=10g或者启动命令行参数设置./bin/elasticsearch -Xmx10g -Xms10g
参考官方文档:堆内存:大小和交换
JVM配置
ES 不建议为 JVM 配置超过 32GB 的内存,超过 32GB 时,Java内存指针压缩失效,浪费一些内存,降低了CPU性能,GC压力也较大。因此推荐设置为31GB:
-Xmx31g -Xms3lg
参考博客:ElasticSearch CPU和内存占用高的优化记录
使用更快的硬件,使用 SSD
写入性能对 CPU的性能更敏感,而搜索性能在一般情况下更多的是在于IO能力,使用 SSD会比旋转类存储介质好得多。尽量避免使用NFS等远程文件系统,如果NFS比本地存储慢3倍,则在搜索场景下响应速度可能会慢10倍左右。这可能是因为搜索请求有更多的随机访问。如果搜索类型属于计算比较多,则可以考虑使用更快的CPU。
参数优化
一次看完28个关于ES的性能调优技巧,很赞,值得收藏
分片设计
初始依据:为避免庞大的分片问题,一个节点可以容纳的分片数跟可用空间成正比。通常情况下,每 1GB 的分片数最好少于 20 个。设置的分片数量越少,效果就越好。
对于高效搜索(热数据),建议“内存-磁盘”比率在1:30为佳,也就是60GB内存可以支撑的检索数据量约1.8TB。对于温数据或冷数据则不受此限制,甚至可以拓展到1:180。
1. 将单个分片存储存储索引数据的大小控制在20G左右;绝对不要超过50G , 否则性能很差 最终分片数量 = 数据总量/20G
number_of_shards
每个索引的主分片数,默认值是 5 。这个配置在索引创建后不能修改
number_of_replicas
每个主分片的副本数,默认值是 1 。对于活动的索引库,这个配置可以随时修改
{"settings":{ "number_of_shards":3, "number_of_replicas":1 },"mappings":{...}
}
使用前需要确定大概的数据量设置分片数量,过多分片也会导致查询缓慢,需要迁移参考下面文章
关于ES分片设置的6个建议
Elasticsearch 使用误区之三——分片设置不合理
增加Buffer大小(增加吞吐量)
本质也是减小refresh的时间间隔,因为导致segment文件创建的原因不仅有时间阈值,还有buffer空间大小,写满了也会创建。 默认最小值 48MB< 默认值 JVM 空间的10% < 默认最大无限制
indices.memory.index_buffer_size: 20% # 默认是10%
index_buffer_size的大小直接影响到何时触发一个新的segment文件的创建。当buffer被填满时,会触发refresh操作,产生新的Lucene segment。
增加buffer大小可以减少因buffer满而触发的refresh操作,从而减少segment的创建和后续的merge操作。
些用户认为这个buffer越大吞吐量越高,因此见过有用户将其设置为40%的。到了极端的情况,写入速度很高的时候,40%都被占用,导致OOM。
Elasticesearch内存详解(四)——indexing buffer
预热文件系统 cache
如果ES主机重启,则文件系统缓存将为空,此时搜索会比较慢。可以使用index.store.preload设置,通过指定文件扩展名,显式地告诉操作系统应该将哪些文件加载到内存中,
例如,配置到elasticsearch.yml文件中:
index.store.preload:["nvd","dvd"]
或者在索引创建时设置:
PUT /my index "settings":{ "index.store.preload":["nvd","dvd"] } }
如果文件系统缓存不够大,则无法保存所有数据,那么为太多文件预加载数据到文件系统缓存中会使搜索速度变慢,应谨慎使用。
如果文件系统缓存不够大,无法保存所有数据,那么为太多文件预加载数据到文件系统缓存中可能会适得其反,导致搜索速度变慢。这是因为:
缓存置换策略:当文件系统缓存满了之后,操作系统会使用某种缓存置换策略(如最近最少使用(LRU)算法)来决定哪些数据应该被置换出去。如果预加载了太多文件,那么这些文件可能会很快被置换出缓存,导致实际需要的数据不在缓存中,从而增加了磁盘I/O,降低了搜索性能。
内存资源竞争:文件系统缓存是操作系统管理的内存资源,如果预加载了太多文件,可能会占用过多的内存资源,影响系统的其他部分,如ES的堆内存和JVM性能,从而影响整体性能。
磁盘I/O增加:如果预加载的文件太多,那么在文件系统缓存中置换出去的文件可能会频繁地被重新加载,这会增加磁盘I/O,降低性能。
es查询设计优化
预索引数据
还可以针对某些查询的模式来优化数据的索引方式。
例如,如果所有文档都有一个price字段,并且大多数査询在一个固定的范围上运行range聚合,那么可以通过将范围“pre-indexing到索引中并使用terms聚合来加快聚合速度。(新增字段做索引)
查询范围为1-10,新建一个字段为1-10直接全匹配即可
使用filter代替query
query和filter的主要区别在: filter是结果导向的而query是过程导向。query倾向于“当前文档和查询的语句的相关度”而filter倾向于“当前文档和查询的条件是不是相符”。即在查询过程中,query是要对查询的每个结果计算相关性得分的,而filter不会。另外filter有相应的缓存机制,可以提高查询效率。
字段映射
-
显式设置映射(Explicit Mapping):
- 显式设置映射可以帮助确保字段类型准确,优化存储和性能,并避免不必要的映射更新。例如,对于不需要分词的字段,应使用
keyword
类型,而对于需要分词的字段,则使用text
类型。这样可以确保查询和聚合操作的正确性,并降低存储空间,提高查询性能。
- 显式设置映射可以帮助确保字段类型准确,优化存储和性能,并避免不必要的映射更新。例如,对于不需要分词的字段,应使用
-
使用合适的字段类型:
- 选择合适的字段类型对于优化搜索性能和准确性至关重要。例如,对于文本字段,应使用
text
或keyword
类型;对于数字类型的数据,可以选择integer
、float
或scaled_float
类型;对于日期和时间,使用date
类型;对于唯一标识符,使用keyword
类型。
- 选择合适的字段类型对于优化搜索性能和准确性至关重要。例如,对于文本字段,应使用
-
null_value参数:
- 使用
null_value
参数为字段指定一个默认值,当字段在文档中为null
或缺失时,Elasticsearch将使用这个默认值。这对于在索引时处理空值非常有用,可以确保查询和聚合的一致性。
- 使用
-
避免使用嵌套(Nested)类型:
- 嵌套类型虽然在某些场景下很有用,但它们也带来了一定的性能影响。查询嵌套字段速度较慢,并且需要额外的处理步骤。为了避免嵌套类型带来的性能影响,可以考虑将嵌套字段转换为扁平化的数据结构。
-
优化存储和性能:
- 通过显式设置映射,可以降低存储空间,提高查询性能。例如,对于不需要
doc_values
的字段,可以禁用doc_values
以减少磁盘使用,但要注意这可能会影响性能。
- 通过显式设置映射,可以降低存储空间,提高查询性能。例如,对于不需要
-
自定义分析器:
- 在创建索引时,可以设置自定义分析器,用于索引时处理文本。这样可以更精确地控制文本的分词和搜索行为,提高搜索的相关性和性能。
-
字段数据(Fielddata):
- 对于需要排序和聚合的字段,启用
fielddata
。但要注意,这可能会消耗大量内存,因此需要根据实际情况权衡内存使用和性能需求。
- 对于需要排序和聚合的字段,启用
-
动态映射(Dynamic Mapping):
- 动态映射允许Elasticsearch自动实现每个字段的类型检测,并进行映射设置。但为了更好的性能和控制,建议关闭动态映射,显式定义字段类型和属性。
避免使用脚本
一般来说,应该避免使用脚本。如果一定要用,则应该优先考虑painless和expressions。
优化日期搜索
在使用日期范围检索时,使用now的查询通常不能缓存,因为匹配到的范围一直在变化。但是,从用户体验的角度来看,切换到一个完整的日期通常是可以接受的,这样可以更好地利用查询缓存。(将时间范围精确到分钟或者更多(过大影响使用,根据业务调整))
举例:
GET index/ search
"query":{"constant score": {"filter": {"range": {"my_date": {"gte": "now-lh","lte": "now"}}}}
}
修改为:
GET index/search"query":{"constant score": {"filter": {"range": {"my_date": {"gte": "now-lh/m","lte": "now/m"}}}}
}
为只读索引执行 force-merge
为不再更新的只读索引执行force merge,将Lucene 索引合并为单个分段,可以提升查询速度。当一个Lucene索引存在多个分段时,每个分段会单独执行搜索再将结果合并,将只读索引强制合并为一个 Lucene分段不仅可以优化搜索过程,对索引恢复速度也有好处。
基于日期进行轮询的索引的旧数据一般都不会再更新。此前的章节中说过,应该避免持续地写一个固定的索引,直到它巨大无比,而应该按一定的策略,例如,每天生成一个新的索引,然后用别名关联,或者使用索引通配符。这样,可以每天选一个时间点对昨天的索引执行force-merge、Shrink等操作。
预热全局序号(global ordinals)
全局序号(Global Ordinals)是Elasticsearch中用于提高聚合查询性能的一种数据结构。它主要用于在keyword
字段上运行terms
聚合。以下是对全局序号的详细解释:
1. **全局序号的基本概念**:- 全局序号通过为字段中的每个唯一字符串值分配一个数值(ordinal)来工作,这个数值是基于字符串的字典顺序分配的。- 在聚合操作中,Elasticsearch使用这些ordinals来收集文档到bucket中,而不是直接使用原始的字符串值。2. **构建过程**:- 每个索引段(segment)定义了自己的ordinal映射,但是聚合操作需要跨整个分片(shard)收集数据。为了在分片级别使用ordinals进行聚合操作,Elasticsearch创建了一个统一的映射,称为全局序号(global ordinals)。- 全局序号是基于段(segment)序号构建的,通过维护一个从全局序号到每个段的局部序号的映射来工作。3. **加载全局序号**:- 默认情况下,全局序号映射在搜索时首次需要时加载。如果优化索引速度是主要考虑因素,这是一种合适的方法。但如果搜索性能是优先考虑的,建议在聚合字段上预先加载全局序号。- 可以通过配置映射来告诉Elasticsearch在刷新(refresh)时预先加载全局序号。这可以通过设置`eager_global_ordinals`为`true`来实现。- 当启用`eager_global_ordinals`时,全局序号会在分片刷新时构建,Elasticsearch总是在暴露索引内容变化之前加载它们。这将构建全局序号的成本从搜索阶段转移到索引时间。4. **对性能的影响**:- 启用`eager_global_ordinals`可以提高搜索性能,因为它减少了在搜索时构建全局序号的需要。然而,这也意味着每次执行刷新操作时都会构建全局序号,这可能会影响写入性能。- 为了减少由于频繁刷新而建立全局序号导致的额外开销,可以增加索引的刷新间隔(`refresh_interval`)。5. **配置示例**:- 以下是如何在创建索引时启用`eager_global_ordinals`的示例:```jsonPUT my-index-000001/_mapping{"properties": {"tags": {"type": "keyword","eager_global_ordinals": true}}}```- 如果需要禁用`eager_global_ordinals`,可以更新设置:```jsonPUT my-index-000001/_mapping{"properties": {"tags": {"type": "keyword","eager_global_ordinals": false}}}```- 调整刷新频率的示例:```jsonPUT my-index-000001/_settings{"index": {"refresh_interval": "30s"}}```- 这将构建全局序号的成本从搜索阶段转移到了数据索引化(写入)阶段,是一种以空间换时间的策略。全局序号是一种优化聚合查询性能的数据结构,通过预先加载全局序号,可以显著提高搜索速度,尤其是在高基数字段上运行`terms`聚合时。然而,这也可能会影响写入性能,因此需要根据具体的使用场景来权衡是否启用`eager_global_ordinals`。
深度优先还是广度优先
ES有两种不同的聚合方式:深度优先和广度优先。深度优先是默认设置,先构建完整的树,然后修剪无用节点。大多数情况下深度聚合都能正常工作,但是有些特殊的场景更适合广度优先,先执行第一层聚合,再继续下一层聚合之前会先做修剪。
根据不同情况优化
官方有一个例子可以参考
限制搜索请求的分片数
一个搜索请求涉及的分片数量越多,协调节点的CPU和内存压力就越大。默认情况下,ES会拒绝超过1000个分片的搜索请求。
我们应该更好地组织数据,让搜索请求的分片数更少。如果想调节这个值,则可以通过 action.search.shard count配置项进行修改。
虽然限制搜索的分片数并不能直接提升单个搜索请求的速度,但协调节点的压力会间接影响搜索速度,例如,占用更多内存会产生更多的GC压力,可能导致更多的stop-the-world 时间等,因此间接影响了协调节点的性能,所以我们仍把它列作本章的一部分。
对整个集群调整max_concurrent_shard_requests参数
PUT /_cluster/settings
{"persistent" : {"max_concurrent_shard_requests : "24"}
}
参考博客:在单个节点的查询并发限制是多少?增加分片一定会提高聚合/查询速度吗
利用自适应副本选择(ARS)提升 ES响应速度
为了充分利用计算资源和负载均衡,协调节点将搜索请求轮询转发到分片的每个副本,轮询策略是负载均衡过程中最简单的策略,任何一个负载均衡器都具备这种基础的策略,缺点是不考虑后端实际系统压力和健康水平。
ARS从6.1版本开始支持,但是默认关闭,可以通过下面的命令动态开启,从ES7.0开始,ARS将默认开启。
PUT /cluster/settings{
"transient":{"cluster.routing.use adaptive replica selection": true}
}
调节搜索请求中的 batched_reduce_size
该字段是搜索请求中的一个参数。默认情况下,聚合操作在协调节点需要等所有的分片都取回结果后才执行,使用batchedreduce size 参数可以不等待全部分片返回结果,而是在指定数量的分片返回结果之后就可以先处理一部分(reduce)。这样可以避免协调节点在等待全部结果的过程中占用大量内存,避免极端情况下可能导致的OOM。该字段的默认值为512,从ES 5.4 开始支持。
{"size": 0,"aggs":{"test_aggs":{"terms":{"target_col": "target_file"},"meta":{"batched_reduce_size": 10}}}}
慢日志设置、跟踪
慢日志设置
查询日志需要单独设置
参考文章:Elasticsearch 日志能否把全部请求打印出来?
日志记录
设置打印日志查询:
- 设置日志等级
-- 这里只设置了搜索日志等级,其余参考文档put http://xxxx:9200/_cluster/settings{"transient" : {"logger.index.search.slowlog" : "DEBUG", }
}
- 设置slowlog的debug等级的阈值
-- 这里只设置了搜索日志等级,其余参考文档put http://xxxxx:9200/索引名称/_settings{"index.search.slowlog.threshold.query.debug": "0s"
}
索引慢日志和查询慢日志,根据不同情况设置
Elasticsearch:Elasticsearch 中的慢日志
查询分析工具
深入解密 Elasticsearch 查询优化:巧用 Profile 工具/API 提升性能
IO分析
查看磁盘情况:
iostat -xd 1
- iops,由r/s(每秒读次数)和w/s(每秒写次数)组成。
- await,平均 IO 等待时间,包括硬件处理 I0的时间和在队列中的等待时间。
- %util,设备的繁忙比,是设备执行的I0时间与所经过的时间百分比。当值接近100%时设备产生饱和。在设备具有并行处理能力的情况下,utl达到100%不代表设备没有余力处理更多 I/O 请求。
iotop
是一个用于监视 Linux 系统中磁盘 I/O 使用情况的命令行工具,类似于 top
,但专注于显示实时的磁盘读写活动。以下是 iotop
的基本使用方法和一些常用选项:
安装 iotop
在大多数 Linux 发行版中,iotop
可能不是预装的。可以使用包管理器来安装它:
- 对于基于 Debian 的系统(如 Ubuntu):
sudo apt-get install iotop
- 对于基于 RPM 的系统(如 Fedora):
sudo dnf install iotop
- 对于 CentOS/RHEL:
sudo yum install iotop
基本用法
要使用 iotop
监视每个正在运行的进程的磁盘使用情况,可以直接在命令行中输入:
sudo iotop
这将打开一个交互窗口,显示当前所有进程的磁盘 I/O 使用情况。
常用选项
-o
或--only
:只显示正在执行 I/O 操作的进程。-b
:以非交互模式运行,适合记录日志。-n NUM
:设置刷新次数。-d SEC
:设置刷新间隔(秒)。-p PID
:只监视指定进程的 I/O。-u USER
:只显示指定用户的进程的 I/O。-a
:显示累积 I/O,而不是实时速率。-k
:以千字节(KB)为单位显示数据大小。-t
:显示时间戳。
示例
- 仅显示正在进行 I/O 操作的进程:
sudo iotop -o
- 以非交互模式运行并刷新 5 次,每次间隔 5 秒:
sudo iotop -b -n 5 -d 5
- 监视特定用户的 I/O:
sudo iotop -u username
注意事项
iotop
通常需要以 root 权限运行,以便查看所有进程的 I/O 活动。- 确保系统已加载必要的内核模块,如
CONFIG_TASK_DELAY_ACCT
和CONFIG_TASK_IO_ACCOUNTING
,否则iotop
可能无法正常工作。 - 频繁刷新可能会对系统性能产生影响,特别是在繁忙的系统上。
iotop
是一个非常有用的工具,可以帮助系统管理员快速识别导致高磁盘 I/O 的进程,从而进行相应的优化和调整。
生产必要关注配置
官方建议关注
数据、日志路径设置(重要)
data 和 logs 目录在都在 $ES_HOME 下。如果这些比较重要的文件夹保留在默认位置,则 Elasticsearch 升级到新版本时,很有可能被删除。
在生产环境中,你可以通过下面这种方式修改数据和日志文件所在的位置:
path:logs: /var/log/elasticsearchdata: /var/data/elasticsearch
RPM 和 Debian 发行版已经为 data 和 logs 使用了自定义路径。
path.data 可以设置为多个路径,在这种情况下,所有路径将用于存储数据 (属于单个分片的文件将全部存储在同一数据路径上):
path:data:- /mnt/elasticsearch_1 - /mnt/elasticsearch_2 - /mnt/elasticsearch_3
集群名称
当节点与集群中的所有其他节点共享 cluster.name 时,该节点就能加入集群。
默认名称是elasticsearch
,但是你应该将其更改为描述集群用途的适当名称。
cluster.name: logging-prod
确保不要在不同的环境中重复使用相同的集群名称,否则最终可能会导致节点加入错误的集群
节点名称
Elasticsearch 使用node.name
作为 Elasticsearch 特定实例的可读标识符,因此它被包含在许多 API 的响应中。
它默认机器在 Elasticsearch 启动时的主机名,但可以在 elasticsearch.yml 中更改配置,如下所示:
node.name: prod-data-2
主机网络
发现设置
堆大小
重要,直接影响性能
设置位置参考
堆储存路径
Elasticsearch 配置 JVM 的默认配置,将内存不足异常时生成的 heap dump 文件保存到 data 目录 ( 当以 RPM 和 Debian 的方式安装时,在/var/lib/elasticsearch
目录下。 当以 tar 和 zip 压缩包的方式安装时,data 目录在 Elasticsearch 的根目录下)。
修改存储路径 heap dump 文件,你应该修改 jvm.options
的配置项 -XX:HeapDumpPath=...
GC 日志
默认情况下,Elasticsearch 启用 GC 日志。这些是在jvm.options
中配置的,默认为与 Elasticsearch 日志相同的默认位置。
默认配置每 64 MB 轮换一次日志,最多可消耗 2 GB 磁盘空间。
临时文件
dokcer容器资源控制
目前是用docker管理es,所以记录一下docker的资源管理
Docker容器:docker的资源控制及docker数据管理
推荐Java开发快速上手es集成框架
推荐理由:新手友好,对于对es语法不熟悉的人来说非常简单,会mybatis plus就能看懂这套使用。
从功能上讲,对目前所有的es结构也有很好的支持,且支持原生的RestHighLevelClient查询(底层也是使用的RestHighLevelClient)
easy-es
遗留问题
因为现有业务是一个聚合数据的业务(大数据平台),从多个业务中需要筛选出有用的数据然后进行导出。
索引设计,目前是用的复杂的类型,全部表直接丢到索引的结构,非常暴力。
业务的复杂性,各个业务关联设计想要做出整合搜索,如何去设计索引目前我没看到比较好的方案或者思路(目前看到的基本都是针对单一业务),希望有朋友有相关经验可以评论一下