如何解决Elasticsearch索引数据延迟

出现场景

前端在调用后台新增的数据之后,立刻调用列表获取接口,但是后台列表获取接口没有返回新增的数据。由于新增的过程都是同步的操作,且对Elasticsearch操作也有成功返回(调用Elasticsearch使用的是开源的工具JestClient),遇到这个问题百思不得其解,觉得不存在代码问题。所以最大的可能性Elasticsearh存储数据的时候,会不会不是实时的?

根本原因

当我看过官方文档https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html之后,更加确定我确定了我的想法。

本身Elasticsearch就被定义为几乎实时搜索-在1秒内(it is indexed and fully searchable in near real-time–within 1 second.)。Lucene引入了the concept of per-segment search。Lucene的index表示”segment的集合加上提交点”。提交后,提交点将添加一个new segment,缓冲区被清除。

位于Elasticsearch和磁盘之间是文件系统缓存,内存索引缓冲区的文件(图1)被写入new segment(图2)。new segment先写入文件系统缓存(不会消耗性能),然后才刷新到磁盘(会很消耗性能)。然而,文件在缓存中后,它可以想任何其他文件一起打开和读取,但是不可搜索。

​ 图1 内存缓冲区中包含新文档的index

Lucene允许编写和打开new segment,使其包含的文档可见,无需知晓完整提交即可搜索。这个是比提交磁盘消耗性能更低的过程,并且经常完成,而不降低性能。

​ 图2 缓存区内容写入一个segment,该segment可以搜索,但尚未提交

在Elasticsearch中,这种编写和打开new segment的过程叫做refreshrefresh 使从上次刷新以来对index执行的所有的操作都可以用于搜索。

控制refresh的方式如下:

  • 等待刷新间隔
  • 设置?refresh参数
  • 通过使用Refresh API来显式完成一次refresh操作

默认情况下,Elasticsearch每秒定期刷新index,但仅适用于在过去30秒中收到一个或者多个搜索请求的index

扩展知识点

以下的知识点是控制refresh其中两种方式的扩展

通过使用Refresh API显式完成refresh操作

  • 由于refresh_interval默认为1s,我们无法复现refresh的间隔中无法查询到数据的场景,所以暂时将index为foorefresh_interval调整为15s。其中如果将refresh_interval设置为-1,则为禁止refresh。
  • Request PUT http://localhost:9200/foo Content-Type: application/json { "settings" : { "refresh_interval" : "15s" } }
  • Result { "acknowledged": true, "shards_acknowledged": true, "index": "foo" }
  • foo的index写入一条数据
  • Request POST http://localhost:9200/foo/_doc/1 Content-Type: application/json {"foo":"test"}
  • Result { "_index": "foo", "_type": "_doc", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
  • 搜索foo的index中的文档数据,保证距离上次写入数据到搜索调用在15s内
  • Request POST http://localhost:9200/foo/_doc/_search Content-Type: application/json {"query":{"match_all":{}}}
  • Result { "took": 5, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 0, "max_score": null, "hits": [] } }
  • 等待15s之后,再次调用上述搜索接口,便可以查询出数据
  • Request POST http://localhost:9200/foo/_doc/_search Content-Type: application/json {"query":{"match_all":{}}}
  • Result { "took": 5, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1.0, "hits": [ { "_index": "foo", "_type": "_doc", "_id": "1", "_score": 1.0, "_source": { "foo": "test" } } ] } }
  • 除了上述需要等待15s,才可以搜索到结果,也可以执行Refresh API来显式执行refresh
  • Request POST http://localhost:9200/foo/_refresh Content-Type: application/json
  • Result { "_shards": { "total": 10, "successful": 5, "failed": 0 } } ⚠️Refresh是资源密集型的。为了确保良好的集群性能,建议等待Elasticsearch的定期刷新,而不要尽可能执行显式刷新。

?refresh接口调用方式

