MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

如何选择ElasticSearch中的refresh值

2023-01-014.4k 阅读

ElasticSearch中的refresh机制概述

在ElasticSearch中,refresh机制在数据可见性和性能之间扮演着关键的平衡角色。ElasticSearch采用了近实时(Near - Realtime, NRT)的搜索模型,这意味着文档一旦被索引,不会立即对搜索可见,而是经过一个短暂的延迟。这个延迟主要与refresh操作相关。

ElasticSearch将索引数据存储在Lucene的段(Segment)中。每当有新文档写入,ElasticSearch不会直接修改已有的段,而是创建新的段来存储新数据。在进行搜索时,ElasticSearch需要合并这些段以返回完整的结果。refresh操作的主要目的就是使新写入的文档对搜索可见。它会将内存中的数据(translog和buffer)刷新到新的段中,并使这些段对搜索可见,而不需要等待Lucene的段合并操作(这通常是一个相对耗时的过程)。

refresh值的基本概念

refresh值决定了ElasticSearch执行refresh操作的频率。它可以设置为不同的值,常见的设置方式有以下几种:

  1. 默认设置:ElasticSearch默认每1秒执行一次refresh操作。这意味着新写入的文档在1秒内就可以对搜索可见,这种设置在大多数情况下能满足近实时搜索的需求。例如,对于一个实时性要求不是特别高的博客搜索系统,1秒的延迟通常是可以接受的。
  2. 手动设置refresh值:可以通过在索引文档时指定refresh参数来手动控制refresh操作的执行。例如,在使用ElasticSearch的REST API时,可以在POST请求的URL中添加?refresh=true,这样在文档写入后会立即执行refresh操作,使文档立即对搜索可见。
  3. 禁用refresh:将refresh设置为false,则会禁用自动的refresh操作。这在批量写入大量数据时非常有用,因为频繁的refresh操作会带来较大的性能开销。

不同场景下refresh值的选择

  1. 实时性要求极高的场景
    • 场景描述:在金融交易监控系统中,每一笔交易的记录都需要立即对搜索可见,以便及时发现异常交易行为。
    • refresh值选择:这种情况下,应该在每次写入文档时设置refresh=true。通过ElasticSearch的Java客户端代码示例如下:
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
public class RealTimeIndexing {
    private final RestHighLevelClient client;
    public RealTimeIndexing(RestHighLevelClient client) {
        this.client = client;
    }
    public void indexDocument(String indexName, String documentId, String jsonString) throws IOException {
        IndexRequest request = new IndexRequest(indexName)
              .id(documentId)
              .source(jsonString, XContentType.JSON)
              .setRefreshPolicy("wait_for");
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        if (response.getResult().name().equals("CREATED") || response.getResult().name().equals("UPDATED")) {
            System.out.println("Document indexed successfully");
        } else {
            System.out.println("Indexing failed: " + response.getResult().name());
        }
    }
}

在上述代码中,setRefreshPolicy("wait_for")确保了文档写入后立即执行refresh操作,使文档对搜索可见。然而,这种方式会对写入性能产生较大影响,因为每次写入都触发一次refresh操作,可能导致I/O压力增大。

  1. 批量写入场景
    • 场景描述:在大数据日志采集系统中,每天可能会有大量的日志数据需要写入ElasticSearch。这些日志数据的实时性要求相对较低,更注重写入性能。
    • refresh值选择:在批量写入时,应禁用自动refresh操作,即设置refresh=false。以Python的Elasticsearch - Py库为例:
from elasticsearch import Elasticsearch
es = Elasticsearch()
data_list = [{"message": "log message 1"}, {"message": "log message 2"}]
for data in data_list:
    es.index(index='log_index', body=data, refresh=False)

在这个示例中,通过设置refresh=False,避免了每次写入时的refresh操作,大大提高了写入性能。在批量写入完成后,可以手动执行一次refresh操作,使所有写入的文档对搜索可见。例如,使用ElasticSearch的REST API:

