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

refresh参数在ElasticSearch中的使用场景

2022-07-073.9k 阅读

ElasticSearch 基础概念回顾

在深入探讨 refresh 参数的使用场景之前,我们先来简要回顾一下 ElasticSearch 的一些基础概念。

ElasticSearch 是一个分布式的开源搜索和分析引擎,旨在快速存储、搜索和分析大量数据。它基于 Lucene 库构建,提供了一个简单易用的 RESTful API 来与索引和文档进行交互。

索引与文档

  • 索引(Index):在 ElasticSearch 中,索引是文档的集合,类似于关系型数据库中的数据库概念。每个索引都有自己的映射(mapping),用于定义文档中字段的类型和属性。例如,我们可以创建一个名为 products 的索引来存储产品相关的文档。
PUT /products
{
    "mappings": {
        "properties": {
            "name": { "type": "text" },
            "price": { "type": "float" }
        }
    }
}
  • 文档(Document):文档是 ElasticSearch 中最小的数据单元,类似于关系型数据库中的行。每个文档都有一个唯一的标识符(ID),并且可以包含多个字段及其对应的值。以下是向 products 索引中添加文档的示例:
POST /products/_doc
{
    "name": "Sample Product",
    "price": 19.99
}

段(Segment)与提交点(Commit Point)

  • 段(Segment):Lucene 中的段是一个不可变的倒排索引结构,包含了一部分文档的索引数据。ElasticSearch 会将新写入的文档先存储在内存中的段(也称为写入缓冲区,write buffer),当写入缓冲区满了或者达到一定的时间间隔时,这些文档会被刷新到磁盘上的新段中。段的这种特性使得搜索操作可以高效地进行,因为多个段可以并行搜索。
  • 提交点(Commit Point):提交点是一个记录,它记录了当前索引中所有已提交段的列表。当进行提交操作时,ElasticSearch 会创建一个新的提交点,并将内存中的数据持久化到磁盘上,确保数据的持久性。然而,提交操作相对比较昂贵,因为它涉及到磁盘 I/O 操作。

refresh 参数详解

refresh 参数是 ElasticSearch 在文档写入操作时的一个重要选项,它主要用于控制文档何时对搜索可见。

refresh 参数的值及含义

  • true:当设置 refresh=true 时,ElasticSearch 在写入文档后会立即执行一次刷新操作,使得新写入的文档可以马上被搜索到。这种方式提供了最高的实时性,但由于每次写入后都要进行刷新操作,会对性能产生一定的影响,因为刷新操作涉及到将写入缓冲区的数据写入新段并打开新段供搜索使用,这会带来额外的磁盘 I/O 和资源开销。
POST /products/_doc?refresh=true
{
    "name": "New Product",
    "price": 29.99
}
  • false:默认值为 false,表示在写入文档后不会立即执行刷新操作。新写入的文档会先存储在写入缓冲区,直到满足某些条件(如写入缓冲区满了、达到默认的刷新间隔等)才会被刷新到磁盘上的段并对搜索可见。这种方式可以提高写入性能,因为减少了频繁的刷新操作,但会导致新写入的文档在一段时间内无法被搜索到,这段时间取决于刷新间隔和写入缓冲区的状态。
POST /products/_doc
{
    "name": "Another Product",
    "price": 39.99
}
  • wait_for:当设置 refresh=wait_for 时,ElasticSearch 会等待直到刷新操作完成,确保新写入的文档对搜索可见。这种方式结合了实时性和性能的平衡,它不像 true 那样每次写入都立即刷新,而是等待一个合适的时机进行刷新操作,同时又能保证文档在写入操作完成后能马上被搜索到。
POST /products/_doc?refresh=wait_for
{
    "name": "Special Product",
    "price": 49.99
}

refresh 参数的使用场景

实时搜索场景

在一些对搜索实时性要求极高的场景中,如股票交易系统、实时监控系统等,新数据需要尽快对搜索可见。例如,在股票交易系统中,每一笔新的交易记录都需要立即反映在搜索结果中,以便交易员及时获取最新信息。

// Java 代码示例,使用 Elasticsearch Java High - Level REST Client
RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost", 9200, "http")));

IndexRequest indexRequest = new IndexRequest("stock_trades")
      .id("1")
      .source(XContentType.JSON, "symbol", "AAPL", "price", 150.0, "quantity", 100);

IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
// 设置 refresh=true 确保新交易记录立即可搜索
IndexRequest indexRequestWithRefresh = new IndexRequest("stock_trades")
      .id("2")
      .source(XContentType.JSON, "symbol", "GOOG", "price", 2500.0, "quantity", 50)
      .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);

IndexResponse indexResponseWithRefresh = client.index(indexRequestWithRefresh, RequestOptions.DEFAULT);

在这个场景中,设置 refresh=truerefresh=wait_for 是比较合适的选择。虽然会增加一定的性能开销,但能满足实时性的需求。

批量写入场景

