Elasticsearch(一)概述与架构

 第1章 Elasticsearch概述

Elasticsearch 是什么?
The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。

Elaticsearch,简称为 ES, ES 是一个开源的高扩展的分布式全文搜索引擎, 是整个 ElasticStack 技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。

elastic
英 [ɪˈlæstɪk] 美 [ɪˈlæstɪk]
n. 橡皮圈(或带);松紧带
adj. 橡皮圈(或带)的;有弹性的;有弹力的;灵活的;可改变的;可伸缩的

全文搜索引擎
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。

一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。

基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:

  • 搜索的数据对象是大量的非结构化的文本数据。
  • 文件记录量达到数十万或数百万个甚至更多。
  • 支持大量基于交互式文本的查询。
  • 需求非常灵活的全文搜索查询。
  • 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
  • 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎 。
  • 这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

Elasticsearch 应用案例

  • GitHub: 2013 年初,抛弃了 Solr,采取 Elasticsearch 来做 PB 级的搜索。 “GitHub 使用Elasticsearch 搜索 20TB 的数据,包括 13 亿文件和 1300 亿行代码”。
  • 维基百科:启动以 Elasticsearch 为基础的核心搜索架构
  • 百度:目前广泛使用 Elasticsearch 作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部 20 多个业务线(包括云分析、网盟、预测、文库、直达号、钱包、 风控等),单集群最大 100 台机器, 200 个 ES 节点,每天导入 30TB+数据。
  • 新浪:使用 Elasticsearch 分析处理 32 亿条实时日志。
  • 阿里:使用 Elasticsearch 构建日志采集和分析体系。
  • Stack Overflow:解决 Bug 问题的网站,全英文,编程人员交流的网站。

官方网址

官方文档

Elasticsearch 7.8.0下载页面

正排索引(传统)

id content
1001 my name is zhang san
1002 my name is li si

倒排索引

keyword id
name 1001, 1002
zhang 1001

Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。 为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比

ES 里的 Index 可以看做一个库,而 Types 相当于表, Documents 则相当于表的行。这里 Types 的概念已经被逐渐弱化, Elasticsearch 6.X 中,一个 index 下已经只能包含一个type, Elasticsearch 7.X 中, Type 的概念已经被删除了

Elasticsearch一般性架构

首先,当我们对记录进行修改时,es会把数据同时写到内存缓存区和translog中。而这个时候数据是不能被搜索到的,只有数据形成了segmentFile,才会被搜索到。默认情况下,es每隔一秒钟执行一次refresh,可以通过参数index.refresh_interval来修改这个刷新间隔或者搜索时加上?refresh=wait_for强制刷新,但是会造成刷新频次过高会造成性能下降,表示如果1秒内有请求立即更新并可见,执行refresh主要做三件事:

1、所有在内存缓冲区中的文档被写入到一个新的segment中,但是没有调用fsync,因此内存中的数据可能丢失;

2、segment被打开使得里面的文档能够被搜索到;

3、清空内存缓冲区;

translog的相当于事务日志,记录着所有对Elasticsearch的操作记录,也是对Elasticsearch的一种备份。因为并不是写到segment就表示数据落到磁盘了,实际上segment是存储在系统缓存(page cache)中的,只有达到一个周期或者数据量达到一定值,才会flush到磁盘上。这个时候如果系统内存中的segment丢失,是可以通过translog来恢复的。这个flush过程主要做了三件事:

1、往磁盘里写入commit point信息。

2、文件系统中的segment,fsync到磁盘。

3、清空translog文件。

translog可以保证缓存中的segment的恢复,但translog也不是实时也磁盘的,也就是说,内存中的translog丢了的话,也会有丢失数据的可能。所以translog也要进行flush。translog的flush主要有三个条件:

1、可以设置是否在某些操作之后进行强制flush,比如索引的删除或批量请求之后。

2、translog大小超过512mb或者超过三十分钟会强制对segment进行flush,随后会强制对translog进行flush,这种情况缓存中的translog在flush之后会被清空。

3、默认5s,会强制对translog进行flush。最小值可配置100ms。

6.3版本显示保留translog文件的最长持续时间。默认为12h。

参考官网:Elasticsearch Guide | Elastic

refresh,flush 和fsync的区别

1.refresh是将缓冲队列buffer里数据刷入文件缓冲系统生成索引文件segement,该segement数据才能被查询到,保证查询属性可见

2.flush是将索引文件segement数据持久化到硬盘(触发机制是translog文件超过512mb或者30分钟强强制刷新segement)

