写入优化
写入时先将副本分片数置为0,完成写入后再将其复原
写入时先将副本分片数置为0,完成写入后再将其复原,设置命令如下所示:
1 | PUT test |
优先使用系统自动生成ID的方式
文档ID的生成有两种方式:系统自动生成ID和外部控制自增ID。
如果使用外部控制自增ID,Elasticsearch会先尝试读取原来文档的版本号,以判断是否需要更新。也就是说,使用外部控制自增ID比系统自动生成ID要多进行一次读取磁盘操作。
所以,非特殊场景建议使用系统自动生成ID的方式。
合理调整刷新频率
背景
为了提高索引性能,Elasticsearch在写入数据的时候,采用延迟写入的策略。
先将数据写到内存中,当超过默认1秒(index.refresh_interval
)会进行一次写入操作,将内存中segment数据刷新到磁盘中。
此时我们才能将数据搜索出来,所以这就是为什么Elasticsearch提供的是近实时搜索功能,而不是实时搜索功能。
关闭自动刷新
将refresh_interval
设置为-1
,即禁用索引的自动刷新,这样索引将不会按照固定的时间间隔进行自动刷新,而是只有在显式调用_refresh
API或者执行某些特定的操作(如批量索引完成后)时才会触发刷新。
示例代码:
1 | PUT test |
注意:
- 在完成写入操作后,应立即调用
_refresh
API来手动刷新索引。 - 在完成写入操作后,应将
refresh_interval
恢复。
延长refresh时间间隔
结合业务需求,我们可以考虑延长refresh时间间隔,这样可以有效地减少segment合并压力,提高索引速度。
示例代码:
1 | PUT test |
合理调整堆内存中的索引缓冲区大小
调整方法
在堆内存中,索引缓冲区用于存储新索引的文档,填满后,缓冲区中的文档将最终写入磁盘上的某个段。
index_bufer_size
的默认值为堆内存的10%。
1 | indices.memory.index_buffer_size: 10% |
例如:给JVM提供31GB的内存,它将为索引缓冲区提供3.1GB的内存,一般情况下足以容纳大量数据的写入操作;如果数据量确实非常大,则建议调大该默认值,例如调整为堆内存的20%,但是必须在集群中的每个数据节点上进行配置。缓冲区越大,意味着能缓存的数据量越大,相同时间内,写入磁盘的频次更低、磁盘IO次数更少,从而提升写入性能。
给堆外的内存留够空间
官方建议内存分配时设置堆内存为机器内存大小的一半但不要超过32GB,堆内存之外的内存留给Lucene使用。
一般设置建议如下:
- 如果内存大小 64GB,则堆内存设置为31GB。
- 如果内存大小 64GB,则堆内存设置为内存大小的一半。
批量写入和多线程并发写入
在高写入负载的场景下,为了提高写入效率,Elasticsearch支持批量写入。
即,我们在《2.基本操作》讨论的"_bulk"。
批量写入允许将多个文档合并成一个请求发送给Elasticsearch,这样可以显著减少网络往返次数和每个请求的开销,从而提高写入性能,而且批量写入意味着相同时间产生的段更大,段的总个数更少。
但批量值的设置一般需要慎重,不能盲目地将其设置得太大。一般建议进行递增步长测试,直到资源使用上限。例如,第一次将批量值设置为100,第二次为200,第三次为400,等。
如果批量值设置完成,但集群尚有富余资源、资源利用没有饱和,怎么办?这时候可以考虑采用多线程,通过并发提升写入性能。
Elasticsearch内部使用了多种线程池来处理不同的任务,包括索引、搜索、批量写入等。对于批量写入,Elasticsearch使用了一个特殊的线程池(通常称为bulk
线程池),这个线程池负责从批量队列中取出请求并处理。批量队列的大小是有限的,这是为了防止过多的未处理请求堆积,占用过多的系统资源,导致集群不稳定甚至崩溃。
当批量队列已满,Elasticsearch会报错"Bulk Rejections",新的批量写入请求无法被加入队列时。这通常是由于写入速率超过了集群的处理能力,或者线程池的线程数不足以快速处理队列中的请求,导致队列溢出。为了避免这种情况,可以考虑以下策略:
- 增加线程池大小
通过调整thread_pool.bulk.queue_size
和thread_pool.bulk.size
配置项来增加批量队列的大小和线程池的线程数,但这需要谨慎操作,以避免过度消耗系统资源。 - 优化批量写入参数
合理设置bulk.size
和concurrent_requests
参数,以平衡批量写入的大小和并发度,找到最佳的写入性能和稳定性之间的平衡点。
设置合理的映射
在实际业务中,不推荐使用默认的动态映射,一定要手动设置映射。
举例:
- 若默认字符串类型是text和keyword的组合类型,不一定适用于所有业务场景,需要结合业务场景进行设置,正文文本内容一般不需要设置keyword类型(因为不需要排序和聚合操作)。
- 在有些采集数据并存储的场景中,正文文本内容需要进行全文检索,但HTML样式的文本一般会留给前端展示用,不需要索引。因此,映射设置时需要将index设置为false。
合理使用分词器
分词器决定分词的粒度,对于中文常用的IK分词,可细分为粗粒度分词ik_smart
和细粒度分词ik_max_word
。
从存储角度来看,基于ik_max_word
分词的索引会比基于ik_smart
分词的索引占据空间大,而更细粒度的自定义分词Ngram
会占用大量资源,并且可能减慢索引速度并显著增加索引大小 。
所以,要结合检索指标(召回率和精准率)以及写入场景进行选型。
写入过程监控
Kibana的监控功能提供了很多指标,索引率(index rate)和查询速率(search rate)。
- 索引率
索引率是指每秒写入Elastiseach的文档数。 - 查询速率
查询速率表示每秒的查询次数,反映了Elastisearch集群的读取性能。
对于这两个指标,我们需要关注的主要是其稳定性和持续性。如果任何一个指标突然变化,都可能意味着系统中存在问题。这可能是因为硬件资源不足,也可能是因为查询或索引操作过于复杂。
除此之外,我们还需要关注其他一些指标,例如CPU使用率、内存使用率、磁盘IO等。
检索优化
不要返回全量或近全量数据
对于返回全量数据的需求,要评估其合理性。
如果必须返回全量文档,建议使用scroll API
,这个可以更快地返回检索结果,并具有更好的性能。
避免使用大文档
不建议使用大文档。
大文档给网络、内存使用和磁盘使用带来了压力,在实现全文检索和高亮请求时,响应时间会随着原始文档的大小而增加。
比如说,我们有一本书需要被搜索。这时候,不一定要将整本书作为一个文档,更好的做法是将每一章(节)甚至每一段作为一个文档,然后在这些文档中添加一个属性,标识它们属于哪一本书的哪一章(节)。这不仅避免了大文档带来的问题,还可以获得更好的搜索体验。
检索方法层面的优化
尽可能减少检索字段数目
采用"显式指定需要返回的字段"的方法来减少检索字段数目。
具体是,在查询请求中使用_source
参数指定需要返回的字段,避免返回所有字段。
合理设置size值
若将检索请求的size值设得很大,则会导致命中数据量大。一方面,会增加网络传输的负载,导致网络延时和性能下降;另一方面,需要占用更多的内存来缓存结果集,导致内存消耗过大,甚至造成OOM。
建议根据具体的业务场景和数据规模来合理设置分页size值,以有效减少资源消耗和保护系统的稳定性。
如果数据量确实很大,则可以考虑通过scroll
或者search_after
方式来实现。
尽量避免使用脚本
脚本可以用来执行一些高级的检索和聚合操作,但是在实际应用中,建议尽可能避免使用脚本,因为它们可能会影响性能,并且不够安全。
因为:
- 使用脚本会需要额外的计算资源和时间,这可能对性能产生负面影响。
- 如果允许用户自定义脚本,那么恶意用户可能会编写脚本来执行一些不安全的操作,例如删除索引或访问敏感信息。
所以,在生产环境中,建议禁止用户自定义脚本或仅支持受信任的用户组使用脚本。
有效使用filter缓存
为了提高性能,Elasticsearch引入了filter缓存机制。
filter缓存是指在执行过滤操作时,将结果缓存到内存中,以便在后续的查询中能够快速访问。
这样可以避免对同一个过滤器进行重复计算,从而提高查询性能。
尤其是在对相同的过滤器进行多次查询时,这种缓存方式可以大幅提高查询效率。
避免使用wildcard检索
避免使用wildcard通配符检索,尤其是前缀通配符查询。
原因在于:当使用通配符查询时,倒排索引针对keyword类型并不能发挥优势,Elastcsarch必须遍历所有符合通配符表达式的文档,这可能会导致性能下降和查询响应慢。
面对类似需求,推荐在前期使用预处理Ngram分词,以空间换时间来解决问题。
尽量避免使用正则匹配检索
不建议频繁使用正则匹配检索的方式,因为:
- 性能问题
正则匹配检索通常比其他检索方法要慢得多,特别是当使用复杂的正则表达式时。
正则表达式需要对每个文档的每个字段进行匹配,这对于大型数据集来说可能会导致性能问题。 - 精度问题
正则匹配检索是基于模式实现的,因此它可能会返回一些用户不需要的结果。
例如,你正在寻找包含单词car的文档,但是你的正则表达式模式匹配了包含单词"catch"和"category"的文档,这将导致精度问题。 - 无法利用倒排索引机制
使用正则表达式进行检索时,将扫描整个文档集合,无法使用索引加快搜索。
正则匹配检索会有响应慢及性能问题,因此要谨慎使用,建议尽量避免。
如果必须使用正则表达式,则需尽可能简化它们,并使用其他过滤条件来缩小返回的结果集。
谨慎使用全量聚合和多重嵌套聚合
聚合本身是不精准的,主要是因为主、副本分片数据的不一致性。
对于实时性业务数据的场景,每分、每秒都有数据写入,就要考虑到数据在变化,聚合结果也会随之变化。
全量聚合会在所有匹配的文档上执行聚合操作,如果匹配的文档数非常大,就可能会占用大量的内存,甚至导致OOM。
同时,全量聚合需要扫描所有匹配的文档,会对查询性能产生影响。
多重嵌套聚合指的是在一个聚合操作内嵌套另一个聚合操作,形成多层嵌套。这种方式会增加查询复杂度和降低查询性能。
因此,在使用Elasticsearch进行聚合分析时,应该谨慎使用全量聚合和多重嵌套聚合,可以考虑将聚合操作拆分成多个步骤来执行,从而降低每个聚合操作的复杂度、减小检索范围。
慢查询日志
可以通过如下的命令,开启慢查询日志。
1 | PUT /_template/{TEMPLATE_NAME} |
解释说明:
PUT /_template/{TEMPLATE_NAME}
,使用模板(Template),未来符合该模板的索引也会被影响。PUT {INDEX_PATTERN}/_settings
,针对一个或一组具体的索引(通过{INDEX_PATTERN}
指定)进行设置,这种修改只会影响指定的索引,不会影响其他未提及的索引,也不会对未来创建的索引产生作用。
设置完成后,在日志目录下的慢查询日志就会有输出记录必要的信息了:
{CLUSTER_NAME}_index_indexing_slowlog.log
{CLUSTER_NAME}_index_search_slowlog.log
数据模型优化
文档结构务必规范、一致
- 避免将结构完全不同的文档放入同一索引,将这些文档放入不同的索引通常会更好。
- 考虑为较小的索引提供较少的分片,因为总体上包含的文档较少。
- 避免具有相同功能的字段命名不一致的问题。
例如,如果索引中的文档包含时间戳字段,但有些文档将其命名为"timestamp",有些文档则将其命名为"ts",这种情况是要避免的。
建议在设计建模阶段就进行一致性处理,以方便后续的检索操作。
设置合理的分片数和副本数
主分片的设置需要结合集群数据节点规模、全部数据量和日增数据量等维度综合考量才给出值, 一般建议设置为数据节点的1~3倍。
分片不宜过小,有很多小分片可能会导致大量的网络调用和线程开销,这会严重影响搜索性能。
在许多情况下,拥有更多副本有助于提高搜索性能,但是不代表副本越多越好。
增加副本之前要考虑磁盘存储空间的容量上限和磁盘警戒水位线,本质还是以空间换时间。
对于一般的非高可用场景,认为一个副本足够。
多使用写入前预处理操作
假设存在一个舆情系统,其中设计的情感值包括3个区间,负面、正面、中性。如果通过范围检索方式进行区间检索会慢。
可以考虑在建立数据模型的时候将数据在写入阶段转成-1
、0
、1
的keyword类型值,以此将范围检索变成基于倒排索引的精准查找的过程。
合理使用边写入边排序机制
考虑以下配置代码段。此配置展示了在创建索引时如何设定排序字段,并且排序操作并不在数据写入后进行,而是预先定义好,即在索引创建时设置。
这个过程的主要目标是通过牺牲一些写入性能来显著提升检索速度。
示例代码:
1 | PUT my-index |
尽量使用keyword字段类型
如果一个字段既可以设置为number类型,也可以设置为keyword类型。
那么可以参考如下方式:
- 如果涉及范围检索,推荐使用数值类型的字段。
- 如果仅需精准匹配term级别的检索,推荐keyword类型。
- 如果两种都有需求,则建议设置keyword和number双类型,可借助fields组合类型实现。
示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15PUT test
{
"mappings":{
"properties":{
"age":{
"type":"integer",
"fields":{
"keyword":{
"type":"keyword"
}
}
}
}
}
}
硬件优化
内存
Elasticsearch是一个内存密集型的应用程序,足够的内存可以提高性能和响应速度。
- Elasticsearch使用缓存来加速搜索操作,缓存是存储在内存中的数据结构,可以减少对硬盘的读取次数,内存越大,可以存储的缓存也就越多,从而可以提高搜索性能。
- Elasticsearch使用倒排索引来加速搜索操作,倒排索引和字段数据也需要存储在内存中,以加快搜索和聚合操作。
- Java程序的内存管理是通过GC来实现的,内存越大,垃圾回收的效率也就越高,从而可以缩短系统的停顿时间。
我们需要给Elasticsearch分配足够的内存以提高搜索性能、减少停顿时间和提高系统的稳定性,同时我们需要根据具体的应用场景和数据规模来合理配置内存大小。
磁盘
SSD
建议必要时使用SSD;如果SSD资源紧张,建议结合业务场景设置冷热集群架构,将SSD优先分配给热节点。
警戒水位线
属性名 | 属性值 | 含义 |
---|---|---|
cluster.routing.allocation.disk.watermark.low |
低警戒水位线 | |
cluster.routing.allocation.disk.watermark.high |
高警戒水位线 | |
cluster.routing.allocation.disk.watermark.flood_stage |
洪泛警戒水位线 |
CPU
Elasicsarch是一个高并发的应用;在选择CPU时,应尽量选择多核的CPU。
在并发写入或查询量变大之后,可能会出现CPU满了的情况,建议根据CPU核数合理调节线程池和队列的大小。