elasticsearch——海量文档高性能索引系统

Elasticsearch

Elasticsearch是一个高性能高扩展性的分布式索引系统,基于apache lucene。官方指导文档

可结合kibana工具进行可视化。

概念:

  • index 索引: 类似SQL中的一张表,索引名必须是全小写单词。
  • type(索引类型):设计初衷是用type对相同逻辑结构(字段名)数据的归并,一个index中只能有一种 type,在6.0版本之后被标记为过时(deprecated),在后续大版本(7.x, 8.x+)中会将被完全弃用。
  • document 文档:若干个键值对的数据,类似SQL的一行记录。
  • node 节点:相当于一台部署了一个ES服务的机器
  • shards/replicas 分片:将一个索引切分成多个功能完整独立的小片,叫做分片。
  • cluster: 一个cluster是多个节点的集合,ES支持多个cluster,以cluster名字区别。

检索 Query/Search/Retrieval

ES的CRUD可通过kibana工具包中的Dev Tools进行。

使用http请求实现索引查询,search操作请求基本格式为GET /<index-name>/_search

一个简单查询,无条件查文档的请求如下:

GET /myindex/_search
{
    "query": {
        "match_all": {}
    }
}

一个简单查询(如无条件查文档)返回的形式如下:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex",
        "_type" : "_doc",
        "_id" : "-31ddGgBVZ-2SafgL7-y",
        "_score" : 1.0,
        "_source" : {
          "name" : "Jack Ma3"
        }
      }
    ]
  }
}

其中字段意义如下:

  • took 查询耗时
  • time_out 查询是否超时
  • _shards 查询在分片上执行的结果状态
  • hits 查询结果
  • hits.total 匹配的总文档数
  • hits.max_score 结果文档的最高分值
  • hits.hits 匹配上的文档结果集合
  • hits.hits._index 该文档所在索引
  • hits.hits._type 该文档所在类型
  • hits.hits._id 文档id
  • hits.hits._score 该文档与查询条件的匹配分值
  • hits.hits._source 文档字段键值数据

查询请求一般格式首先是GET的HTTP方法,请求地址形式为“//_search”,http请求携带的数据为一个json,包括"query"部分,该部分是查询条件限定文档条数通过指定"size"实现。在分页场景中设置查询起始位置通过指定"from"实现。可通过设置"sort"指定排序条件。如:

GET /myindex/_search
{
    "query": {"match_all":{}},
    "from": 100,
    "size": 10,
    "sort":"age"
}

查询条件部分,"query",其内容为一个json对象,键是查询谓词或筛选谓词(filter),值是对应内容,一般是json对象。查询谓词包括布尔逻辑操作谓词、文档匹配谓词,前者对应布尔逻辑的与或非等,后者用于判断一个文档是否满足某种要求(并计算匹配程度)。

一个谓词存在于查询上下文或筛选上下文情况下,查询上下的谓词是要解决如何匹配以及匹配得怎么样(匹配程度)问题,而筛选上下文的谓词只需解决是否匹配问题,该上下文的谓词只需回答“是”或“否”,不计算匹配程度(分值)。

full text查询,指谓词查询在全文(text类型)字段上进行,查询时可指定分析器。

term级别查询,指谓词针对一个term做匹配。

谓词是查询条件json对象的键,其值一般是json对象(或者对象数组),值内容json对象的键是谓词,从json对象顶层深入往里看,查询条件类似前缀表达式,首先看到的是谓词(operator),然后才看到谓词对应的操作数(operand)。
例:

#下列内容中“#”后内容表示注释,尽管json中没有注释的概念
{
    "query": {
        "bool": {       #query下允许bool,表示一个bool条件,内容是json对象,键名是谓词,多个键是"and"关系
            "must": [       #must表达必须包含给定的若干个条件,类似“与 and”
                {
                    "match": {  #表示下面的json对象表达一个文档字段匹配条件
                        "age": 50   #表示字段“age”值为50的文档
                    }
                },
                {
                    "match": {"gender": "male"}
                }
            ],
            "must_not": {   #must_not必须不匹配,可是多个条件(数组)
                "match": {"name": "Jack Ma"}
            }
        }
    }
}

有以下查询/筛选谓词:

  • match_all 无条件,匹配所有文档。
  • match_none 不匹配任何文档
  • ids 根据若干id查询文档。
  • match 值内容是json对象,键是文档字段,值是目标匹配值或者带选项的值(json对象,目标值在"value"字段)。该种查询将会对字段值进行分析。
  • multi-match 多个字段上的match
  • match_phrase 整串(短语 phrase)匹配(匹配连续的term)
  • match_phrase_prefix 整串作为前缀匹配
  • term 非分析性查询。精确匹配一个项
  • terms 类似term,匹配给定数组中任意一个值
  • terms_set
  • range 范围查询。
  • prefix 非分析性查询。查询字段的项是否具有给定前缀。
  • wilcard 非分析性查询。支持通配符*?的查询。查询将遍历每个项,故应避免通配符打头的查询。
  • regexp 正则匹配。是在字段的项(terms)上的匹配,而非原文本。
  • fuzzy 根据给定值对字段的项进行模糊查询,以编辑距离度量模糊性。
  • bool 表明是一个布尔条件,其下是若干谓词组成的复杂条件
  • must 必须匹配,一般对多个条件使用(数组),类似“与 AND”
  • must_not 不匹配,相当于"非 NOT",可以对多个条件(数组)使用
  • should 语义为“应该”匹配,“逻辑或 OR”与之接近(可指定子条件的至少匹配个数),一般对若干条件使用(或),表示匹配多个条件中的若干个条件,一般指至少匹配1个,通过与should平行层级的键minimum_should_match指定should中需要至少匹配的条件数量。在某些情况默认至少匹配1个。
  • exists 字段值存在且不为null,且不为空数组[](如果是多值字段)。用mus_not取反查询时,指查询字段值为null或为[](不能查询字段缺失情况)。ES 5+之后没有missing谓词,不能查询字段缺失情况。
  • script 脚本。以脚本方式自定义查询。
  • nestednested类型字段的查询。
  • has_parent 用于有join关系的数据,使得在筛选目标文档时要求其具有某种父文档。
  • has_child 用于有join关系的数据,使得在筛选目标文档时要求其具有某种子文档。

谓词值是json对象时,其多个键名(子谓词)之间的关系基本是“与 and”关系。与"should"(平行层级)共存时,需小心"should"能匹配的条件数以及条件至少匹配数minimum_should_match的设置。

项 termterm用于精确匹配倒排索引中的一个项(term)。也可用在范围类型数据(range data types)上。

GET _search
{
    "query":{
        "term": {
            "field1": "value1"      #值不可为null
        }
    }
}

GET _search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "field1": {
              "value": "value1",
              "boost": 2.0        #相比boost:1.0的条件重要两倍
            }
          }
        },
        {
          "term": {
            "field2": "value2"
          }
        }
      ]
    }
  }
}

如,字段“content”的类型配置为text,现有一文档,其该字段的值为“hello es”,在入库该文档时,由于字段类型是text,分词器会将其分为"hello"和"es"两个项(term)存入倒排索引。在用term查询时,如果查询字段"content"上的值"hello"是能检索到该文档的(或查询值为"es"也可检索到),但利用term查询字段"content"上值为"hello es"的文档,则不能命中上述文档,因为在倒排索引中没有"hello es"的项。

多项 termsterms

GET _search
{
    "query": {
        "terms": {
            "my-field": ["val1", "val2"],  #“或”关系,匹配其中之一
        }
    }
}

多项 terms_set: terms_setterms,此外额外支持参数minimum_should_match_field/minimum_should_match_script以匹配多个值中的指定个数。

GET _search
{
    "query":{
        "terms_set": {
            "my-field": ["value1","value2",...],
            "minimum_should_match_field":"<another_field>"  #引用文档其他整型字段的值来指定terms中需要匹配的term的个数
            "minimum_should_match_script": "或者通过脚本提供需匹配个数"
        }
    }
}