3.fsyncd是将translog 持久化到硬盘(每5秒执行一次) 写入translog其实也是在内存中。translog 和segement持久化到硬盘是两回事。

持久化的translog文件中存的是所有索引成segement的数据但还未持久化到硬盘的内容,一旦segement持久化到硬盘translog会清空。

参考:https://elasticsearch.cn/question/3847

索引存储方式

Elasticsearch是一个建立在全文搜索引擎库Apache Lucene 基础上的分布式搜索引擎,Lucene最早的版本是2000年发布的,距今已经18年,是当今最先进,最高效的全功能开源搜索引擎框架。

Lucene

Lucene中包含了四种基本数据类型,分别是:

  • Index:索引,由很多的Document组成。
  • Document:由很多的Field组成,是Index和Search的最小单位。
  • Field:由很多的term组成,包括field_name和field_value。
  • Term:由很多的字节组成,可以分词,分词之后每个词即为一个term。term是索引的最小单元。

上述四种类型在Elasticsearch中同样存在,意思也一样。

Lucene中存储的索引主要分为三种类型:

  • Invert Index,即倒排索引。通过term可以快速查找到包含该term的doc_id。如果Field配置分词,则分词后的每个term都会进入倒排索引,如果Field不指定分词,那该Field的value值则会作为一个term进入倒排。(这里需要注意的是term的长度是有限制的,如果对一个Field不采取分词,那么不建议该Field存储过长的值。关于term超长处理)
  • DocValues,即正排索引。采用的是类似数据库的列式存储。对于一些特殊需求的字段可以选择这种索引方式。
  • Store,即原文。存储整个完整Document的原始信息。

倒排索引是lucene的核心索引类型,采用链表的数据结构,倒排索引中的key就是一个term,value就是以doc_id形成的链表结构。

Term      Doc_1      Doc_2
-------------------------
Quick      |               |  X
The         |   X          |
brown     |   X          |  X
dog         |   X          |
dogs       |                |  X
fox          |   X          |
foxes      |                |  X
in            |                |  X
jumped  |   X          |    
lazy        |   X          |  X
leap        |                |  X
over        |   X          |  X
quick      |   X          |
summer  |               |  X
the          |   X          |
------------------------

现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:

Term      Doc_1      Doc_2
-------------------------
brown   |   X          |  X
quick    |   X           |
------------------------
Total     |   2           |  1

这里分别匹配到了doc1和doc2,但是doc1匹配度要高于doc2。

倒排索引中的value有四种存储类型:

  • DOCS:只存储doc_id。
  • DOCS_AND_FREQS:存储doc_id和词频(Term Freq)。
  • DOCS_AND_FREQS_AND_POSITIONS:存储doc_id、词频(Term Freq)和位置。
  • DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:存储doc_id、词频(Term Freq)、位置和偏移(offset)。

DocValues

正排索引类似关系型数据库的存储模式,作用是通过doc_id和field_name可以快速定位到指定doc的特定字段值。DocValues的key是doc_id+field_name,value是field_value。

ES默认会对所有字段进行正排索引,但并不是所有字段都需要DocValues。所以合理配置DocValues可以节省存储空间。DocValues的使用场景一般是:需要针对某个Field排序、聚合、过滤和script。

Store

存储的是Document的完整信息,包括所有field_name和field_value。Store的key是doc_id,value是field_name+field_value。对于上诉中需要聚合和排序的Field并没有开启DocValues的情况,依然可以实现排序和聚合,会从Store中获取要排序聚合的字段值。

Elasticsearch在Lucene基础上的改变

Lucene本身不支持分布式,Elasticsearch通过_routing实现分布式的架构。我们可以通过_routing来实现不同doc分布在不同的Shard上,我们可以自己定义也可以系统自动分配。对于指定_routing的插入和查询,性能上会更好。

Lucene中没有主键索引,并且id是在Segment中唯一。那么Elasticsearch是如何实现doc_id唯一?Elasticsearch中有个系统字段_id,来决定doc唯一。_id是在用户可见层度上的id值,实际上Elasticsearch内部会把_id存储成_uid(_uid =index_type + '#' + _id)。_uid只会存储倒排和原文,目的就是为了通过id可以快速索引到doc。这里需要注意的是,在Elasticsearch6.x版本以后,一个index只支持一个type,这也就意味着_id和_uid概念几乎相同。以后的版本中,Elasticsearch会取消type。

Elasticsearch通过_version字段来保证文档的一致性。更多关于文档的一致性和锁机制的参考:ElasticSearch干货(一):锁机制

