内存设置
1.不要超过物理内存的50%
Elasticsearch和Lucene依赖于操作系统的文件系统缓存来加速数据读取。因此,留出足够的内存用于操作系统的文件系统缓存是非常重要的。
2.堆内存大小不要超过32GB
JVM的指针压缩机制在堆内存不超过32GB时有效,可以节省内存和提高性能。
3.Xms和Xmx设置相同
设置初始堆内存(Xms)和最大堆内存(Xmx)为相同的值,确保堆内存大小固定,这样可以减少垃圾回收时的开销。
假设有一台128GB内存的机器,建议将其配置为两个Elasticsearch节点,每个节点的堆内存设置为31GB。具体配置步骤如下:
1.确保操作系统有足够的内存用于文件系统缓存。
对128GB的机器,将大约64GB分配给Elasticsearch堆内存,剩下的内存给操作系统。
2.在机器上安装两个独立的Elasticsearch实例,确保它们的配置文件、数据目录、日志目录和启动脚本互相独立。
3.配置每个实例的jvm.options
文件
shell
-Xms31g
-Xmx31g
磁盘选择
Elasticsearch 的基础是 Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在 ES 的配置文件.../config/elasticsearch.yml 中配置。
properties
path.data: /path/to/data
path.logs: /path/to/logs
Elasticsearch 重度使用磁盘,磁盘能处理的吞吐量越大,ES节点就越稳定,推荐使用多块SSD 硬盘,条带化存储多个目录中,以提高磁盘 I/O 能力。例如,两块SSD分别挂载在/data1
和/data2
目录下,可以这样配置:
properties
path.data=/path/to/data1/elasticsearch,/path/to/data2/elasticsearch
通过配置多个path.data
目录,可以将索引数据条带化地分布到多个硬盘上,从而提高I/O吞吐量和数据处理性能。这样做的原理是,通过将索引数据分散到多个物理磁盘上,可以并行地利用多个磁盘的I/O能力,减少数据访问的等待时间,进而提升整个集群的性能。代价显然就是当一块硬盘故障时整个节点就故障了。
合理设置分片数
分片和副本的设计为 ES 提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且索引的分片完成分配后由于索引的路由机制,是不能重新修改分片数的。
分片的成本:
- 一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。
- 每一个搜索请求都需要命中索引中的每一个分片,如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
- 如果分片数量过多,每一个都只有很少的数据会导致很低的相关度,而相关度过低会影响查询统计的性能。
了解了分片的成本之后,就要求必须合理的设置分片数:
- 推荐控制每个分片占用的硬盘容量不超过 ES 的最大 JVM 的堆空间设置,一般设置不超过 32G;
- 分片数量一般不超过节点数量的3倍,过多的分片数量可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了多副本,也有可能会导致数据丢失,集群无法恢复。
- 如果预计索引的总容量在 500G 左右,按照推荐最佳选择应该是,6个ES节点,16个分片。
合理设置副本数
副本数量决定了每个分片有多少个额外的拷贝,这些拷贝存储在集群的不同节点上,以防止单点故障导致的数据丢失。从集群的高可用性和查询性能的角度来说,副本数推荐设置为节点数减一,因为查询可以在多个节点上并行执行,也确保了数据的冗余。只要还有一个节点运行,即使其他节点全部发生故障,运行节点上的副本仍然可以提供数据服务,保证了集群的高可用性,但同时也意味着更多的存储和CPU开销。例如,在一个3节点的集群中,如果设置2个副本,那么每个主分片将有2个副本,分别存储在另外两个节点上。这样,即使其中两个节点完全离线,集群仍然可以正常工作。当然这个前提是
对于较大或更复杂的集群,设置副本数时需要对集群规模、数据价值、可用资源和成本的综合考虑,以达到数据可用性、性能和成本之间的最佳平衡,这样的话,副本数可能远小于节点数。例如,在一个有10个节点的集群中,可能只需要1或2个副本,具体取决于数据的重要性和集群的资源分配。
推迟分片分配
由于特殊原因导致的节点瞬时中断的问题,默认情况下,集群会等待一分钟来查看节点是否会重新加入,如果等待期间重新加入,会保持其现有的分片数据,不会触发新的分片分配,这样就可以减少 ES 在自动再平衡可用分片时所带来的极大开销。有时,1分钟的时间是远远不够的,比如长时间的网络波动,软件或系统升级等情况。通过修改参数 delayed_timeout ,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改。
全局修改:
kotlin
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}
专门为某个索引修改:
kotlin
PUT /{index}/_settings
{
"index": {
"unassigned": {
"node_left": {
"delayed_timeout": "5m"
}
}
}
}
路由选择
当我们查询文档的时候,Elasticsearch通过公式计算出文档所在的分片:shard = hash(routing) % number_of_primary_shards
,其中,routing 默认值是文档的 id,也可以采用自定义值;number_of_primary_shards是主分片的数量。
不带路由查询时,因为不知道要查询的数据具体在哪个分片上,请求到达协调节点后,协调节点会将查询请求分发到每个分片上,然后聚合每个分片上的查询结果,再进行排序后返回结果给客户端。带路由查询时,可以直接根据 routing 信息定位到某个分片,不需要协调节点去聚合排序,效率提升很多。
写入速度优化
ES 的默认配置,是综合了数据可靠性、写入速度、搜索实时性等因素。实际使用时,我们需要根据实际进行偏向性的优化。
批量数据提交
ES 提供了 Bulk API 支持批量操作,当我们有大量的写任务时,可以使用 Bulk 来进行批量写入。Bulk批量提交的数据量不能设置太大,防止单个请求占用过多的网络带宽和服务器资源,造成不必要的性能瓶颈,默认为100M。推荐单次批处理的数据大小应从 5MB~15MB 开始逐渐增加,观察性能指标(如响应时间、CPU和磁盘I/O使用率),当性能没有提升时,把这个数据量作为最大值。
合理使用段合并
当有新的数据写入索引时,Lucene 就会自动创建一个新的段,随着数据量的变化,段的数量会越来越多,消耗的多文件句柄数及 CPU 就越多,查询效率就会下降。由于 Lucene 段合并的计算量庞大,会消耗大量的 I/O,而 ES 默认让后台定期进行段合并,这可以应对绝大多数场景,如果有特殊情况需要调整,比如对于写密集型的应用,你可能希望减少段合并的频率,以减少写操作的延迟;而对于读密集型的应用,你可能希望更频繁地进行段合并,以提高查询效率。
Elasticsearch提供了几个参数来控制段合并的策略,例如:
index.merge.policy
: 控制段合并策略的配置,包括何时进行合并以及合并的条件。index.merge.scheduler.auto_throttle
: 控制是否自动调节段合并的速率,以避免过度消耗I/O资源。indices.memory.index_buffer_size
: 控制用于缓存段合并的内存大小。
减少 Refresh 的次数
Lucene 在新增数据时,采用了延迟写入的策略,默认情况下索引的 refresh_interval 为 1 秒。Lucene 将待写入的数据先写到内存中,超过 1 秒(默认)时就会触发一次 Refresh,然后 Refresh 会把内存中的的数据刷新到操作系统的文件缓存系统中。
单从优化写入速度的角度来讲,可以将 Refresh 周期延长至 30 秒,这样可以有效地减少段刷新次数,提升写入效率,但是会影响到搜索效率。因为在30秒内进行的所有写入操作都不会立即可见于搜索,直到30秒的周期结束并触发refresh才能进行搜索。
kotlin
PUT /{index}/_settings
{
"index": {
"refresh_interval": "30s"
}
}
如果设置为-1,意味着禁用自动refresh,可以用于批量写入操作,以获得最大的写入性能。
加大 Flush 设置
Flush 的主要目的是把文件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到 512MB 或者 30 分钟时,就会触发一次 Flush,可以通过修改index.translog.flush_threshold_size
调整数据量大小,默认是512MB。flush
操作本身是磁盘I/O密集型的,它会将数据从内存刷入磁盘,这在短时间内会占用较多的CPU和I/O资源,影响写入性能。
减少副本数量
每个主分片的副本也会执行分析、索引及可能的合并过程,所以 Replicas 的数量会严重影响写索引的效率。当写索引时,需要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。如 果 我 们 需 要 大 批 量 进 行 写 入 操 作 , 可 以 先 禁 止 Replica 复 制 , 设 置index.number_of_replicas: 0
关闭副本。在写入完成后,Replica 修改回正常的状态。
搜索速度优化
1.避免全文搜索大字段,对于大的文本字段,考虑使用keyword
类型或提取关键字。
在Elasticsearch中,text
和keyword
是两种用于处理字符串数据的字段类型,text
类型用于全文检索,keyword
类型用于精确匹配和排序,也就是可以创建一个字段,并为该字段定义多个类型的子字段。例如,定义title
字段是一个text
类型,用来全文搜索;title.keyword
字段是一个keyword
类型,用于精确匹配和排序。
json
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
2.如果是稀疏字段(在大多数文档中不存在或者未空),可以标记为doc_values为false,以减少存储和搜索开销。
当一个字段的doc_values
设置为true
时,表示该字段的数据将以一种特殊的格式存储在磁盘上,这种格式非常紧凑,非常适合快速的排序、聚合和脚本计算。这是因为doc_values
会将字段值存储为一个有序的、固定长度的数组,这个数组的长度等于索引中的文档数量。这种存储方式使得Elasticsearch能够非常快速地访问和处理字段值,特别是在进行排序和聚合操作时。
对于稀疏字段,如果仍然设置doc_values
为true
,即使字段在很多文档中不存在,也需要为每一个文档保留一个位置,这会占用额外的磁盘空间。将doc_values
设置为false
,字段数据将不会以紧凑的doc_values
格式存储,而是以普通的倒排索引格式存储。虽然这会略微降低排序和聚合的性能,但由于避免了为大量不存在的字段值保留空间,因此可以显著减少存储开销。更多的可用磁盘空间可以减少磁盘碎片,加快数据读取速度,从而间接提升查询速度。
3.对于数值数据,使用integer或float类型,这比字符串类型的搜索更快。
4.使用过滤器代替查询,过滤器在搜索时不参与评分,因此比查询更高效。
使用搜索
kotlin
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "category": "electronics" } },
{ "range": { "price": { "gte": 100, "lte": 500 } } }
]
}
}
}
使用过滤器
kotlin
GET /products/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "category": "electronics" } },
{ "range": { "price": { "gte": 100, "lte": 500 } } }
]
}
}
}
5.使用_source
,只返回必需的字段,减少数据传输量。
6.对于检查字段是否存在的情况,使用exists
查询比全文搜索更快。
7.对于大量结果,使用from
和size
进行分页查询。
8.开启查询缓存。
它主要用于缓存查询结果,尤其是那些不涉及文档评分的过滤器(Filters)的查询结果。通过缓存这些结果,当相同的查询再次执行时,Elasticsearch可以直接从缓存中获取结果,而不需要重新执行查询,从而显著提高了查询速度。
它是基于分片级别工作的,这意味着每个分片都会维护自己的查询缓存。当一个查询到达时,Elasticsearch会检查该分片的Query Cache中是否存在对应的缓存结果。如果存在,那么查询可以直接使用缓存的结果;如果不存在,查询将被执行,并且结果会被缓存,以便后续相同的查询可以使用。从Elasticsearch 7.10开始,Query Cache默认是关闭的,因为Query Cache会占用一定量的内存,如果配置不当,可能会导致内存使用过高,影响系统的稳定性;而且在某些情况下,维护Query Cache的成本(如更新缓存和检查缓存命中)可能会超过其带来的性能提升,尤其是当查询模式频繁变化时,缓存的命中率较低;另外,如果数据更新频繁,Query Cache中的数据可能很快变得过时,这要求缓存必须频繁更新,降低了缓存的有效性。
在elasticsearch.yml
配置文件中启用并调整Query Cache的参数:
indices.cache.query.size
: 设置Query Cache的最大大小。这是一个百分比值,表示相对于节点总堆大小的比例。例如,indices.cache.query.size: 10%
表示Query Cache最大可以占用堆内存的10%。indices.cache.query.expire
: 设置Query Cache中条目的过期时间。这可以防止缓存中存储过时的数据。例如,indices.cache.query.expire: 1m
表示缓存条目将在1分钟后过期。
如果查询模式相对稳定且同一查询会频繁出现、数据更新不频繁,启用Query Cache会带来性能显著提升。
9.调整ES的缓存策略。
- 节点查询缓存:主要用于缓存过滤器查询的结果。ES会自动缓存那些频繁使用的过滤器查询 ,以提高查询性能。节点查询缓存的大小可以通过
indices.queries.cache.size
参数进行配置,默认值为10%。如果过滤器查询非常频繁且结果稳定,可以适当增加缓存比例。从Elasticsearch 7.10开始,节点查询缓存被默认关闭,因为它在某些情况下可能会降低性能。如果需要启用,需要在elasticsearch.yml
中进行配置。 - 分片请求缓存:主要用于缓存搜索请求的结果,特别是聚合结果。ES会自动缓存那些频繁使用的搜索请求 ,以提高查询性能。分片请求缓存的大小可以通过
index.requests.cache.size
参数进行配置,默认值为1%。如果搜索请求非常频繁且结果稳定,可以适当增加缓存比例。从Elasticsearch 7.x开始,index.requests.cache.size
参数已被弃用,现在分为两个参数:index.search.id.cache.size
用于缓存搜索ID,而index.search.results.cache.size
用于缓存搜索结果。这两个缓存可以帮助减少对磁盘的读取,从而提高查询性能。 - 字段数据缓存:主要用于缓存字段数据,用于排序、聚合和脚本操作。ES会自动缓存那些频繁使用的字段数据 ,以提高查询性能。字段数据缓存的大小可以通过
indices.fielddata.cache.size
参数进行配置,默认值为无限制。如果字段使用非常频繁且结果稳定,可以适当增加缓存比例。从Elasticsearch 7.x开始,indices.fielddata.cache.size
参数被默认为无限制,但可以通过indices.memory.fielddata_size
参数来限制其最大内存消耗。 - 分片查询缓存:主要用于缓存查询的中间结果,特别是在复杂查询中。ES会自动缓存那些频繁使用的查询中间结果,以提高查询性能。分片查询缓存的大小可以通过
indices.queries.cache.size
参数进行配置,默认值为10%。从Elasticsearch 7.10开始,查询缓存被默认关闭,如果需要启用,需要在配置文件中明确设置。
其他优化建议
禁用交换(swap)
禁用交换区可以提高Elasticsearch等内存密集型应用的性能和稳定性,但需要注意内存压力的管理。
交换区会将内存中的部分数据写入磁盘,当系统需要使用这些数据时,再从磁盘读取回内存。这种操作会导致额外的磁盘I/O,显著降低性能。禁用交换区可以避免这些不必要的性能开销。
Elasticsearch运行在JVM上,JVM的垃圾回收机制需要大量的内存。如果系统频繁使用交换区,会导致GC暂停时间变长,从而影响应用的性能。禁用交换区可以减少这种情况的发生。
临时禁用交换区
shell
sudo swapoff -a
永久禁用交换区
shell
vim /etc/fstab
# /swapfile swap swap defaults 0 0
free -h # 确认输出中的`Swap`一行显示为0。
调整文件描述符限制
Elasticsearch在运行过程中会打开大量的文件句柄,包括索引文件、日志文件、缓存文件等。增加文件描述符的限制可以确保ES有充足的资源来管理这些文件句柄,避免因文件句柄不足而导致的服务异常或性能下降。可以在/etc/security/limits.conf
中添加。
shell
* hard nofile 65536
* soft nofile 65536