范围查询 rangerange,不等式比较符 gt,gte,lt,lte

GET _search
{
    "query":{
        "range":{
            "my_field": {
                "gt": ...   # 大于
                "lte": ...  # 小于等于。值可为null,甚至区间起止两个参都可为null,
                            #*但是*,只要字段在查询条件中存在谓词,则隐含前置条件:该字段存在,除了谓词为exists。所以,
                            #都为null时,等价于字段存在,而不是等价于压根儿没有在这个字段上做range(其包含了字段不存在的文档)。
            }
        }
    }
}

匹配 matchmatch会用分析器进行分词。

GET /_search

{
    "query": {
        "match": {
            "my_field":"my_value"
        }
    }
}

多字段匹配 multi-matchmulti-match多字段上的match,字段名可用模式匹配(通配符),可指定字段的重要性倍数。

GET _search
{
    "query": {
        "multi-match": {
            "query": "query string",
            "fields": ["field1","first_name","last_name"],
            "fields": ["field1", "*_name"],                 #字段名模式匹配(通配符)
            "fields": ["first_name^5", "last_name"]         #字段first_name上具有5倍重要性
            "type": "best_fields",      # best_fields, most_fields, cross_fields, phrase, phrase_prefix
            "operator": "and",          # and, or
        }
    }
}

多词整串匹配 match_phrasematch_phrasetext类型字段上匹配一个整串,让服务器不要对参数值进行分析(非分析性字段),或者说连续匹配terms(分析性字段), slop参数。

GET _search
{
    "query":{
        "match_phrase": {
            "my_field": "match the whole phrase"
        }
    }
}

前缀匹配 prefixprefix term级别的查询,匹配一个term是否具有查询参数那样的前缀。

GET _search
{
    "query":{
        "prefix": {
            "my_field": "my_prefix"
        }
    }
}

(分析性字段上的)整串前缀匹配 match_phrase_prefixmatch_phrase_prefix 在分析性字段上做整串前缀匹配。

GET _search
{
    "query":{
        "match_phrase_prefix": {
            "url": "http://localhost"       #假设中库中有字段url类型为text,该查询将匹配该字段以此值开头的文档。因为类型为text,故此处不能用谓词prefix,
        }
    }
}

完全匹配 perfect match:只能在非分析性单值字段(mapping中设置该字段的"index":"not_analyzed")上用做,字段类型为keyword。如何在一个类似文本全文这种分析性字段(或非分析性多值字段)上既可以做match等检索,又可以做完全匹配? <==不可以。要实现这样的需求,一般要另建一个字段用于完全匹配功能,类型为非分析性单值keyword类型字段,完全匹配功能由在此字段上的term查询实现。

通配符 wildcardwildcard

正则匹配 regexpregexp,term级别查询。

GET _search
{
    "query":{
        "regexp":{
            "my_field":{
                "value":"reg-expression",
                "flags": "FLAG1|FLAG2",     #正则特性 ALL (default), ANYSTRING, COMPLEMENT, EMPTY, INTERSECTION, INTERVAL, or NONE.
                "boost":"2.0",  #权重
            }
        }
    }
}

联合查询 Joining Queriesnested, has_parent, has_child, parent_id

嵌套 nestednested用于nested类型字段的查询。这只是一个查询谓词,查询语句中即是仅有nested一个查询谓词(即仅想查嵌套字段),返回结果并不是仅返回nested字段数据中被命中的那些,而该字段的所有元素都会返回。若指定inner_hits参数,则在返回结果的inner_hits部分将返回嵌套字段中仅命中的那些元素。

GET _search
{
    "query":{
        "nested":{
            "path":"my-nested-field",   #指定nested类型字段的名字
            "query":{
                ...         #查询谓词,如term, bool等,需注意其中的字段名必须是全路径的,即使看起来这里已经有了"path"这个嵌套字段名作为上下文了
            },
            "inner_hits":{
                "from": 0,      # from,size只是限制每篇文档内嵌套字段中命中元素于本文档内的偏移和数量,*并非*是将所有嵌套字段数据拿出后在全局上的偏移和数量
                "size": 10,
                "sort": ...,
                "name":"result_name"    #返回结果是命名的,在这里设置名字
            }
        }
    }
}