Elasticsearch通过_source字段来存储doc原文。这个字段非常重要。Lucene的update是覆盖,是不支持针对doc中特定字段进行修改的。但Elasticsearch支持对特定字段的修改,就是基于_source字段实现的。关于Elasticsearch的update详细内容,参考:ElasticSearch干货(二):index、create、update区别

Elasticsearch通过_field_names字段来判断doc中是否存在某个字段。_field_names的存储形式为倒排,可以快速判断出是否包含某个field_name。

Lucene中Segment一旦创建不可修改。那么Elasticsearch如何实现实时修改并索引数据的呢?详细参考:ElasticSearch原理(三):写入流程

关于原理和索引先介绍到这里,主要这里还是聚焦与如何实现几个关键需求?

ES延时问题

默认情况下,es每隔一秒钟执行一次refresh,可以通过参数index.refresh_interval来修改这个刷新间隔或者搜索时加上?refresh=wait_for强制刷新,但是会造成刷新频次过高会造成性能下降,表示如果1秒内有请求立即更新并可见。另外由于没有生成segment,也就是说不能通过索引来获取,但是可以通过直接get by id来获取单条记录。注意:在并发写入的时候,特别是updateByQuery的时候,很容易出现复写或者是丢失的情况,坑很多,所以最好要频繁地去更新同一个索引的同一条数据。

搜索(同时实现精确查询和模糊查询和组合查询)

ES的搜索是分2个阶段进行的,即Query阶段和Fetch阶段。  Query阶段比较轻量级,通过查询倒排索引,获取满足查询结果的文档ID列表。  而Fetch阶段比较重,需要将每个shard的结果取回,在协调结点进行全局排序。  通过From+size这种方式分批获取数据的时候,随着from加大,需要全局排序并丢弃的结果数量随之上升,性能越来越差。

1、精确查询

在elasticsearch 中输入查询条件,一般会匹配到很多结果,是因为analyzer的存在:

Each element in the result represents a single term:

{
   "tokens": [
      {
         "token":        "text",
         "start_offset": 0,
         "end_offset":   4,
         "type":         "",
         "position":     1
      },
      {
         "token":        "to",
         "start_offset": 5,
         "end_offset":   7,
         "type":         "",
         "position":     2
      },
      {
         "token":        "analyze",
         "start_offset": 8,
         "end_offset":   15,
         "type":         "",
         "position":     3
      }
   ]
}

必须要将字段设置为not_analyzed 才可以,如下:

PUT /my_store { "mappings" : { "products" : { "properties" : { "productID" : { "type" : "string", "index" : "not_analyzed" } } } } }

2、es组合多个条件进行查询

1、must、should

GET /test_index/_search
{
  "query": {
    "bool": {
      "must": { "match": { "name": "tom" }},
      "should": [
        { "match": { "hired": true }},
        { "bool": {
          "must": { "match": { "personality": "good" }},
          "must_not": { "match": { "rude": true }}
        }}
      ],
      "minimum_should_match": 1
    }
  }
}

在es中,使用组合条件查询是其作为搜索引擎检索数据的一个强大之处,在前几篇中,简单演示了es的查询语法,但基本的增删改查功能并不能很好的满足复杂的查询场景,比如说我们期望像mysql那样做到拼接复杂的条件进行查询该如何做呢?es中有一种语法叫bool,通过在bool里面拼接es特定的语法可以做到大部分场景下复杂条件的拼接查询,也叫复合查询

首先简单介绍es中常用的组合查询用到的关键词,

filter:过滤,不参与打分
must:如果有多个条件,这些条件都必须满足 and与
should:如果有多个条件,满足一个或多个即可 or或
must_not:和must相反,必须都不满足条件才可以匹配到 !非

发生 描述
must
该条款(查询)必须出现在匹配的文件,并将有助于得分。

filter
子句(查询)必须出现在匹配的文档中。然而不像 must查询的分数将被忽略。Filter子句在过滤器上下文中执行,这意味着评分被忽略,子句被考虑用于高速缓存。

should
子句(查询)应该出现在匹配的文档中。如果 bool查询位于查询上下文中并且具有mustor filter子句,则bool即使没有should查询匹配,文档也将匹配该查询 。在这种情况下,这些条款仅用于影响分数。如果bool查询是过滤器上下文 或者两者都不存在,must或者filter至少有一个should查询必须与文档相匹配才能与bool查询匹配。这种行为可以通过设置minimum_should_match参数来显式控制 。

must_not
子句(查询)不能出现在匹配的文档中。子句在过滤器上下文中执行,意味着评分被忽略,子句被考虑用于高速缓存。因为计分被忽略,0所有文件的分数被返回。