Index、Update、Delete和Bulk api支持将refresh设置为控制该请求所做的更改何时对搜索可见。基于上面foo的index设置的refresh_interval为15s。按照下面的操作,来了解?refresh接口调用的作用。

  • foo的index存入一条数据
  • Request POST {{host}}/foo/_doc/2?refresh=true Content-Type: application/json {"foo":"test1"}
  • Result { "_index": "foo", "_type": "_doc", "_id": "2", "_version": 1, "result": "updated", "forced_refresh": true, "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }
  • 搜索foo的index的文档数据
  • Request POST {{host}}/foo/_doc/_search Content-Type: application/json {"query":{"match_all":{}}}
  • Result {"took":5,"timed_out":false,"_shards":{"total":5,"successful":5,"skipped":0,"failed":0},"hits":{"total":2,"max_score":1.0,"hits":[{"_index":"foo","_type":"_doc","_id":"2","_score":1.0,"_source":{"foo":"test1"}},{"_index":"foo","_type":"_doc","_id":"1","_score":1.0,"_source":{"foo":"test"}}]}}

​ 此时可以立刻搜索到上面写入的{"foo":"test1"}的数据

  • refresh参数的说明 Refresh赋值参数 说明 空字符串或者true 在操作发生后立即刷新相关的主和复制碎片(而不是整个索引),以便更新的文档立即出现在搜索结果中。⚠️只有在仔细考虑和验证它不会导致性能不佳后才能做到这一点,无论是从索引还是从搜索的角度。 wait_for 在响应之前,等待刷新使请求所做的更改可见。这不会强制立即刷新,而是等待刷新发生。Elasticsearch会自动刷新已经更改了每个索引的碎片。refresh_interval默认为1秒。这种设置是动态的。调用Refresh API或在任何支持它的API上将Refresh设置为true也会导致刷新,进而导致已经运行的Refresh =wait_for请求返回。 false(默认) 不采取刷新相关操作。此请求所做的更改将在请求返回后某个时刻可见。
  • ❓选择要使用哪个设置的依据
  • 除非您有充分的理由等待更改变得可见,否则始终使用refresh=false(默认设置)。最简单和最快的选择是从URL中省略refresh参数。
  • 如果你绝对必须让请求所做的更改与请求同步可见,则必须在为Elasticsearch加载更多(true)和等待响应(wait_for)之间做出选择。以下是该决定的几点:
    • true相比,wait_for对索引做的更改越多。在每个索引只更改一次索引的情况下,refresh_interval这样就不会节省任何工作。
    • true创建效率较低的索引结构(小段),这些结构以后必须合并到效率更高的索引结构(较大的段)。这意味着true的成本是在索引时间上会创建小段,在搜索时间上会搜索小段,在合并时为创建较大的段。
    • 永远不要在一行中启动多个refresh=wait_for请求。取而代之的是将它们批处理成单个的批量请求使用refresh=wait_for,此时Elasticsearch将并行开始它们,只有当它们全部完成时才返回。
    • 如果刷新间隔设置为-1,禁用自动刷新,请求refresh=wait_for将无限期等待,直到某个动作导致刷新。相反,设置索引配置refresh_interval小于默认值时,比如200ms,会使refresh=wait_for返回更快,但它仍然会产生低效的段。
    • refresh=wait_for只影响正在执行的请求,但是,通过强制执行refresh,refresh=true将影响其他正在进行的请求。一般来说,如果你有一个运行中的系统你不希望打扰,那么refresh=wait_for则会最优解法。但是需要保证refresh_interval不是被设置为-1。
  • refresh=wait_for可以强制刷新吗? 如果在已经有索引的情况下出现refresh=wait_for请求。max_refresh_listeners(默认为1000)请求等待该分片上的刷新,那么该请求的行为就像将refresh设置为true一样:它将强制刷新。这保证了当refresh=wait_for请求返回时,它的更改可以被搜索,同时防止阻塞请求未检查的资源使用。如果请求因为耗尽侦听器槽而强制刷新,那么它的响应将包含”forced_refresh”: true。

解决方案

  • 由于我使用了JestClient来用于与Elastcisearch的交互,而该开源框架对?refresh参数设置仅支持true或者false,则我在在必要这种实时查询的场景下,对index写入数据的时候,设置?refresh=true的参数

参考

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注