在进行大量文档批量写入时,为了提高写入性能,通常会将 refresh 参数设置为 false。例如,在数据导入的场景中,可能需要一次性导入成千上万条数据。

from elasticsearch import Elasticsearch

es = Elasticsearch([{'host': 'localhost', 'port': 9200}])

data = [
    {'name': 'Product 1', 'price': 10.0},
    {'name': 'Product 2', 'price': 20.0},
    # 更多数据...
]

for doc in data:
    es.index(index='products', body=doc, refresh=False)

通过将 refresh 设置为 false,可以减少刷新操作的频率,从而大大提高写入性能。因为每次刷新操作都会涉及到磁盘 I/O 和资源开销,批量写入时频繁刷新会严重影响效率。在批量写入完成后,可以手动执行一次刷新操作,使得所有新写入的文档对搜索可见。

POST /products/_refresh

混合场景

有些应用场景可能既有实时写入的需求,又有批量写入的情况。例如,在一个电商系统中,用户下单操作需要实时反映在搜索结果中,以便客服人员能及时查询到订单信息;而商品数据的批量更新(如库存更新)则更注重性能。

const { Client } = require('@elastic/elasticsearch');

const client = new Client({ node: 'http://localhost:9200' });

// 实时下单场景
async function placeOrder(order) {
    const response = await client.index({
        index: 'orders',
        body: order,
        refresh: 'wait_for'
    });
    return response;
}

// 批量商品库存更新场景
async function updateProductStock(products) {
    const requests = products.map(product => ({
        index: {
            _index: 'products',
            _id: product.id
        }
    }));
    requests.push(...products);
    const response = await client.bulk({
        refresh: false,
        body: requests
    });
    await client.indices.refresh({ index: 'products' });
    return response;
}

在这种混合场景中,对于实时性要求高的操作(如下单),可以使用 refresh=wait_for;对于批量操作(如商品库存更新),先将 refresh 设置为 false 以提高性能,操作完成后再手动执行刷新。

refresh 参数与性能优化

虽然 refresh 参数在满足不同需求方面非常灵活,但它对性能有着显著的影响,因此需要进行合理的优化。

减少不必要的刷新

如前文所述,刷新操作会带来磁盘 I/O 和资源开销,所以应尽量减少不必要的刷新。在批量写入场景中,将 refresh 设置为 false 是一个有效的优化手段。同时,也可以适当调整 ElasticSearch 的默认刷新间隔,默认情况下,ElasticSearch 每 1 秒自动执行一次刷新操作。如果应用场景对实时性要求不是特别高,可以适当增大这个间隔,例如设置为 5 秒或更长。

PUT /products/_settings
{
    "index": {
        "refresh_interval": "5s"
    }
}

异步刷新

在一些场景中,可以考虑使用异步刷新的方式。例如,在批量写入完成后,不立即执行刷新操作,而是将刷新操作放入一个异步任务队列中,由专门的线程或进程在系统负载较低的时候执行刷新。这样可以避免在高负载时刷新操作对系统性能的影响。

import threading
import time

def async_refresh(index):
    time.sleep(60)  # 等待 60 秒,可根据实际情况调整
    from elasticsearch import Elasticsearch
    es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
    es.indices.refresh(index=index)

# 批量写入后启动异步刷新线程
index = 'products'
thread = threading.Thread(target=async_refresh, args=(index,))
thread.start()

结合其他性能优化手段

refresh 参数的优化不能孤立进行,还需要结合其他性能优化手段。例如,合理设置 ElasticSearch 的缓存机制,包括节点级别的字段数据缓存(field data cache)和查询结果缓存(query cache),可以减少磁盘 I/O 和计算开销。同时,对索引的设计进行优化,如选择合适的字段类型、避免过多的嵌套字段等,也能提高整体性能。

总结与注意事项

refresh 参数在 ElasticSearch 中是一个非常重要的配置选项,它直接影响到数据的实时性和系统的性能。在实际应用中,需要根据具体的业务场景和性能需求,合理选择 refresh 参数的值。

  • 实时性需求:如果业务对数据的实时搜索要求极高,如金融交易、实时监控等场景,可选择 refresh=truerefresh=wait_for,但要注意性能开销。
  • 性能优先:对于批量写入或对实时性要求不高的场景,应将 refresh 设置为 false,以提高写入性能。操作完成后可根据需要手动执行刷新。
  • 混合场景:在既有实时写入又有批量操作的混合场景中,需要灵活运用 refresh 参数,对不同的操作采用不同的设置。

同时,为了保证系统的整体性能,还需要结合其他性能优化手段,如调整刷新间隔、使用异步刷新、优化索引设计和缓存设置等。只有综合考虑这些因素,才能充分发挥 ElasticSearch 的优势,满足各种复杂的业务需求。

通过深入理解 refresh 参数的使用场景和性能优化方法,开发者可以更好地利用 ElasticSearch 构建高效、实时的搜索和分析应用。在实际项目中,需要不断地测试和调整,以找到最适合业务需求的配置方案。