3、模糊查询

前缀查询:匹配包含具有指定前缀的项(not analyzed)的字段的文档。前缀查询对应 Lucene 的 PrefixQuery 。

案例
GET /_search
{ "query": {
    "prefix" : { "user" :  { "value" : "ki", "boost" : 2.0 } }
  }
}

正则表达式查询:egexp (正则表达式)查询允许您使用正则表达式进行项查询。有关支持的正则表达式语言的详细信息,请参阅正则表达式语法。第一个句子中的 “项查询” 意味着 Elasticsearch 会将正则表达式应用于由该字段生成的项,而不是字段的原始文本。注意: regexp (正则表达式)查询的性能很大程度上取决于所选的正则表达式。匹配一切像 “.*” ,是非常慢的,使用回顾正则表达式也是如此。如果可能,您应该尝试在正则表达式开始之前使用长前缀。通配符匹配器 “.*?+” 将主要降低性能。

案例
GET /_search
{
    "query": {
        "regexp":{
            "name.first":{
                "value":"s.*y",
                "boost":1.2
            }
        }
    }
}
‘

通配符查询:匹配与通配符表达式具有匹配字段的文档(not analyzed)。支持的通配符是 “*”,它匹配任何字符序列(包括空字符);还有 “?”,它匹配任何单个字符。请注意,此查询可能很慢,因为它需要迭代多个项。为了防止极慢的通配符查询,通配符项不应以通配符 “*” 或 “?” 开头。通配符查询对应 Lucene 的 WildcardQuery 

案例
GET /_search
{
    "query": {
        "wildcard" : { "user" : { "value" : "ki*y", "boost" : 2.0 } }
    }
}

###模糊查询数据量越大效率越低,当查询内容较多,数据量较大时建议将该字段设置成text进行分词,然后通过match进行匹配。

排序与相关性

默认情况下,返回的结果是按照 相关性 进行排序的——最相关的文档排在最前。 在本章的后面部分,我们会解释 相关性 意味着什么以及它是如何计算的, 不过让我们首先看看 sort 参数以及如何使用它。

1、排序

为了按照相关性来排序,需要将相关性表示为一个数值。在 Elasticsearch 中, 相关性得分 由一个浮点数进行表示,并在搜索结果中通过 _score 参数返回, 默认排序是 _score 降序。

有时,相关性评分对你来说并没有意义。例如,下面的查询返回所有 user_id 字段包含 1 的结果:

GET /_search
{
    "query" : {
        "bool" : {
            "filter" : {
                "term" : {
                    "user_id" : 1
                }
            }
        }
    }
}

里没有一个有意义的分数:因为我们使用的是 filter (过滤),这表明我们只希望获取匹配 user_id: 1 的文档,并没有试图确定这些文档的相关性。 实际上文档将按照随机顺序返回,并且每个文档都会评为零分。

1.1、按照字段的值排序

在这个案例中,通过时间来对 tweets 进行排序是有意义的,最新的 tweets 排在最前。 我们可以使用 sort 参数进行实现:

GET /_search
{
    "query" : {
        "bool" : {
            "filter" : { "term" : { "user_id" : 1 }}
        }
    },
    "sort": { "date": { "order": "desc" }}
}

1.2、多级排序

假定我们想要结合使用 date 和 _score 进行查询,并且匹配的结果首先按照日期排序,然后按照相关性排序:

GET /_search
{
    "query" : {
        "bool" : {
            "must":   { "match": { "tweet": "manage text search" }},
            "filter" : { "term" : { "user_id" : 2 }}
        }
    },
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

排序条件的顺序是很重要的。结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推。

多级排序并不一定包含 _score 。你可以根据一些不同的字段进行排序, 如地理距离或是脚本计算的特定值。

1.3、字段多值的排序

一种情形是字段有多个值的排序, 需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢?

对于数字或日期,你可以将多值字段减为单值,这可以通过使用 min 、 max 、 avg 或是 sum 排序模式 。 例如你可以按照每个 date 字段中的最早日期进行排序,通过以下方法:

"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}
  • 更多详情清参考此文;

如何在elasticsearch里面使用分页功能

from + size 浅分页

"浅"分页可以理解为简单意义上的分页。它的原理很简单,就是查询前20条数据,然后截断前10条,只返回10-20的数据。这样其实白白浪费了前10条的查询。

GET test_dev/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "age": 28
          }
        }
      ]
    }
  },
  "size": 10,
  "from": 20,
  "sort": [
    {
      "timestamp": {
        "order": "desc"
      },
      "_id": {
        "order": "desc"
      }
    }
  ]
}