POST /log_index/_refresh

这样可以在保证写入性能的同时,在合适的时机确保数据的可见性。

  1. 一般业务场景
    • 场景描述:对于一个普通的电商搜索系统,用户希望在几分钟内就能搜索到新上架的商品,但又不想因为过于频繁的refresh操作影响系统性能。
    • refresh值选择:可以适当延长refresh的间隔时间。例如,将refresh间隔设置为30秒。通过修改ElasticSearch的索引设置来实现:
PUT /product_index/_settings
{
    "index": {
        "refresh_interval": "30s"
    }
}

这种设置在一定程度上平衡了数据可见性和性能。新上架的商品在30秒后对搜索可见,同时减少了refresh操作的频率,降低了系统开销。

refresh值对性能的影响

  1. 写入性能
    • 频繁refresh:当refresh间隔设置得非常短(如refresh=true每次写入都刷新)时,写入性能会显著下降。这是因为每次refresh操作都需要将内存中的数据刷新到磁盘,涉及到I/O操作。频繁的I/O操作会导致磁盘I/O瓶颈,从而降低整体的写入速度。
    • 延长refresh间隔或禁用refresh:在批量写入时禁用refresh(refresh=false)或延长refresh间隔,写入性能会得到提升。因为减少了I/O操作的频率,系统可以更专注于数据的写入,从而提高写入吞吐量。
  2. 搜索性能
    • 频繁refresh:频繁的refresh操作虽然能使数据更快地对搜索可见,但也会增加搜索时的负担。因为每次refresh都会创建新的段,搜索时需要合并更多的段来获取完整的结果,这会增加搜索的CPU和内存开销。
    • 延长refresh间隔:延长refresh间隔可以减少搜索时需要合并的段数量,从而提高搜索性能。但同时,数据的可见性会有一定延迟,需要在性能和数据实时性之间进行权衡。

refresh值与集群资源的关系

  1. 磁盘资源
    • 频繁refresh:频繁的refresh操作会导致更多的数据写入磁盘,因为每次refresh都会将内存中的数据刷新到新的段中。这会增加磁盘的I/O负载,可能导致磁盘空间快速消耗。如果磁盘I/O性能不足,频繁的refresh操作可能会使系统性能急剧下降。
    • 延长refresh间隔:延长refresh间隔可以减少磁盘I/O操作的频率,从而降低磁盘资源的消耗。这对于磁盘空间有限或I/O性能较低的集群来说非常重要,可以有效避免磁盘瓶颈问题。
  2. 内存资源
    • 频繁refresh:由于每次refresh都会将内存中的数据刷新到磁盘,频繁的refresh操作会使内存中的数据更快地被清空。这在一定程度上可以减少内存的占用,但如果refresh过于频繁,可能会导致系统频繁地进行内存与磁盘之间的数据交换,增加系统的整体开销。
    • 延长refresh间隔:延长refresh间隔意味着内存中的数据会在内存中停留更长时间。这可能会增加内存的占用,如果内存不足,可能会导致系统出现OOM(Out of Memory)错误。因此,在设置refresh间隔时,需要考虑集群的内存资源情况,确保内存使用在合理范围内。

refresh值与数据一致性的关系

  1. 强一致性要求
    • 在某些对数据一致性要求极高的场景,如银行转账记录的搜索,需要确保新转账记录立即对搜索可见,以避免出现数据不一致的情况。在这种情况下,设置refresh=true每次写入都执行refresh操作,可以保证数据的强一致性。但正如前面提到的,这会对性能产生较大影响。
  2. 最终一致性
    • 对于大多数业务场景,最终一致性是可以接受的。例如,社交媒体平台上用户发布的动态,即使在发布后几秒钟甚至几分钟才对搜索可见,用户通常也不会感觉到明显的差异。在这种场景下,可以适当延长refresh间隔或在批量写入时禁用refresh,以提高系统性能,同时保证最终数据的一致性。