#返回结果类似
{
    "hits":{
        "total": 100,   #其嵌套字段数据中有命中条件的文档的数量,并非所有命中的嵌套元素的数量,因此这个数量要比所有命中的嵌套元素的数量要小(或相等,如果每篇文档的嵌套字段中有且仅有1个元素被命中)
        "hits":[
            {
                ...,
                "_id": ...,
                "inner_hits": [
                    "result_name":{
                        "hits":{
                            "total": 2, #在本文档内命中的元素的个数
                            "hits": [
                                {}
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

具有父文档 has_parenthas_parent用于有join关系的数据,使得在筛选目标文档时要求其具有某种父文档。

GET _search
{
    "query":{
        "has_parent":{
            "parent_type":"parent_my_type"    #父文档的join类型,必须提供
            "query":{
                #查询条件
            },
            "score": true|false     #父文档的查询的匹配度是否计入子文档得分,默认false
            "ignore_unmapped": true|false   #在子文档没有对应的parent_type时是忽略这种情况还是报错,默认false(无对应时报错)。一般用于多索引查询将其设为true以明确告知系统可能需要适应索引结构不一致的情况。
        }
    }
}

依据父文档字段排序:不能将has_parent用于标准排序。依据父文档字段排序是通过以脚本函数将父文档字段值作为父文档匹配分数传递到子文档(即查询的目标文档)后以目标文档分数来排序的。

GET /_search
{
    "query": {
        "has_parent" : {
            "parent_type" : "parent_my_type",
            "score" : true,
            "query" : {
                #`function_score`作为`query`的键后,`query`不能再有其他键,如term,bool之类的。
                "function_score" : {
                    "script_score": {
                        #如,将父文档的'my_count'数值乘以子文档(目标文档)的分数作为最终分数来排序。
                        "script": "_score * doc['my_count'].value"
                    }
                }
            }
        }
    }
}

具有指定ID的父文档 parent_idparent_id用于有join关系的数据,使得在筛选目标文档时要求其具有指定ID的父文档。

#搜索子文档
GET _search
{
    "query":{
        "parent_id":{
            "type":"my-child"       #注意,是子文档(目标文档)的join类型,而不是父文档的类型
            "id": "id-of-parent-doc"
            "ignore_unmapped": false|true   #默认false
        }
    }
}

具有子文档 has_childhas_child

GET /_search
{
    "query": {
        "has_child" : {
            "type" : "child",       #子文档的join类型
            "query" : {             #对子文档的筛选条件
                "match_all" : {}
            },
            "max_children": 5,      #对子文档匹配数量的要求
            "min_children": 2,
            "score_mode" : "none"    #值可为min,max,sum,avg,none。从匹配的若干子文档中归约出分数的策略
            "ignore_unmapped": false|true   #默认false
        }
    }
}

以子文档中的数据来排序:

GET /_search
{
    "query": {
        "has_child" : {
            "type" : "child",
            "query" : {
                "function_score" : {
                    "script_score": {
                        "script": "_score * doc['my_count'].value"
                    }
                }
            },
            "score_mode" : "max"
        }
    }
}

模糊查询 fuzzyfuzzy 在字段的项(term)上模糊查询

GET _search
{
    "query":{
        "fuzzy":{
            "field1": {
                "value": "my_value",
                "fuzziness": 4      #编辑距离参数以配置模糊程度
            }
        }
    }
}

根据多个id查询文档 ids:利用_mget,索引名和类型是可选项。 GET /_mget GET /<index>/_mgetGET /<index>/_doc/_mget

GET /mget
{
    "ids":["id1", "id2"]
}

字段存在且非null existsexists

GET _search
{
    "query":{
        "exists":{
            "field":"my_field"
        }
    }
}

{
    "query":{
        "bool": {
            "filter":{
                "field":"my_field"
            }
        }
    }
}

字段存在且值非空: 用term匹配空串后取非(must_not)。

多值性(数组)相关的查询:对于多值字段,term, match, match_phrase等term级别谓词都是在一个term上(即一个数组中的每个元素上)进行的。

多值性相关的查询:

  • 长度。 对数组的长度的过滤可通过脚本实现,没有直接关于长度的查询谓词。doc['my_field'].length即表示数组长度(注意不是doc['my_field'].value.length,这是指其中的元素的长度)。

批获取 multi-get
根据多个条件一次性获取

GET /_mget      #或 /<index>/_mget  或 /<index>/_doc/_mget
{
    "docs": [   #多个条件构成的数组
        {
            "_index": "myindex"  #如果URL中没有<index>则必须提供;如果URL中有<index>,可不提供"_index",若提供则覆盖URL中<index>作为查询索引
            "_type": ""     #URL中没有<type>都不要求提供
            "_id": ""       #需提供,以匹配文档
        }
    ]
}

批量操作 Bulk API
REST API服务端点是_bulk,请求携带数据是以行分隔的json(NDJSON),即一行一json,基本形式如下:

action_and_meta_data\n
optional_source\n
action_and_meta_data\n
optional_source\n
....
action_and_meta_data\n
optional_source\n

携带行分隔json数据的请求,其http头“content-type”的值需设置为"application/x-ndjson"。

Bulk API允许的操作有“index”、“create”、“delete”、“”、“update”。

POST /_bulk
{"index": {"_index": "myindex","_type":"", "_id": ""}}   # index operation
{"field1":"val1"}  # data carried by index operation
{"create": {"_index":""}
{"field1":"val1"}
{"update": {"_index":"","_type":"","_id":""}}
{"doc": {field1":"val1"}}  #注意:update需指定是doc还是script方式,而不是直接提供文档结构数据
{"delete":{"_index":"", "_id":""}}  #delete不携带数据体

批查询_msearch

计数_count

GET /.../_count     # /_count  /<index>/_count
{
    "query": ...
}

查询时要求分数不低于阈值 min_score

GET /_search
{
    "min_score": 0.8,
    "query": ...
}

复制索引 reindex:将一个索引中的若干文档复制到另一个中,rest服务端点_reindex

POST _reindex
{
    "source":{
        "index": "myindex"  #必须。指定源索引,单个或多个(数组)
        "_source": ...      #可选。指定复制的字段,默认全部字段。
        "type": ""          #可选。单个或多个(多个时对应多个索引情况)
        "sort": ...     #排序标准,当指定size时有意义。
        "query": ...    #可选。筛选条件
        "size": ...     #scroll batch size
    },
    "dest": {
        "index": ""     #必须。指定目标索引
        "op_type": "create"   #可选参数,复制操作类型
        "version_type": ""  #可选参数,指定版本类型
    }
    "size": <number>    #可选。指定复制文档数量,默认全部。
    "script": ...       #可选。脚本
}

立即更新 ?refresh: index,update,delete等操作的影响默认在一段时候后才会被其他(查询)操作观察到,而操作是立即返回的。即类似于多线程,一个线程对数据的更新在一定延迟后才对其他线程可见。这一行为可通过?refresh参数控制,为false时(默认情况),操作影响在一定延迟后可见;为true或空时,立即对其他(查询)操作可见,当然,这需要考虑因此可能引发的性能问题;为wait_for时,等待索引的自动刷新操作时更新数据,索引刷新动作根据index.refresh_interval(默认1秒)周期性更新。

分页
指定from(默认从0开始), size(默认10条?), sort(默认升序):

GET /myindex/_search
{
    "query": {"match_all":{}},
    "from": 100,
    "size": 10,
    "sort":"created_time"
}

排序

可以在若干上字段进行排序,每个字段可设置若干参数,可在嵌套类型字段上排序,可在数组类型字段上排序。

升降序:通过参数order指定升序asc、降序desc。默认值情况:对于_score字段默认降序,其他字段默认升序。

特殊排序字段_score_doc_score以分数排序;_doc以索引系统中的序,效率最高,一般用于不关心序的场景,尤其于scroll接口。

多值(数组)字段上的元素选择:mode参数可控制从数组字段上如何选择元素来排序,其值可以是:

  • min 取最小者
  • max 取最大者
  • sum 取和,仅可用于数值型数组。
  • avg 均值。
  • median 中位数。
    默认值,在升序时为最大者(max),在降序时为最小者(min)。

嵌套(nested)字段排序
sort中定义nestednested的可用参数为:

  • path 嵌套字段的名字的全路径。(必须提供)
  • filter 筛选字段中的元素用以排序。一般来说,如果在查询部分涉及了nested查询,则这里的filter从查询部分的nested查询条件复制而来。
  • max_children 选择时选取的最多(每个顶级文档中的嵌套字段中)元素个数。
  • nested 用于嵌套中的嵌套字段(如果该嵌套字段中定义多层嵌套的字段)。
GET /_search
{
   "sort" : [
       {
          "pets.age" : {        #字段'pets'的类型被定义为`nested`
             "mode" :  "avg",
             "order" : "asc",
             "nested": {
                "path": "pets",
                "filter": {
                   "term" : { "pets.family" : "cat" }
                }
             }
          }
       }
    ]
}

数值型字段的类型转换:在多索引查询排序时,可转换其中一个索引上的数值型字段以在类型上完全匹配另一个索引上的该字段类型(但都是数值型的)。通过参数numeric_type参数,值可以是"double", "long", "date", "date_nanos"。
如有两个索引"myindex1"、"myindex2",其有同名字段"myfield"表示某种时间,该字段的类型在其中一个索引上被定义为date,而在另一个索引上被定义为long,这时在排序时需指定转换:

GET /myindex1,myindex2/_search
{
    "sort": {
        "myfield": {
            "numeric_type": "date_nanos"
        }
    }
}

值缺失(空值):
参数missing,可为_last_first,分别使得排序字段空值的文档排最前或最后。

sort的各种形式:

{
    "sort" : [
        { "date" : {"order" : "asc"}},
        "user",
        { "name" : "desc" },
        { "age" : "desc" },
        {
            "grades": {     #字段'grades'被定义为数组类型
                "mode": "min",  # min: 
            }
        },
        "_score"
    ]
}

指定返回字段

GET /myindex/_search
{
    "query":{"match_all":{}},
    "_source":["field1","field2"]
}
GET ...
{
    ...
    "_source": {
        "includes": ["profile"],
        "excludes": ["profile.location"]
    }
}

不返回任何文档字段:_source: false
单个字段:_source: "字段名"
多个字段: _source:["字段1","字段2"]
字段模式: _source: myf1*
涵盖及排除:

{
    _source: {
        "includes": "time_*",       #返回以 time_ 开头的所有字段
        "excludes": "time_modified" #但排除time_modified字段
    }
}

不想返回hits数组(如在聚合查询时):设置size为0:

GET ...
{
    "query": ...,
    "size": 0,
    "aggs": ...
}

{"doc_fields":["field1"] }返回'not_analyzed'的字段,'text'之类的字段不允许在doc_fields中。

查询谓词可以用在"query"和"filter"两种上下文,"query"要解决的问题是“是否匹配+匹配得怎么样(即相似性、分值)”,"filter"解决的是“是否匹配”,filter更多用于结构性、可直接匹配性的字段(数值、枚举值字段)。

游标式分批获取 滚取文档 scroll
ES支持以“游标”方式读取大批量文档,文档查询时设置以“游标”方式返回数据,ES每批次返回一定数量的文档,以及一个字符串指针,以供下次继续查询使用。在不需要指针后应及时删除指针。

滚取文档报错:查询上下文缺失异常(SearchContextMissingException)。这是因为服务器上已不存在该滚动指针。滚动指针在服务器上只会被保存一定时间,之后会被自动删除,或该指针是通过程序接口被删除,从而导致上下文失效。

#查询文档,并表明用scroll方式返回结果,通过参数?scoll=<指针保存时长>实现,如 ?scroll=1m保存1分钟
GET _search?scroll=1m           #让服务器保存指针1分钟
{
    "size": 100,        #每次滚取100篇
    "query": ...        #查询条件
}
#上述查询会返回下次滚取指针scroll_id

#滚取下一批数据
GET /_search/scroll
{
    "scroll":"1m",      #下批数据滚取指针保存时长
    "scroll_id": "上次滚取操作返回的指针(字段_scroll_id)"
}

#删除指针
DELETE /_search/scroll
{
    "scroll_id": ""     #删除单个或多个(数组)
}

inner_hits
用在具有内部结构的类型(如nested)的字段上,以返回命中的内部元素。

nested, has_parent, has_child查询。

{
    "query": ... ,
    "nested": {
        "inner_hits": {}   #值可为空对象,即使用默认参数
    }
}

{
    "query": ... ,
    "nested": {
        "inner_hits": {
            "name": inner_hits_result_name",    #结果名字,主要用于多个inner_hits查询时的情况
            "from": ,       #内部命中的若干结果中开启取元素的第一个的偏移量
            "size": ,       #需要取的元素的个数
            "sort"          #内部命中结果的排序
        }
    }
}

sliced scroll:类似滚取,但以数个切片方式获取,切片相互间独立无关,可支持对切片的并行处理,但对切片数量要求不能太大,大于集群分片(shards)数量时会存在严重各分片上首次查询性能以及内存爆炸隐患。详见 https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll

聚合 Aggregations

GET ...
{
    ...
    "aggs": ...  #聚合
}

均值 avgavg

{
    "aggs": {
        "my_return_field": {    #聚合结果字段名
            "avg": {
                "field": "my_field" 
                "missing": value-for-missing-such-field     #可选。在字段缺失时的补充值
            }
        }
    }
}
{
    "aggs": {
        "my_avg": {
            "avg": {"script": "doc.my_field.value" }
        }
    }
}

加权均值weighted_avg,计算公式∑(value * weight) / ∑(weight)

{
    "aggs": {
        "my_return_field": {
            "weighted_avg": {
                "weight": {
                    "field": "权值字段",
                    "missing": ...  #可选。默认忽略该文档
                },
                "value": {
                    "field": "值字段",
                    "missing": ...      #可选。默认权值为1
                }
            }
        }
    }
}

数组数据+单值权重:对于一个文档的字段值为数组权重值为单值的情况,视为各自元素是相互独立的,即各自元素乘以权重,权重和(分母)是参与计算的权重的和,即元素多少个则该次计算就有多少倍权重参与计算。

#数据为:
{
    "g": [5,2,8],
    "w": 3
}
#聚合查询为:
{
    "aggs": {
        "weighted_avg": {
            value: {
                "field": "g"
            },
            "weight": {
                "field": "w"
            }
        }
    }
}
#结果为: (5*3+2*3+8*3)/(3+3+3)

最大值 max

最小值min

sum

百分位percentiles 要求数据字段类型为数值型。

{
    "aggs":{
        "my_return_field":{
            "percentiles": {
                "field": "数据字段",
                "percents": [,,,] #可选。百分位点,默认[ 1, 5, 25, 50, 75, 95, 99 ]
            }
        }
    }
}
#返回
{
    "aggregations": {
    "my_return_field": {
        "values": [
            {
                "key": 1.0, #百分位点
                "value": xxx    #数据点
            },
            { "key": 99.0, "value": xxx }
        ]
    }
    }
}
}

cardinalitycardinality ……

常用统计量stats 返回 和(sum)、个数(count)、最小值(min)、最大值(max)、均值(avg)。

{
    "aggs": {
        "my_return_field": {
            "stats": {
                "field": "统计字段",    #或通过脚本
                "missing": xxx      #可选。
            }
        }
    }
}
#返回
{
    "aggregations": {
        "my_return_field": {
            "count": 4,
            "min": 20.0,
            "max": 80.0,
            "avg": 55.0,
            "sum": 220.0
        }
    }
}

常见统计量extended_stats 返回 和(sum)、平方和(sum_of_squares)、个数(count)、最小值(min)、最大值(max)、均值(avg)、方差(variance)、标准差(std_deviation)等统计量。

{
    "aggs": {
        "extended_stats": {
            "field": "统计字段"  #字段或者脚本'script'
        }
    }
}

文档聚合统计量terms 根据字段值或某种条件聚合文档,然后统计各条件值对应文档量。返回值以及对应文档统计值(用在类型为keyword或其他适宜bucket聚合的类型的字段上),默认按文档量降序排列("order":{"_count":"desc"}),也可指定以键排序("order":{"_key":"desc"})以及返回结果条数("size": 10)。(多值字段值可做过滤)

{
    "aggs":{
        "my_facet":{
            "terms"{
                "field": "统计字段" #在字段(field)上或使用脚本(将"field"换为"script": ...)
                "size": <结果条数(默认以计数降序排列)>,
                "include": "通配符模式 *_movie_*",
                "exclude": "通配符模式",
                "order": {"_count":"asc"}  #以文档量升序排列,不建议此种排序
                "order": {"_key":"desc"}  #以键降序排列
            }
        }
    }
}

返回样例

{
    ...
    "aggregations": {
        "my_facet": {
            ...
            buckets:[
            {
                "key": "词1",
                "doc_count": <n1>  #对应计数,默认按计数降序排列
            },{
                "key": "词2",
                "doc_count": <n2>
            }
            ]
        }
    }
}

例:文档有创建时间字段create_time,是long型,表示epoch millis,现需统计每天的文档量:

{
  "size": 0, 
  "sort": [
    {
      "create_time": {
        "order": "desc"
      }
    }
  ], 
  "aggs": {
    "DayCount": {
      "terms": {
       "script": {
         "source":"java.time.LocalDate.ofEpochDay(doc['create_time'].value/86400000L).format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)",
         "lang": "painless"
       }
      }
    }
  }
}

聚合显著统计量significant_terms 具有“前景”和“背景”文档量两个概念,前景量在背景量中的占比视为“显著性”(significance)。该聚合

{
    "query": {}, #查询条件
    "aggs": {
        "my_result": {
            "significant_terms": {
                "field": "my_field",
                "size": 10,     #输出结果最多个数
                "min_doc_count": 10        #要求的文档最小量,默认10,故低于默认值量以下时不会输出结果
            }
        }
    }
}

经纬坐标边界聚合:返回匹配文档的经纬坐标所覆盖的边界,以边界左上和右下两点坐标为代表数据返回,要求数据字段类型为geo_point

{
    "aggs":{
        "my_return_field":{
            "geo_bounds":{
                "field": "geo-data-field"
            }
        }
    }
}

经纬坐标中心点查询geo_centroid

自定义map-reduce脚本聚合scripted_metric

{
    "aggs":{
        "my_return_field":{
            "scripted_metric": {
                "init_script": ...,
                "map_script": ...,
                "combine_script":...,
                "reduce_script":...
            }
        }
    }
}

聚合流水线: TODO

……

脚本 script

ES默认使用painless脚本语言。

doc['field'].value

TODO
查询脚本里可用的上下文变量

索引 Indexing

索引及相关分片信息:GET /_cat/indices?vGET /_cat/indices列举所有索引及相关信息,一索引一行,各列表示相关信息,第三列是索引名,?v参数表示需要显示表头。可指定索引名模式,如所有索引除了以点号打头的:GET /_cat/indicies/*,-.*

索引别名 Aliases

可以为索引定义别名,如将"myindex1"->"myindex",甚至可以将多个具体索引名定义到同一个别名,如将"myindex1"和"myindex2"都定义到别名"myindex",对该别名的查询操作相当于对多个索引的查询。

数据筛选:可以将一个或多个索引上的数据筛选(filter)后作为一个索引别名,这通过在定义别名时提供筛选器(filter)来实现。

写数据
定义在多个索引上的别名,可配置其中一个具体索引称为写操作的索引,通过在定义某个具体索引为别名关联的一个索引时指定其选项is_write_indextrue来打开别名可写功能,功能默认关闭。

GET /_aliases

GET myindex1/_alias

POST /_aliases
{
  "actions": [
    {
      "add":{
        "index": "myindex",
        "alias": "myalias",
        "filter": {...}     #添加筛选器
        "routing": ...      #可指定路由依据,以避免不必要的分片操作
        "search_routing": ...
        "index_routing": ...
        ""is_write_index" : true        #启用时,可向别名提交写操作,默认false
    },
    {
      "add": {
        "index": "my_index_2",     #可以向已存在的别名添加另一个具体索引
        "alias": "my_index_alias"
      }
    },
    {
        "add": {
            "index": "my_index*",   #可使用通配符
            "alias": "my_alias3"
        }
    },
    {
        "add":{
            "indices": ["my_index1, myindex2*"],  #多个索引名/名字模式时使用参数名`indices`
            "alias":"my_alias4"
        }
    },
    {
        "remove": {     #从别名指向的索引中移除指定索引(索引模式、若干索引)
            "index": "xx",
            "alias":"my_alias"
        }
    }
  ]
}

#重命名别名,通过先移除后添加方式实现
POST _aliases
{
    "actions":[
        {
            "remove":{
                
            }
        },
        {
            "add":{
                
            }
        }
    ]
}

带筛选条件的别名(filtered alias):可在创建别名提供一个查询筛选条件,使得通过查询该别名索引时“天然”带有一个筛选条件,相当于可以为同一个实际索引通过带筛选条件的别名定义不同的数据视图,使得不同的别名看到的数据是各自筛选后的数据。

POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "my_index",
        "alias": "my_index_myalias1",
        "filter":{
            "term":{
                "myfield1":"myvalue"  #定义一个term查询,使得该别名索引下的数据是通过该条件筛选后的
            }
        }
      }
    }
}

创建索引PUT /<索引名>,索引名必须全小,可以提供索引的字段配置。

PUT /myindex
{
    "mappings": {
        "_doc": {
            "properties": {
                "field1": {"type": "keyword"},
                "field2": {"type": "text" },
                "field3": {"type": "integer" },
                "field4": {"type": "date" }
            }
        }
    }
}

字符串形态的类型有两种,textkeyword,text类型的字符串将会被分析器分词,分词后一个词叫做一个'term',然后存入倒排索引。keyword类型字符串不会被分词。

删除索引DELETE /<索引名>,删除索引将导致其中所有文档被删除。

显示集群信息GET /

Mapping 映射(文档/字段定义)

映射(Mapping)定义了文档及其字段被存储和索引的方式。

mapping type概念已自版本6.0开始被弃用,8.0之后不再受支持。在版本6.8, 7.x的索引相关的接口(及其他部分接口)中可使用HTTP查询参数include_type_name=false使得相关调用时免去mapping type概念,该参数在版本8.0之后不再可用。如

#定义
PUT index?include_type_name=false
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": { 
      "foo": {
        "type": "keyword"
      }
    }
  }
}

PUT index/_mappings?include_type_name=false
{
  "properties": { 
    "bar": {
      "type": "text"
    }
  }
}

GET index/_mappings?include_type_name=false

配置键index.mapping.total_fields.limit用以设置一个索引在允许创建的字段的数量,以避免手动或动态创建过多字段导致内存溢出、发生错误后恢复数据困难等问题。该值默认1000。

index.mapping.depth.limit 默认20。

index.mapping.nested_fields.limit 默认50。

index.mapping.nested_objects.limit 默认10000。

index.mapping.field_name_length.limit 字段名长度限制,默认无限制。

不能为已定义映射的字段更改映射或数据类型。

为字段执行重命名会导致该字段上的索引数据失效并重新索引,而为字段定义别名则不会。

字段的值为空数组时等同字段无值。

字段类型与查询:
text类型的字段值不能做terms聚合(默认不能)。

keyword类型的字段不能用match谓词做模糊查询(但可以匹配索引值与实参值完全相同的情况,相当于term谓词)。

keyword类型上的模糊查询用fuzzy,精确查询用term

为索引定义Mapping示例

#创建并定义映射
PUT /my-index
{
  "mappings": {
    "properties": {
      "age":    { "type": "integer" },  
      "email":  { "type": "keyword"  }, 
      "name":   { "type": "text"  }     
    }
  }
}

#为以存在的索引添加字段映射
PUT /my-index/_mapping
{
    "properties": {
        "field1": {
            "type": "integer",
            "index": false
        }
    }
}

#查看索引的映射
GET /my-index/_mapping

#查看指定字段的映射
GET /my-index/_mapping/field/my-field

字段数据类型

支持的数据类型:

  • 核心类型。如text, keyword, date, long, double, boolean.
    • 类字符串。 text, keyword
    • 数值。 long, integer, short, byte, double, float, half_float, scaled_float
    • 布尔 boolean
    • 时间 date
    • 时间(纳秒级) date_nanos
    • 区间。 integer_range, float_range, long_range, double_range, date_range
    • 二进制 binary。该类型可接受Base64编码的字符串作为二进制数据,默认不存储,不可查询。
  • 支持JSON层级结构的类型。如object, nested.
    • object。单个JSON对象。
    • nested。支持JSON对象数组。数据插入时就像JSON对象,但底层以扁平化的多汁字段(以点号分割各层字段名)为存储,实际已无JSON对象中各键间的关联性。
  • 特化的类型。如geo_point, geo_shape, completion
    • geo_point 地理坐标点(经度、纬度)。
    • geo_shape 地理区域(如一个多边形区域)。
    • ip IPv4, IPv6地址。
    • completion 用于自动补全。
    • token_count 存字符串中token的数量,比如结合multi-fields,为text字段定义子字段以表示token数量。
    • alias 字段别名。
    • flattened(需要X-Pack) 将JSON对象结构的数据以扁平化形态存储到一个字段(即该类型对应的字段)。插入/更新值时实参可为JSON对象,查询时,用dot-notation访问子字段(子字段是指从JSON对象结构来看的,并非从数据库)。
    • join 用于同个索引内文档间创建父子关联关系。
    • dense_vector 稠密向量。
    • sparse_vector 稀疏向量。
    • percolator, rank_feature, rank_features, search_as_you_type, shape
  • 数组。
    如果字段需要存储多个值,不需要做任何额外工作,字段本默认可存储任意个值(零个、一个、多个),插入时提供多个值该字段即自动是“数组”,数组中的元素都必须是同种类型的。对于object的数组,不能对其中独立地查询每个对象而不与其他对象挂钩。达不到的这种意图应考虑nested类型。
  • multi-fields 一个字段以多种不同的方式索引以达到不同的目的(由定义索引字段映射时在相应字段的json对象下提供键为fields的json对象参数实现)。

一个字段可以以不同的方式为其索引,以达到不同的目的。如为字符串数据,以text类型为其索引以支持全文检索,以keyword类型索引以支持排序和聚合,另外还可为字符串数据指定不同的分析器,分析器决定了字符串如何被切分为索引项的。
即所谓数据类型的“multi-fields”功能。

multi-fields
定义映射:

#一个字段多种方式索引
PUT /my-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text",
        "fields": {
          "my_sub_field": { 
            "type":  "keyword"
          }
        }
      }
    }
  }
}

字段访问(以dot-notation语法):

GET my-index/_search
{
  "aggs": {
    "agg_result_name": {
      "terms": {
        "field": "my_field.my_sub_field" 
      }
    }
  }
}

字段别名

PUT my-index
{
  "mappings": {
    "properties": {
      "my_field1": {
        "type": "long"
      },
      "my_alias": {
        "type": "alias",
        "path": "my_field1"     #如果涉及子字段,此参数需是绝对路径
      }
    }
  }
}

别名的实参目标字段不能是别名这种“虚”的字段,必须是实在的字段,目标字段在定义别名前需存在。
别名可用于读取类接口,如查询、聚合、排序,不能用于插入、更新。

text类型:用于全文检索,text字段不能排序,很少用于聚合(significant text aggregation是个例外)。
text字段的参数:

  • analyzer 分析器,用于一个analyzed的字符串字段。默认同索引的analyzer。
  • search_analyzer 对查询字符串用的分析器。默认同analyzer
  • search_quote_analyzer 对查询字符串中被引号包裹的字符串(即短语)用的分析器。默认同search_analyzer
  • fielddata 是否使用内存来排序、聚合、处理脚本等。值为布尔,默认false
  • fields 配置multi-fields。
  • index 是否索引(可被搜索),默认true
  • index_options 索引选项,用于高亮等目的。默认positions
  • index_prefixes 是否对term的前2-5字符额外单独索引以提高前缀查询效率。
  • index_phrases 启用时提高slop=0的短语匹配效率,以巨大的存储开销为代价。
  • norms 在为查询打分时是否考虑字段长度(是否进行长度归一化)。默认true
  • store 是否存储,启用时在查询时指定_source字段可返回存储值。默认false
  • similarity 打分/相似度算法。默认BM25。
  • term_vector 是否存储term向量,对于analyzed字段。

时间类型:
date在ES底层是以epoch millis存储的,展示时(如聚合等)是以配置的格式将时间戳转为字符串的。
传值时可以字符串或epoch millis时间戳形式,字符串解析为时间时所用的格式可在定义字段时配置多种格式。

PUT my_index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type":   "date",
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      }
    }
  }
}

IP类型:
查询时可以掩码方式进行范围查询,如“172.16.0.0/16”。

object类型:字段平铺的JSON对象数组。

从存储角度看,object类型字段是数组,其中的每个元素是一个JSON结构;从索引(及查询)角度看,object字段并不是由类型为JSON对象的元素组成,原本为JSON对象的每个元素通过元素各自的字段被“分解”,字段值被平铺(flatten)到分别对应的字段(而此时object类型的字段名就仅仅成为“命名空间”字符串,而不是一种普通意义上的数组,那种数组中每个元素是一种JSON对象,object不能满足的这种情况正是类型nested出现的背景)。

PUT my_index
{
  "mappings": {
    "properties": {
      "user": {
        #"type":"object" <==不需要这样的指定,不提供"type"同时提供"properties"即成为`object`类型
        "properties": {
            "firstname": ...
            "lastname": ...
        }
      }
    }
  }
}
#在索引角度看,这里字段结构看起来更应该像
{
    "user.firstname": []  #这是一个数组,如,当firstname是字符串时,则这里就是字符串数组
    "user.lastname": []
    #firstname和lastname在这里就是两个完全不相干的字段,只是都具有相同的字段名前缀user(“命名空间”)而已
    #当然,"user"被定义为object类型时,纵使在索引时其中的firstname和lastname已无相关性,但在插入/更新数据时,我们仍可以将"user"作为JSON数组那样去对待,将firstname&lastname放在同个JSON对象中,而非分别单独地操作"user.firstname", "user.lastname",即可以在插入时提供{ user: [ {firstname:"", lastname:""},  {firstname:"", lastname:""}]
}

nested类型:JSON对象数组。该类型中索引时和存储时以基本相同形式来对待JSON结构数据(基本上也就是我们看到的JSON对象数组形式)。

PUT my_index
{
  "mappings": {
    "properties": {
      "user": {
        "type":   "nested",
        "properties": {
            "firstname": ...
            "lastname"
        }
      }
    }
  }
}

区别nestedobject,以一个简单的例子:
假设索引中的顶级文档代表“人”,其有"names"字段(类型为nestedobject;场景含义:一个人有若干个名字(如人的小名、笔名、艺名、曾用名等),该字段在被定义为类型nestedobject时,场景含义有略微的变化),有子字段"first", "last",mapping为

#已忽略 mapping type概念
# nested例子
PUT person
{
  "mappings": {
    "properties": {
      "names": {
        "type":   "nested",
        "properties": {
            "first": {"type": "keyword" }
            "last": {"type": "keyword" }
        }
      }
    }
  }
}

# object例子
PUT person
{
  "mappings": {
    "properties": {
      "names": {
        "properties": {
            "first": {"type": "keyword" }       #名
            "last": {"type": "keyword" }        #姓
        }
      }
    }
  }
}

#插入数据
PUT person/1
{
    "names": [
    {
        "fist": "Jack",
        "last": "Smith"
    },
    {
        "fist": "Bob",
        "last": "Lee"
    }
    ]
}

PUT person/1
{
    "names": [
    {
        "fist": "Bob",
        "last": "Williams"
    },
    {
        "fist": "Steph",
        "last": "Lee"
    }
    ]
}

考虑这样的需求:

  • 找出这样的人,他的某个姓名的“名”(first)部分使用"Bob",他的某个名字的“姓”(last)部分使用过"Lee"(不是要求他有一个姓名为"Bob Lee")。这时使用object即可满足需求 ,当然也可使用nested(查询时使用“或”来组合分别在first和last字段上的两个独立的查询)。
  • 找出这样的人,已知他用过的一个姓名是"Bob Lee"(即{fist:"Bob", last:"Lee"}),我们掌握的这个姓名信息足够可靠,我们就希望仅匹配使用过姓名"Bob Lee"的人,不希望匹配到用过姓名"Bob Williams"以及名字"Steph Smith"这样的人。这时使用类型nested,无法用object实现这样的需求。

join类型:同个索引内创建文档间的父子关系。
(这里的“父子”指的是在关联关系(其是有向的)结构上的层级角度看的“父”与“子”,不是数据字段上的继承这一类概念的“父子”。)

一个父文档及其所有子文档必须存储于同一个分片,因此存储子文档时需设置路由参数。

PUT my_index
{
  "mappings": {
    "properties": {
      "my_join_field": { 
        "type": "join",
        "relations": {
          "parent_my_type": "child_my_type" #或者多个子类型。但父类型只能有一个,在每个索引里。 
          #"parent_my_type": ["child_my_type","child_my_type2"] #或者多个
          "child_my_type": "..."    #多级join,不推荐(其性能损耗大)
        }
      }
    }
  }
}

#插入数据
PUT my_index/_doc/1
{
  "text": "这个文档是父文档类型的",
  "my_join_field": {
    "name": "parent_my_type" 
  }
}

PUT my_index/_doc/3?routing=1   #必须指定routing,为关联父文档的ID
{
  "text": "这个文档是子文档类型的",
  "my_join_field": {
    "name": "child_my_type",
    "parent": 1     #必须指定关联的父文档
  }
}

文档元字段/元数据:ES为每个文档定义了一些元字段以保存元数据。

  • _index 文档所在的索引。字段实际不存在于底层Lucence,不支持谓词prefix, wildcard, regexp, or fuzzy
  • _id 文档ID(需注意的是在有mapping type概念时,ID的唯一性仅限于一个mapping type下,也就说同个索引下可能存在相同的ID,其文档位于不同的mapping type下)。允许最长512字节。
  • _type 文档的mapping type(该概念在版本6.0及之后被标记为弃用,版本8.0及之后完全移除)。
  • _source 文档原本的JSON结构数据。该字段被存储但不被索引,因此可被获取但不可被搜索。该字段会增加额外的存储开销,可以禁用该字段,但禁用会导致部分功能不可用,如接口update, update_by_query, and reindex、即时高亮。当存储开销成为担忧,考虑增加压缩级别而非禁用该字段。
    定义_source的映射时,可指定包含和/或排除的文档字段,支持通配符*,以定义需要或不需要被存入_source的文档字段,不被存入_source的字段不影响其是否可被检索。
  • _size 文档大小,以字节计。
  • _field_names 索引本文档的这种字段的名字,其doc_valuesnorms都被禁用了。当太多字段都禁用了doc_valuesnorms时,可以选择禁用字段_field_names,通过设置映射{ "mappings": {"_field_names": {"enabled": false}}}。(以前该元字段用于索引本文档的所有值非null的字段名。)
  • _ignored 当某个字段启用_ignore_malformed时,若索引一个文档时该字段数据存在错误,正确的字段会被正常索引,错误的字段的名字会被索引入元字段_ignored。功能_ignore_malformed未被启用时,字段错误将导致整篇文档被拒绝索引同时抛异常。`
  • _routing 用于路由文档到分片。
  • _meta 自定义的元数据,值为JSON,可被更新。该字段可以被获取,不会被用于索引。

映射的参数(mapping parameters)
以下是字段的一些常见映射参数(并非所有数据类型都有以下所有参数):

  • index 是否索引,默认true
  • index_options 索引选项。(数值型不再支持此参数)
    • docs 文档数量。能力有限,仅可回答给定term是否存在于文档的字段。
    • freqs 加之(term的)频次。
    • positions 加之位置(即序号) (默认)。
    • offsets 加之term在原字符串中的起止偏移量。
  • store 是否存储。不影响_source中关于该字段数据的是否存储性。当仅想存储很少的字段时,可关闭_source同时打开特定字段的store。默认false
  • analyzer
  • normalizer 类似analyzer,只是这产生仅1个token。
  • search_analyzer
  • similarity
  • boost
  • coerce 该功能开启时使得,接受类型不太严格的数据,如原本类型(即定义映射时该参数所对应的类型)为整数时,接受字符串类型的数字,或者无小数的浮点数。当关闭时,实参类型未严格对应时文档将被拒绝。默认true(开启)。
  • fields 设置multi-fields。
  • norms
  • copy_to 将该字段的原本值复制到指定的若干字段。注意(1)复制的是原本的值而非分析后的若干term;(2)复制出的值不具备传递性,即若目标字段也定义了copy_to则复制出的值不会传递到第3个字段;(3)复制的值不影响_source
  • doc_values 搜索的过程是通过倒排索引找到term后取得文档,而像排序、聚合、字段值获取等需要先找到文档然后取得数据。doc values功能为这种方式提供了高效的实现,其数据位于磁盘,面向列。大多数类型都支持设置doc_values,但analyzed的字符串(text)是个明显的例外。doc_values默认被开启。
  • dynamic 对于未预先定义于映射的字段的数据插入。
  • enabled 是否启用字段。禁用时,不可存储、搜索,除了仅有_source中存储。
  • fielddata 排序、聚合、脚本中需要用的数据结构形式是“正排索引”而非倒排索引(而索引库中存的是倒排),fielddata的存在解决的问题同doc_values,但doc_values(其是将数据存于磁盘)不能用于text类型,fielddata则正为解决这一问题,fielddata启用时,text字段于上述应用场景时其“正排”数据会被载入到内存(而不像doc_values那样在索引时载入磁盘)。
  • format 时间格式。可配置多种,以||分隔。可接受epoch millis/seconds时间戳,格式名为epoch_millis, epoch_second
  • ignore_above 允许的字符串的最大元素(term)个数,超过则不索引/存储。
  • null_value null值不可被索引或搜索,因此在ES索引遇到null时将其换之以参数的值以进行索引,仅影响索引,不影响数据原本形式,如_source中对应字段的值。
  • eager_global_ordinals
  • ignore_malformed
  • position_increment_gap 默认100。
  • properties 为类型objectnested配置子字段时使用。

分析 Analysis

分析,即是将文本(字符串)转变为若干token或term的过程。

索引时分析、查询时分析。

分析器 analyzer: 由3部分构成,字符过滤器(若干个)、分词器(有且仅有一个)、词过滤器(若干个)。

字符过滤器:不一定只作用过滤,只要满足输入一个字符串(相当于字符流)、输出一个字符串即可,过滤器可有若干个,按序被执行。 同理词过滤器,只是其被用于对term或token流的处理。

“keyword”分析器:是一个内置的分析器,实际什么都没做,其将输入字符串直接作为一个token/term。

REST API

文档操作接口

索引创建/替换)文档:

#创建或替换
PUT /<index>/_doc/<_id>         #用PUT时必须提供<_id>
#创建,自动生成文档ID
POST /<index>/_doc/
#给定文档ID创建,仅创建在ID已存在时失败
PUT /<index>/_create/<_id>      #必须提供ID
POST /<index>/_create/<_id>     #可不提供文档ID,此时将会自动生成

POST /<index-name>/_doc/ 或指定文档id: PUT /<index-name>/_doc/<id>,PUT时指创建或者替换,必须指定文档id。POST时指创建或更新,可不指定文档id。索引不存在时会自动创建索引。其中的_doc指mapping type,index有且仅有一个type(默认为_doc)(版本8.0及之后mapping type概念不再存在;参数?include_type_name=true|false不可用于文档索引接口)。PUT /<index>/_doc/<id>/_create,仅作插入(创建),如果文档已存在,则报失败。

# create a document
POST /myindex/_doc
{
    "name":"Jack Ma"
}

# create or replace a document
PUT /myindex/_doc/<id>
{
    "name":"Jack Ma"
}

选项(HTTP Query parameters):

  • op_type 值为index(默认)或create,后者表示仅当给定的文档ID不存在时创建,已存在时报失败。
  • version 整型。显式地指定文档版本号,用于并发控制
  • version_type (枚举)版本号类型。
    • internal
    • external, external_gte 版本号由外部维护而非由ES内部。比较时版本号需严格大于(前者)或等于(或者)当前版本号。
    • force
  • ……

更新文档: POST /<index-name>/_doc/<_id>/_update POST /<index-name>/_update/<_id> 文档ID<_id>必须提供。
参数:

  • retry_on_conflict 整数。默认0。
  • refresh 值可为true, false, wait_for

更新已存在文档时,更新操作是“增量性”的,即仅对提供的文档中的字段对应着进行更新,不会删除参数文档没有的而库中文档有的字段(即不会以参数文档替换掉库中文档,如果需要这样,应使用文档索引接口)。更新接口减少了用户的一些来回操作以及版本冲突问题。

更新操作要求_source被启用。更新操作实际上会将整个文档重新索引,利用参数文档中的增量字段以及_source中的其他字段。

文档版本
文档会有一个整型值的版本号,在每次被改、删时会增加1(删除文档后其版本号仍可用于一小段时间(默认60秒)),在给定参数verson的更新操作中将会比较参数值是否超过库中当前版本号,是则更新否则失败。

通过查询更新

POST /<index-name>/_update_by_query
{
    "query": ...
}

更新示例:
更新id为“1”的文档的"name"字段,并添加一个“age”和"films"字段。更新可通过文档内容或脚本完成,分别提供顶级键“doc”或“script”指定。更新请求的地址中如果没有最后的"_update",则是创建或替换(创建/替换没有顶级键"doc"或"script",而是直接提供文档内容)。

POST /myindex/_doc/1/_update
{
    "doc": {
        "name":"Stephen Chow",
        "age":50,
        "films": ["喜剧之王","九品芝麻官"]
    }
}

也可以通过脚本更新文档。
将"age"字段值加6:

POST /myindex/_doc/1/_update
{
    "script:"ctx._source.age += 5"
}

删除文档:通过id删除 DELETE /<index-name>/_doc/<id>

DELETE /myindex/_doc/<_id>`

通过查询删除:POST /<index>/_delete_by_query

POST /myindex/_delete_by_query
{
    "query": {...}
}

重新索引 reindex(到新索引名):将一个索引库中的文档数据全部进行索引到一个新的索引名(索引库)。要求_source被启用。

POST _reindex
{
  "source": {
    "index": "<source-index-name>"
  },
  "dest": {
    "index": "<target-index-name>"  #必须不同于源索引
  }
}

接口相关的约定

大多数接口具有一些约定

多索引:查询时的“索引”()部分可指定多个索引(multi index),或是名字模式,或以逗号,分隔的索引名/索引名模式。
GET indexa,indexb/_search_all表示所有索引。多索引查询中索引名支持通配符*,如所有以"myindex"打头的索引:myindex*,以及通配符+排除-,如以"myindex"打头但除了myindex1:myindex*,-myindex1_all只能单独使用。排除语法不能单独使用,如果想表达“所有索引,除了某索引”意图,可通过通配符+排除实现*,-myindex1;排除语法内可用通配符,如*,-myindex*

多索引相关参数:

  • ignore_unavailable true或false,忽略不存在或已关闭(closed)的索引
  • allow_no_indices false或true,表示在通配符未能匹配任何索引时是否需要报异常。
  • expand_wildcards 为其中之一none,open,closed,all, open,closed(等同all)。分别指禁用通配符扩展、仅扩展到open的索引、仅扩展到closed的、扩展到两种的。

索引名的日期算术支持
形式:<static_index_name{date_math_expr{date_format|time_zone}}>
其中的<>{}|等符号相当于关键字(在HTTP请求URL时需将其转义)。
其中

  • static_index_name 相等于模式前缀
  • date_math_expr 为日期算术表达式,其中"now"为索引系统处理请求时的当前时间。
  • date_format为日期格式,遵循java.time.format.DateTimeFormatter的约定
  • time_zone 为时区名或时区时间偏移量,默认"utc"。

表达式例子:

#假如当前时间为UTC 2050年7月7日15点。

<log-{now/d}>   => log-2050.07.07  (注意默认年月日分隔符是点号而非横杠)
<log-{now/M}>   => log-2050.07.01   (“/M”月的日期变为月首日,并非将天数四舍五入影响到月上)
<log-{now/M{yyyy-MM}}> => log-2050-07-07   (抹掉月的日期部分)
<log-{now/M-1M{yyyy-MM}}>  => log-2050-06 (减1个月)
<log-{now/d{yyyy-MM-dd|+8:00}}> log-2050-07-08(加8小时)

通用选项(常见于多数接口的选项):

  • pretty 美化输出,值为truefalse,定义但未给值时(?pretty)相当于?pretty=true。与之关联的选项format可为json(默认)或yaml
  • human 人可读性增强,如时间(如1h而非毫秒数)、内存/磁盘数据大小(kb而非字节数)。
  • 时间算术 /d四舍五入到最近的一天,可用单位
    • y
    • M
    • w
    • d
    • h, H 小时
    • m 分钟
    • s
  • 响应字段筛选。参数名filter_path,用于HTTP Request Parameter,值为字段路径,以逗号,分隔多个字段,支持单星号*和双星号**的通配符,后者可匹配若干子层级路径,支持排除符-。例filter_path=took,hits.hits._id,hits.hits.*count,-hits.hits.my_count,hits.**.my_field

客户端接口(Java)

// REST接口
//依赖 org.elasticsearch.client:elasticsearch-rest-high-level-client

object app {
  def main(args: Array[String]): Unit = {
    val client = new RestHighLevelClient(RestClient.builder(HttpHost.create("localhost:9201"),new HttpHost("localhost", 9200, "http")))
//        .refresh()    // 添加  ?refresh

    //RestHighLevelClient是线程安全的

    val ssb = new SearchSourceBuilder()
      .query(QueryBuilders.termsQuery("nickname", "Jack Ma","马云"))
      .fetchSource(false)   // no source fields. returning only _id, _score
/*对应kibana工具中的查询
GET srcdoc/_search
{
    "_source":false,
    "query": {
        "terms": {"nickname":["Jack Ma","马云"]}
    }
}
*/

    val searchResponse = client.search(new SearchRequest(Array("srcdoc"), ssb))

    // 获取每个文档的_id字段的值
    searchResponse
        .getHits
        .getHits
        .map(_.getId)
        .foreach(println)

    //searchResponse.getAggregations        // 获取聚合结果,本例查询未指定聚合操作

    client.close()
  }
}

部署

安装ElasticSearch

进入Elasticsearch下载页面,选择下载最新稳定版程序包。
解压程序包并启动单节点集群:

tar -xzvf elasticsearch-x.x.x.tar.gz     #其中x.x.x为版本号
#配置环境变量ES_HOME,非必须
#export ES_HOME=...
cd  elasticsearch-x.x.x
bin/elsaticsearch
#后台运行
bin/elasticsearch -d #(daemonize)后台运行
#后台执行ES,将pid输出到FILE
#记录PID
bin/elasticsearch -d -p FILE
#通过启动命令行参数覆盖配置文件中的配置  -E参数
bin/elasticsearch -E<key>=<value>

#关停ES程序
kill PID    # kill -SIGTERM <pid>

ES默认在9200端口提供REST API服务。

ES也提供Transport API服务,该服务区别于REST API的地方主要是其数据交互是以java对象序列化/反序列化进行,Transport API已被弃用。

如果报错max virtual memory areas vm.max_map_count [xxxx] is too low,可在/etc/sysctl.conf中添加vm.max_map_count=655360,然后sysctl -p来调整相应系统参数,之后可成功启动es。

配置 ElasticSearch

环境变量ES_PATH_CONF指定ES配置文件存放目录。

配置目录可有如下配置文件:

  • elasticsearch.yml
  • jvm.options JVM参数,一行一个参数。支持仅对特定jvm版本启用配置的语法。环境变量ES_JAVA_OPTS配置jvm参数。
  • log4j2.properties 日志相关

默认配置文件目录$ES_HOME/conf/。

#elasticsearch.yml
action.auto_create_index: myindex*,other_inx*,+allowed_inx*,-disallowed_inx* #对满足模式的索引名按需自动创建索引,支持通配符,支持以逗号拼接出的多个模式,“+”开头的表示允许,“-”开头的表示禁止

cluster.name: #集群名,默认“elasticsearch”,同集群名的节点形成集群
  
node.name: #节点名

path:
  data:  #索引数据存取目录,可以配成多个目录的数组
  logs:  #日志输出目录
network.host: #服务网口,特殊值_local_, _site_, _global_ 
#特殊的值:${prompt.text}和${prompt.secret}表示将在控制台提示并读取输入作为对应配置值
#node.name: ${prompt.text}

discovery.zen.ping.unicast.hosts:  #若干<host>:<port>,<port>未指定时,将会扫描对应主机的9300~9305端口
discovery.zen.minimum_master_nodes: <数量>  #

#multi-search, multi-get, bulk相关
rest.action.multi.allow_explicit_index: true(默认)| false  # false时,将会拒绝request-body中修改URL参数中指定的index的行为

环境变量ES_TMPDIR配置ES临时文件目录,linux版默认/tmp

安装Kibana

进入kibana下载页面,选择下载最新稳定版,解压程序包并启动,程序将在默认的5601端口提供Web界面服务。

tar -xzvf kibana-x.x.x-linux-x86_64.tar.gz
cd kibana-x.x.x-linux-x86_64
#配置环境变量,非必须
#export KIBANA_HOME=...
bin/kibana
#后台运行
nohup bin/kibana &      #没有像bin/elasticsearch -d那样的-d选项

配置Kibana

conf/kibana.yml

server.host:  #kibana服务绑定的host, 0.0.0.0绑定到所有网络接口
server.port: 

posted @ 2019-01-23 14:56  二球悬铃木  阅读(668)  评论(0编辑  收藏  举报