其中,from定义了目标数据的偏移值,size定义当前返回的数目。默认from为0,size为10,即所有的查询默认仅仅返回前10条数据。

在这里有必要了解一下from/size的原理:
因为es是基于分片的,假设有5个分片,from=100,size=10。则会根据排序规则从5个分片中各取回100条数据数据,然后汇总成500条数据后选择最后面的10条数据。

做过测试,越往后的分页,执行的效率越低。总体上会随着from的增加,消耗时间也会增加。而且数据量越大,就越明显!

es默认的from+size的分页方式返回的结果数据集不能超过1万点,超过之后返回的数据越多性能就越低;

这是因为es要计算相似度排名,需要排序整个整个结果集,假设我们有一个index它有5个shard,现在要读取1000到1010之间的这10条数据,es内部会在每个shard上读取1010条数据,然后返回给计算节点,这里有朋友可能问为啥不是10条数据而是1010条呢?这是因为某个shard上的10条数据,可能还没有另一个shard上top10之后的数据相似度高,所以必须全部返回,然后在计算节点上,重新对5050条数据进行全局排序,最后在选取top 10出来,这里面排序是非常耗时的,所以这个数量其实是指数级增长的,到后面分页数量越多性能就越下降的厉害,而且大量的数据排序会占用jvm的内存,很有可能就OOM了,这也是为什么es默认不允许读取超过1万条数据的原因。 

scroll 深分页

from+size查询在10000-50000条数据(1000到5000页)以内的时候还是可以的,但是如果数据过多的话,就会出现深分页问题。

为了解决上面的问题,elasticsearch提出了一个scroll滚动的方式。
scroll 类似于sql中的cursor,使用scroll,每次只能获取一页的内容,然后会返回一个scroll_id。根据返回的这个scroll_id可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景。

GET test_dev/_search?scroll=5m
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "age": 28
          }
        }
      ]
    }
  },
  "size": 10,
  "from": 0,
  "sort": [
    {
      "timestamp": {
        "order": "desc"
      },
      "_id": {
        "order": "desc"
      }
    }
  ]
}
  1. scroll=5m表示设置scroll_id保留5分钟可用。
  2. 使用scroll必须要将from设置为0。
  3. size决定后面每次调用_search搜索返回的数量

然后我们可以通过数据返回的_scroll_id读取下一页内容,每次请求将会读取下10条数据,直到数据读取完毕或者scroll_id保留时间截止:

GET _search/scroll
{
  "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAJZ9Fnk1d......",
  "scroll": "5m"
}

注意:请求的接口不再使用索引名了,而是 _search/scroll,其中GET和POST方法都可以使用。

scroll删除
根据官方文档的说法,scroll的搜索上下文会在scroll的保留时间截止后自动清除,但是我们知道scroll是非常消耗资源的,所以一个建议就是当不需要了scroll数据的时候,尽可能快的把scroll_id显式删除掉。

清除指定的scroll_id:

DELETE _search/scroll/DnF1ZXJ5VGhlbkZldGNo.....

清除所有的scroll:

DELETE _search/scroll/_all

search_after 深分页

scroll 的方式,官方的建议不用于实时的请求(一般用于数据导出),因为每一个 scroll_id 不仅会占用大量的资源,而且会生成历史快照,对于数据的变更不会反映到快照上。

search_after 分页的方式是根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。但是需要注意,因为每一页的数据依赖于上一页最后一条数据,所以无法跳页请求。

为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,其实使用业务层的 id 也可以。

GET test_dev/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "age": 28
          }
        }
      ]
    }
  },
  "size": 20,
  "from": 0,
  "sort": [
    {
      "timestamp": {
        "order": "desc"
      },
      "_id": {
        "order": "desc"
      }
    }
  ]
}
  1. 使用search_after必须要设置from=0。
  2. 这里我使用timestamp和_id作为唯一值排序。
  3. 我们在返回的最后一条数据里拿到sort属性的值传入到search_after。

使用sort返回的值搜索下一页:

GET test_dev/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "age": 28
          }
        }
      ]
    }
  },
  "size": 10,
  "from": 0,
  "search_after": [
    1541495312521,
    "d0xH6GYBBtbwbQSP0j1A"
  ],
  "sort": [
    {
      "timestamp": {
        "order": "desc"
      },
      "_id": {
        "order": "desc"
      }
    }
  ]
}
        

只是整合记录少有原创

你可能感兴趣的:(记录,日常编程,elasticsearch,架构,搜索引擎)