动态调整refresh值的策略

  1. 根据业务负载动态调整
    • 业务负载监测:可以通过ElasticSearch的监控工具(如Elasticsearch Monitoring或第三方监控工具)来实时监测集群的写入和搜索负载。例如,在业务高峰期,写入负载较大,可以适当延长refresh间隔以提高写入性能;在业务低谷期,写入负载较小,可以缩短refresh间隔以提高数据的实时性。
    • 自动化调整:利用ElasticSearch的API和脚本,可以实现refresh值的自动化调整。例如,通过编写一个脚本,根据集群的当前写入吞吐量动态调整refresh间隔:
import requests
import json
# 获取集群写入吞吐量
response = requests.get('http://localhost:9200/_nodes/stats/indices/write?pretty')
data = json.loads(response.text)
write_throughput = data['nodes'][list(data['nodes'].keys())[0]]['indices']['write']['total']['throughput_in_bytes_per_sec']
# 根据写入吞吐量调整refresh间隔
if write_throughput > 1000000:  # 如果写入吞吐量大于1MB/s
    refresh_interval = '60s'
else:
    refresh_interval = '30s'
# 修改索引设置
settings = {
    "index": {
        "refresh_interval": refresh_interval
    }
}
requests.put('http://localhost:9200/my_index/_settings', json = settings)
  1. 根据数据重要性动态调整
    • 数据分类:将数据按照重要性进行分类,例如,对于重要的交易数据,设置较短的refresh间隔或每次写入都执行refresh操作;对于普通的日志数据,设置较长的refresh间隔。
    • 索引级别设置:可以在不同的索引级别设置不同的refresh值。例如,在ElasticSearch中创建两个索引,一个用于存储重要交易数据,设置refresh_interval5s;另一个用于存储普通日志数据,设置refresh_interval60s
PUT /transaction_index
{
    "settings": {
        "index": {
            "refresh_interval": "5s"
        }
    }
}
PUT /log_index
{
    "settings": {
        "index": {
            "refresh_interval": "60s"
        }
    }
}

refresh值与其他ElasticSearch参数的关联

  1. translog参数
    • translog作用:translog是ElasticSearch用于保证数据可靠性的机制,它记录了所有的写入操作。在refresh操作时,translog中的数据会被合并到新的段中。
    • 与refresh关联:如果refresh间隔设置得很短,translog中的数据会频繁地被合并到段中,这可以减少translog的大小,降低数据恢复时的成本。但同时,频繁的refresh操作也会增加I/O开销。如果refresh间隔设置得很长,translog会不断增长,在发生故障时,数据恢复的时间会变长。因此,在设置refresh值时,需要考虑translog的相关参数,如index.translog.durabilityindex.translog.sync_interval。例如,如果将index.translog.durability设置为async,可以提高写入性能,但可能会在系统故障时丢失部分数据;而将其设置为sync,则可以保证数据的可靠性,但会降低写入性能。
  2. merge参数
    • merge作用:Lucene的段合并操作会将多个小的段合并成一个大的段,以提高搜索性能。合并操作通常在后台进行,并且会消耗一定的系统资源。
    • 与refresh关联:refresh操作会创建新的段,而频繁的refresh操作会导致段的数量增加,从而增加merge操作的频率和负担。如果refresh间隔设置得很长,段的数量增长较慢,merge操作的频率也会相应降低。但如果段合并不及时,过多的小段会影响搜索性能。因此,需要在refresh值和merge参数(如index.merge.policy.max_merge_at_onceindex.merge.scheduler.max_thread_count)之间进行平衡。例如,通过调整index.merge.policy.max_merge_at_once可以控制每次合并的段数量,避免在段数量较多时合并操作对系统性能造成过大影响。

在选择ElasticSearch中的refresh值时,需要综合考虑业务场景的实时性要求、系统性能、集群资源以及与其他参数的关联等多方面因素。通过合理地设置refresh值,可以在数据可见性和系统性能之间找到最佳平衡点,使ElasticSearch集群能够高效稳定地运行。