elasticsearch
发布于 6 年前 作者 BengBu-YueZhang 2356 次浏览 来自 分享

part 1

image

<!–more–>

前言

emmmmm…我一个前端学习es我也很为难啊。elasticsearch涉及的内容非常多,分布式相关的章节我都是跳过的。因为我真的看不懂😭

参考文章以及相关资料

Elastic Stack and Product Documentation

Elasticsearch: 权威指南

Elastic Stack从入门到实践

安装,启动,汉化

首先你的电脑需要拥有Java8的环境

// 使用Homebrew一键安装

brew update

brew install elasticsearch

// kibana是一个可视化工具
brew install kibana

🀄️kibana的汉化, 可以使用GitHub上的提供的包(需要安装python环境)

RESTful API

可以使用RESTful API通过9200端口与Elasticsearch进行交互

基本概念

Index

索引类似于数据库中表,是用来存储文档的地方。将数据存储到es的行为也可以叫做索引

type

💡 Mapping types will be completely removed in Elasticsearch 7.0.0. (type的概念将在7.0中被删除)

💡 The first alternative is to have an index per document type (更好的做法是, 每一种type将会有单独的Index, 不同的type存储到不同的索引中)

Document

Index(索引)中的每一条记录被称为文档, 许多的Document组成了Index

搜索

空搜索

返回所有索引下的所有文档


GET /_search

# 等同于,匹配所有的文档
GET /_search
{
    "query": {
        "match_all": {}
    }
}

简单搜索

通过查询字符串query-string搜索, 通过URL参数的形式, 传递查询参数


# 查询test_index索引中, name字段包含lihang的文档
GET test_index/_search?q=name:lihang

# 查询test_index索引中, name字段包含zhangyue或者liyubo
GET test_index/_search?q=name:(zhangyue OR liyubo)

QueryDSL

Query DSL指定了一个JSON进行检索, 它支持构建更加复杂和健壮的查询


# match查询, 会将查询语句进行分词处理, 查询name字段中包含lihang的文档

GET test_index/_search
{ 
  "query": {
    "match": {
      "name": "lihang"
    }
  }
}

全文搜索

使用match查询全文字段,可以对查询语句进行分词操作,返回所有的相关的文档,查询结果按照相关度算分排序。如果查询的是数字,日期,Boolean,match则会进行精确匹配


# 查询job字段中和engineer相关的文档

GET test_index/_search
{
  "query": {
    "match": {
      "job": "engineer"
    }
  }
}

短语搜索

使用match_phrase可以查询job字段中包含查询短语的文档


GET test_index/_search
{
  "query": {
    "match_phrase": {
      "job": "node engineer"
    }
  }
}

# 通过设置slop相似度, 可以允许查询文档与差异语句有一些差异
# 设置slop为2, java engineer 和 java web engineer 可以匹配

GET test_index/_search
{
  "query": {
    "match_phrase": {
      "job": {
        "query": "java engineer",
        "slop": 2
      }
    }
  }
}

高亮搜索

会对检索的匹配的结果中,匹配的部分做出高亮的展示, 使用标签em包裹


GET test_index/_search
{
  "query": {
    "match": {
      "job": "engineer"
    }
  },
  "highlight": {
    "fields": {
      "job": {}
    }
  }
}
// 返回的部分结果
{
  "_index": "test_index",
  "_type": "doc",
  "_id": "3",
  "_score": 0.2876821,
  "_source": {
    "name": "houjiaying",
    "job": "java engineer",
    "age": 22
  },
  "highlight": {
    "job": [
      "java <em>engineer</em>"
    ]
  }
}

多索引搜索

GET /test_index, test2_index/_search

🌈 针对多个索引进行搜索操作, 文档中也是类似的语法。但是我不太清楚为什么返回错误给我,如果有好心人请告诉我

🌈 Multi-Index search (autocomplete) (折中的解决方案)


# 使用_msearch方法多索引搜索

GET _msearch
{"index":"test_index"}
{"query":{"term":{"age":{"value":23}}}}
{"index":"test2_index"}
{"query":{"term":{"_id":{"value":1}}}}

分页

使用form指定查询的起始位置, size指定查询的数量


GET _all/_search?size=5&from=1

GET _all/_search
{
  "from": 0,
  "size": 1
}

聚合


# 统计索引中全部文档的age,并会返回聚合的结果

GET /test_index/doc/_search
{
  "aggs": {
    // 返回聚合结果到all_age字段中, 聚合terms匹配的age字段
    "all_age": {
      "terms": { "field": "age" }
    }
  }
}

// 返回的聚合结果
"aggregations": {
  // all_age是自己定义的
  "all_age": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": 24,
        "doc_count": 2
      },
      {
        "key": 22,
        "doc_count": 1
      },
      {
        "key": 23,
        "doc_count": 1
      },
      {
        "key": 30,
        "doc_count": 1
      }
    ]
  }
}

聚合分级汇总


# 添加测试用数据

DELETE test_index
PUT test_index
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"name":"zhangyue","age":23,"job":"web"}
{"index":{"_id":2}}
{"name":"lihange","age":23,"job":"web"}
{"index":{"_id":3}}
{"name":"liyubo","age":22,"job":"web"}
{"index":{"_id":4}}
{"name":"houjiaying","age":22,"job":"java"}
{"index":{"_id":5}}
{"name":"xiaodong","age":26,"job":"java"}

# 聚合查询分级汇总
# 汇总所有的职业并计算职业的评价年龄

GET test_index/doc/_search
{
  "aggs": {
    "job_info": {
      "terms": {
        "field": "job"
      },
      "aggs": {
        "avg_age": {
          "avg": {
            "field": "age"
          }
        }
      }
    }
  }
}

💡 解决方案

💡 Fielddata is disabled on text fields by default. Set fielddata=true

💡 在text字段中聚合是聚合是禁用的,需要设置索引的mapping, 设置字段的fielddata为true


# 设置job字段的fielddata为true

PUT test_index/_mapping/doc
{
  "doc": {
    "properties": {
      "job": {
        "type": "text",
        "fielddata": true
      }
    }
  }
}

Document

元数据

  • _index 文档存放的索引
  • _type 文档的类型
  • _id 文档的唯一标识

创建文档

可以分为指定ID创建, 和不指定ID创建(Elasticsearch会自动创建一个唯一标示)。指定ID使用PUT请求, 不指定ID使用POST请求

查询指定ID文档

GET /you_index/you_type/you_document_id

指定返回的字段


# 只返回文档的name字段

GET test_index/doc/1?_source=name

# 只返回文档的age字段

GET test_index/doc/_search
{
  "query": {
    "range": {
      "age": {
        "gt": 23
      }
    }
  },
  "_source": {
    // 返回的字段
    "includes": ["age"],
    // 不返回的字段
    "excludes": []
  }
}

检查文档是否存在

使用HEAD方法,HEAD不返回响应体


# 检查ID为1的文档是否存在

HEAD test_index/doc/1

更新文档


POST test_index/doc/1/_update
{
  "doc": {
    "name": "zhang yue"
  }
}

🌈 更新文档后的返回结果, 可以看的_version变为了2,_version字段的含义是文档更新了几次, _version可以实现乐观锁的功能


{
  "_index": "test_index",
  "_type": "doc",
  "_id": "1",
  "_version": 2,
  "found": true,
  "_source": {
    "name": "zhangyueHI",
    "age": 23,
    "job": "web"
  }
}

删除文档


# 删除ID为2的文档

DELETE test_index/doc/2

乐观锁

更新的时候,指定_version的大小,当更新文档的时候_version必须等于我们指定的大小,更新操作才会成功


# version为2时更新操作才会成功
POST test_index/doc/1/_update?version=2
{
  "doc": {
    "name": "zhangyue"
  }
}

批量检索


# 检索同一个索引下的多个的文档

GET /test_index/doc/_mget
{
  "docs": [
    {
      "_id": 1
    },
    {
      "_id": 3
    }
  ]
}

# 直接通过不同的id, 检索同一个索引下的多个的文档

GET /test_index/doc/_mget
{
  "ids": [1, 3, 4]
}


# 检索不同索引下的多个文档

GET /_mget
{
  "docs": [
    {
      "_index": "test_index",
      "_type": "doc",
      "_id": 1
    },
    {
      "_index": "test2_index",
      "_type": "doc",
      "_id": 1
    }
  ]
}

批量创建

批量创建的JSON中, 不能包含未转义的换行符,因为他们将会对解析造成干扰。必须使用Kibana自动格式化JSON,才可以正确的执行_bulk操作

批量操作关键字, index(创建, id重复时覆盖), delete(删除), create(创建, id重复时报错), update(更新)


# 批量操作 
POST /test_index/doc/_bulk
{"index":{"_index":"test_index","_type":"doc","_id":2}}
{"name":"songmin","age":20}
{"delete":{"_index":"test_index","_type":"doc","_id":2}}
{"create":{"_index":"test_index","_type":"doc","_id":2}}
{"name":"songmin","age":18}
{"update":{"_index":"test_index","_type":"doc","_id":2}}
{"doc":{"age":22}}

Mapping与分析

精确值与全文

精确值,通常指的是日期, 用户ID, 邮箱地址。对于精确值, java 与 java web是不同的。精确值,要么匹配查询,要么不匹配。

全文, 通常指的是文本数据, 匹配时是查询的匹配的程度

倒排索引

Elasticsearch中使用倒排索引的方式,实现快速的全文搜索

倒排索引是由文档中, 是所有可以被分词的字段的复词列表组成的。将所有文档的复词列表,用来创建一个没有重复分词的复词列表。并且记录了,每一个分词出现在那一个文档中。

例如搜索短语"dog over", 在倒排索引中在Doc_1, Doc_2都会匹配, 但Doc_1文档的匹配度会更高


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   |
------------------------

分析器

分析器由以下三种功能组成:

  • 字符过滤器, 例如过滤文本中HTML标签
  • 分词器, 将文本拆分成单独的词条
  • Token过滤器, 过滤分词, 例如转化大小写, 删除语气组词

Elasticsearch内置了多种的分析器, 例如空格分析器, 可以按照空格分词。语言分析器, 可以删除多余的语气助词

如果希望指定索引中字段的分析器,则需要我手动的指定索引的Mapping

analyze API


# 测试standard分析器
GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze"
}

Mapping

Mapping的定义可以类比为Mongo中Schema, 在Mapping中定义了索引里每个字段的类型以及分析器等


# 查看索引的Mapping

GET test_index/doc/_mapping

自定义Mapping

💡 Elasticsearch中, 已经不存在string, object类型, 使用text类型。

💡 字段的type被设置为"text"类型, 字段通常会被全文检索。如果设置为"keyword"类型, 字段会则需要精确查找。

💡 当字段的type被设置为"text"类型, 可以另外设置index(是否可以被搜索)和analyzer(分析器)


# 自定义索引的Mapping

PUT m2y_index
{
  "mappings": {
    "doc": {
      "properties": {
        "info": {
          type: "text"
        }
      }
    }
  }
}

更新Mapping

不能直接更新索引的Mapping, 只能在创建索引的时候指定Mapping, 以及添加新的字段的Mapping

复杂类型

  • 数组类型, 数组的内容必须类型一致, 检索时, 所有包含在数组中的内容都可以被检索
  • null, 空值不会被索引
  • Object, 可以通过以下的方式定义Object类型的Mapping(通过嵌套properties)
# 定义Object类型的Mapping

POST test3_index/doc
{
  "mappings": {
    "doc": {
      "properties": {
        "info": {
          "properties": {
            "name": {
              "type": "text"
            },
            "age": {
              "type": "text"
            },
            "hobbies": {
              "properties": {
                "one": {
                  "type": "text"
                },
                "two": {
                  "type": "text"
                }
              }
            }
          }
        }
      }
    }
  }
}

查询

match查询

match查询时,如果查询是全文字段,会对查询内容分词后进行全文搜索。如果指定的是数字, 日期, 布尔字段, match查询则会进行精准匹配

multi_match查询

对多个字段(fields数组中的字段), 执行相同的match查询

GET test_index/doc/_search
{
  "query": {
    "multi_match": {
      "query": "web",
      "fields": ["job", "name"]
    }
  }
}

range查询

进行范围查询操作, 关键字gt(大于), gte(大于等于), lt(小于), lte(小于等于)


GET test_index/doc/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 22,
        "lte": 30
      }
    }
  }
}
# 日期字段进行range查询

# 添加测试数据
PUT range_index
POST range_index/doc
{
  "mappings": {
    "doc": {
      "date": {
        "type": "date"
      }
    }
  }
}
POST range_index/doc/_bulk
{"index":{"_id":1}}
{"name":"zhangyue","date":"1994-06-07"}
{"index":{"_id":2}}
{"name":"lihang","date":"1994-10-22"}
{"index":{"_id":3}}
{"name":"zhaochen","date":"1994-07-01"}

# 查询日期范围, 日期小于1994年7月

GET range_index/doc/_search
{
  "query": {
    "range": {
      "date": {
        "lt": "1994-07"
      }
    }
  }
}

term查询

term查询用于精准匹配, 对查询的内容, 不进行分词处理

terms查询

与term查询一致,区别在于terms提供了一个数组, 可以允许多值匹配

组合多查询(bool)

bool支持多种条件的查询和过滤

bool支持多种参数, must(必须包含的条件), must_no(必须不包含的条件), should(满足should中的内容可以增加匹配得分), filter(必须匹配, 但是不影响匹配得分)


# bool查询中, 文档必须满足must中的全部条件,并进行相关性算分。filter会过滤不符合条件的文档, 但不进行相关性算分。

GET test_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "lihang"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gt": 20
          }
        }
      }
    }
  }
}

验证查询

验证查询语句是否合法


GET zc_index/doc/_validate/query
{
  "query": {
    "range": {
      "age": {
        "gt1e": 2
      }
    }
  }
}

// 当查询非法的时候,valid会返回false
{
  "valid": false
}

排序

  • desc 降序
  • asc 升序

// 查询job中包含web的,并不进行相关性算分, 按照age的升序排序
GET zc_index/doc/_search
{
  "query": {
    "bool": {
      "filter": {
        "match": {
          "job": "web"
        }
      }
    }
  },
  "sort": [
    {
      "age": {
        "order": "asc"
      }
    }
  ]
}

多值字段(不是多字段)排序。当字段内拥有多个值的时候,可以使用mode,对这些值进行min,max,sum,avg等操作。然后,对计算后的结果进行排序


"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

字符串排序

有时候,我们希望对字符串进行排序(例如: 通过首字母的方式排序字符串), 但是对于text类型, elasticsearch会进行分词处理。如果需要对字符串进行排序,我们不能进行分词处理。但是如果需要全文检索,我们又需要对字符串进行分词处理。

我们可以通过fields对相同的字段,以不同的目的,设置不同的type类型


// job字段设置为text类型,进行全文检索
// job.row 设置为keyword类型, 可以进行排序,精确匹配
PUT zy_index
{
  "mappings": {
    "doc": {
      "properties": {
        "job": {
          "type": "text",
          "fields": {
            "raw": {
              "type": "keyword"
            }
          }
        }
      }
    }
  }
}

// 添加一些测试数据
POST zy_index/doc/_bulk
{"index":{"_id":1}}
{"name":"zhangyue","job":"web node"}
{"index":{"_id":2}}
{"name":"lihang","job":"node java"}
{"index":{"_id":3}}
{"name":"liyubo","job":"web"}
{"index":{"_id":4}}
{"name":"houjiaying","job":"java"}

// 全文检索job含有node
// 通过job.row进行首字母排序
GET zy_index/doc/_search
{
  "query": {
    "match": {
      "job": "node"
    }
  },
  "sort": [
    {
      "job.raw": {
        "order": "asc"
      }
    }
  ]
}

文档的相关性

_score表示文档的相关性,_score越高文档与查询的条件匹配程度越高。

_score的评分标准:

  1. 检索词频率,检索词在字段出现的频率越高,文档的相关性越高
  2. 反向文档频率,检索词在索引中出现的越高,文档的相关性越低(因为大多数文档都出现了该检索词)
  3. 字段长度准则, 检索词出现的字段越长, 相关性越低

索引


# 创建索引
PUT test_index

# 查看索引
GET test_index

# 删除索引
DELETE test_index

创建索引

通过索引一篇文档可以自动创建索引, 索引采用默认的配置。字段会通过动态映射添加类型,当然我们可以指定索引的mapping

删除索引


// 删除全部的索引
DELETE /_all
DELETE /*

动态映射 dynamic mapping

Elasticsearch会将mapping中没有定义的字段,通过dynamic mapping确定字段的数据类型,这是默认的行为。如果对没有出现的未定义字段作出其他的操作,可以通过定义mapping的dynamic配置项目

  • dynamic: true 会自动为新增的字段添加类型
  • dynamic: false 忽略新增的字段
  • dynamic: strict 会抛出异常
PUT hello_index
{
  "mappings": {
    "doc": {
      "dynamic": true
    }
  }
}

重新索引数据

在es中mapping定义后是无法修改的,如果想要修改mapping只能创建新的索引。更多内容Reindex API


POST _reindex
{
  "source": {
    "index": "zc_index"
  },
  "dest": {
    "index": "zc2_index"
  }
}

part2

image

<!–more–>

导入

使用elasticsearch-dump导入文件数据到Es中


// dump的使用方法
elasticdump \
  // JSON文件路径
  --input=/Users/zhangyue/Documents/swmgame-activity-2018.06/swmgame-activity-2018.06.mapping.json \
  // 导入的路径
  --output=http://127.0.0.1:9200/my_index_type \
  --type=mapping
elasticdump \
  --input=/Users/zhangyue/Documents/swmgame-activity-2018.06/swmgame-activity-2018.06.data.json \
  --output=http://127.0.0.1:9200/my_index_type \
  --type=data

搜索

精确值搜索

term查询数字

term查询会查找指定的精确值, 如果不想计算相关度算分, 可以使用filter可以更快的执行操作


// 检索passStatus为2的文档

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "passStatus": {
              "value": 2
            }
          }
        }
      ]
    }
  }
}

// 检索passStatus等于2并且gameId等于G09, 并且查询不计算相关度算分

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      // 不计算相关度算分
      "filter": {
        "bool": {
          "must": [
            {
              "term": {
                "passStatus": {
                  "value": 2
                }
              }
            },
            {
              "term": {
                "gameId.keyword": {
                  "value": "G09"
                }
              }
            }
          ]
        }
      }
    }
  }
}

term查询文本

😔 使用term检索文本的时候, 检索的结果可能与我们预期的结果并不一致, 这是什么原因导致的呢?

💡 我们通过analyzeAPI可以看到, 文档中的text类型的字段, 已经被分词。如果使用term精确查找全文, 是不会匹配到数据的。因为这些全文并没有被存储在倒排索引中。

📖 有两种解决的办法, 解决办法1, 在es中es默认会为字段创建名为keyword的keyword类型的fields字段, 于字段一致但是类型并不一致。可以使用keyword的fields字段进行term查询。解决办法2, 创建新的索引使用新的mapping, 指定字段为keyword类型


// 解决办法1
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "gameId.keyword": {
              "value": "G09"
            }
          }
        }
      ]
    }
  }
}

// 解决办法2
DELETE test5_index
PUT test5_index
{
  "mappings": {
    "doc": {
      "properties": {
        "gameId": {
          "type": "keyword"
        }
      }
    }
  }
}

term查询范围类型


DELETE test_index
PUT test_index
{
  "mappings": {
    "doc": {
      "properties": {
        // 定义age字段为范围类型
        "age": {
          "type": "integer_range"
        }
      }
    }
  }
}

// 添加测试数据
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"age":{"gte":10,"lte":20}}
{"index":{"_id":2}}
{"age":{"gte":21,"lte":30}}

// 查询文档
GET test_index/doc/_search
{
  "query": {
    "term": {
      "age": {
        "value": 11
      }
    }
  }
}

组合过滤器

组件过滤器的组成:

  1. must 必须满足的一组条件
  2. must_not 必须不满足的一组条件
  3. should 可以满足的一组的条件, 如果满足可以增加相关性算分(如果组合过滤器中只有should, 则文档必须满足should中的一项条件)

// 嵌套的bool查询
// 检索gameId等于G09, 或者passStatus等于2并且source等于ios
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "gameId.keyword": {
              "value": "G09"
            }
          }
        },
        {
          "bool": {
            "must": [
              {
                "term": {
                  "passStatus": {
                    "value": "2"
                  }
                }
              },
              {
                "term": {
                  "source.keyword": {
                    "value": "ios"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

terms

terms查询与term查询类似,terms匹配的是一组精确值, 是一个精确值数组


// terms匹配的是一组数组
// 检索passStatus为1或者2的文档

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "terms": {
          "passStatus": [
            1,2
          ]
        }
      }
    }
  }
}

range

  • gt 大于
  • gte 大于等于
  • lt 小于
  • lte 小于等于

数字范围查询 🔢


// passStatus大于等于1并且小于2的文档

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "passStatus": {
            "gte": 1,
            "lt": 2
          }
        }
      }
    }
  }
}

日期范围查询 📅

在使用日期范围查询的时候,需要预先将日期的字段的mapping的type类型设置为date


DELETE test_index
PUT test_index
{
  "mappings": {
    "doc": {
      "properties": {
        // 设置为time类型
        "time": {
          "type": "date"
        }
      }
    }
  }
}

// 添加测试数据
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"time":"1994-06-07"}
{"index":{"_id":2}}
{"time":"1994-10-22"}
{"index":{"_id":3}}
{"time":"1993-07-01"}
{"index":{"_id":4}}
{"time":"2005-01-01"}

// 查询文档的time字段的日期范围是大于1994-10-22, 小于等于2005-01-01
GET test6_index/doc/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "time": {
            "gt": "1994-10-22",
            "lte": "2005-01-01"
          }
        }
      }
    }
  }
}

字符串范围查询 📖

字符串范围使用的是字典顺序(lexicographically)的排序方式。

🏠 什么是字典序?

比如说有一个随机变量X包含1, 2, 3三个数值。

其字典排序就是123 132 213 231 312 321


DELETE test_index
// 添加测试数据
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"name":"abce fg"}
{"index":{"_id":2}}
{"name":"cba twa"}
{"index":{"_id":3}}
{"name":"a cb"}
{"index":{"_id":4}}
{"name":"gfw wfw"}
{"index":{"_id":5}}
{"name":"bgfw wfw"}

GET test_index/doc/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "name": {
            "gte": "a",
            "lt": "c"
          }
        }
      }
    }
  }
}

处理Null值

存在查询

检索文档中至少包含一个非空的文档。(🐶全部为空或者没有该字段的除外)


// 查找game字段至少包含一个非空文档的文档
GET test_index/doc/_search
{
  "query": {
    "exists": {
      "field": "game"
    }
  }
}

缺失查询

查询文档中为null, 或者没有该字段的文档

💡 缺失查询已经在ElasticSearch2.2.0中废弃。可以使用以下的方法代替


GET test_index/doc/_search
{
  "query": {
    "bool": {
      "must_not": {
        "exists": {
          "field": "game"
        }
      }
    }
  }
}

全文搜索

基本概念

基于词项

如term查询不会经过分析阶段,只会在倒排索引中查找精确的词项,并用TF/DF算法为文档进行相关性评分_score。term查询只对倒排索引的词项精确匹配,而不会对查询词进行多样性处理。

基于全文

如match查询和query_string查询。对于日期和整数会将查询字符串当作整数对待。如果查询的是keyword类型的字段,会将查询字符串当作单个单词对待。如果是全文字段,会将查询字符串使用合适的分析器,作出处理,生成一个查询词项的列表,查询会对词项列表中的每一项进行查询,再将结果进行合并。

匹配查询

match查询即可以进行精确检索也可以进行全文检索,以下是match查询的具体的过程:

  1. 检查字段类型, 如果检索的字段是text类型, 那么查询字符串也应当被分析
  2. 分析查询字符串, 将查询字符串经过默认的分析器的处理, match执行的是当个底层的term查询
  3. term查询会在倒排索引中对分析后的查询字符串进行检索
  4. 为匹配的文档评分(检索词频率, 反向文档频率, 字段长度准则)
  5. 为什么在这里测试只创建一个分片, 后面会有介绍

// 只创建一个主分片
PUT /swmgame-activity-2018.06
{ "settings": { "number_of_shards": 1 }} 

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "match": {
          "ipInfo.city": "蚌埠"
        }
      } 
    }
  }
}

多词查询

match查询如果查询的是text类型的全文字段, 那么也会对查询字符串进行分析


// match查询会分别查询蚌埠和龙子湖区两个字段
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "match": {
          "ipInfo.city": "蚌埠 龙子湖区"
        }
      } 
    }
  }
}

如果希望查询的文档, 同时包含所有的关键词, 则可以使用operator使用and操作符


// 查询包含蚌埠并且包含龙子湖区两个字段
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "match": {
          "ipInfo.city": "蚌埠 龙子湖区",
          "operator": "and"
        }
      } 
    }
  }
}

最小匹配数

match查询可以设置minimum_should_match参数,即最小匹配数,如果match查询有四个词项,如果minimum_should_match设置为50%,那么最少匹配2个词项

组合查询

组合查询接受must, must_not, should。如果当DSL中只包含should的时候, should至少有一个必须匹配。同时可以指定should的minimum_should_match参数, 控制查询的精度。控制必须匹配的数量。

多词查询中, 使用or操作符号, 和使用should查询是等价的


// 两条查询是等价的
GET test_index/doc/_search
{
  "query": {
    "match": {
      "title": "fox dog"
    }
  }
}

GET test_index/doc/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "title": {
              "value": "fox"
            }
          }
        },
        {
          "term": {
            "title": {
              "value": "dog"
            }
          }
        }
      ]
    }
  }
}

多词查询中, 使用and操作符, 和使用must查询是等价的


// 两条查询是等价的

GET test_index/doc/_search
{
  "query": {
    "match": {
      "title": {
        "query": "fox dog",
        "operator": "and"
      }
    }
  }
}
GET test_index/doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "title": {
              "value": "fox"
            }
          }
        },
        {
          "term": {
            "title": {
              "value": "dog"
            }
          }
        }
      ]
    }
  }
}

权重

设置boost可以设置查询语句的权重, 大于1权重增大, 0到1之间权重逐渐降低。匹配到权重越高的查询语句, 相关性算分越高


// 安徽的查询语句的权重为3, 匹配到的文档的得分更高

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "passStatus": {
              "value": 2,
              "boost": 2
            }
          } 
        },
        {
          "match": {
            "ipInfo.province.keyword": {
              "query": "安徽",
              "boost": 3
            }
          }
        }
      ]
    }
  }
}

默认的分析器

索引时的分析器:

  1. 首先使用字段映射的分析器
  2. 其次是index默认的分析器
  3. 最后是默认为standard分析器

搜索时的分析器:

  1. 优先查找定义的分析器
  2. 其次是字段映射的分析器
  3. 最后是索引默认的default的分析器, 默认为standard分析器

相关度被破坏

Q: 为什么在之前的测试索引时,需要指定只为这个索引创建一个主分片?

A: 为了避免相关度被破坏。在elasticsearch中计算相关度使用TF/IDF算法,词频/逆向文档词频的算法。如果有5个包含”fox”的文档位于分片1,一个包含“fox”的文档位于分片2。就会导致一个问题, fox在分片1的相关度会降低, 而在分片2的相关度就会非常高。但是在实际的场景中这并不是一个问题,因为在真实的环境中,数据量通常是非常大的,分片之间的差异会被迅速均化,以上的问题只是因为数据量太少了

最佳字段

什么是最佳字段,我们需要首先了解什么是竞争关系。

下面是两条文档, 假设我们需要搜索"Brown fox"两个关键词并且需要同时检索"title"和"body"两个字段的时候。文档2的body字段虽然同时匹配了两个关键词, 但是文档2的title没有匹配到任何的关键词。所以相关性算分, 文档2是低于文档1的。

但是如果取最佳字段的评分,文档2的body字段为最佳字段, 文档2的评分是高于文档1的评分的


{
  "title": "Quick brown rabbits",
  "body":  "Brown rabbits are commonly seen."
}
{
  "title": "Keeping pets healthy",
  "body":  "My quick brown fox eats rabbits on a regular basis."
}

dis_max


// 在最后计算文档的相关性算分的时候, 只会取queries中的相关性的最大值

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "match": {
            "FIELD1": "TEXT1"
          }
        },
        {
          "match": {
            "FIELD2": "TEXT2"
          }
        }
      ]
    }
  }
}

tie_breaker

使用dis_max最佳字段可能存在另外的问题就是。如果同时查询多个字段, 如果一个文档同时匹配了多个字段,但是由于文档的相关性算分取的是最佳字段, 可能导致该文档的相关性算分并不是最高的。

使用tie_breaker参数,可以将其他匹配语句的评分也计算在内。将其他匹配语句的评分结果与tie_breaker相乘, 最后与最佳字段的评分求和得出文档的算分。


GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "dis_max": {
      "tie_breaker": 0.3,
      "queries": [
        {
          "match": {
            "FIELD1": "TEXT1"
          }
        },
        {
          "match": {
            "FIELD2": "TEXT2"
          }
        }
      ]
    }
  }
}

multi_match

多字段的match查询, 为多个字段执行相同的match查询。multi_match查询默认取最佳字段的相关性算分


// 下面两种查询是等同的
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "match": {
            "FIELD1": "TEXT1"
          }
        },
        {
          "match": {
            "FIELD2": "TEXT1"
          }
        }
      ]
    }
  }
}

GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "multi_match": {
      "query": "TEXT1",
      "type": "best_fields", // 取最佳字段的相关性算分
      "fields": [
        "FIELD1",
        "FIELD2",
        "FIELD3^2" // 可以指定的查询字段的权重, 如果匹配FIELD3字段得分将会更高
      ]
    }
  }
}

fields

fields是对同一个字段索引多次, 每一个字段不同的fields可以定义不同的type以及分析器,用做不同的用途


// title为text类型
// title.keyword为keyword类型
// title.english使用english的分析器

PUT test3_index
{
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            },
            "english": {
              "type": "text",
              "analyzer": "english"
            }
          }
        }
      }
    }
  }
}

跨字段实体搜索

比如一个人的信息,包含姓名,电话号码,家庭住址等多个字段。如果想要搜索人的信息,可能需要检索多个字段。

// 姓名, 手机号, 家庭住址字段都包含了一个人的信息
{
  "name": "Frank",
  "mobi": "13053127868",
  "hours": "bengbu"
}

最直接的检索的方式是通过multi_match或者bool的should实现对多个字段的检索


GET test_index/doc/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": "Frank 1305312786"
          }
        },
        {
          "mathch": {
            "mobi": "Frank 13053127868"
          }
        },
        {
          "mathch": {
            "hours": "Frank 13053127868"
          }
        }
      ]
    }
  }
}


{
  "query": {
    "multi_match": {
      "query": "Frank 1305312786",
      "type": "most_fields", // 合并匹配字段的评分, 不使用最佳字段的评分
      "fields": [ "name", "mobi", "hours" ]
    }
  }
}

跨字段检索的问题

  1. 在同一个字段匹配到多个关键词的相关性算分,要小于多个字段匹配同一个关键词的算分。
  2. 使用and操作符, 会导致所有查询变为所有关键词都必须要在同一个字段匹配。
  3. 逆向文档频率的问题, 当一个查询具有较低的逆向文档频率(相关度较高)。可能会削弱较高的逆向文档频率(相关度较低)的作用。

_all

使用copy_to的功能,将多个字段内容拷贝到文档的一个字段中。检索时使用合并的字段进行检索。


PUT test3_index
{
  "mappings": {
    "doc": {
      "properties": {
        "name": {
          "type": "text",
          "copy_to": "info"
        },
        "mobi": {
          "type": "text",
          "copy_to": "info"
        },
        "hours": {
          "type": "text",
          "copy_to": "info"
        },
        "info": {
          "type": "text"
        }
      }
    }
  }
}

// 查询时可以直接通过info字段查询
GET test3_index/doc/_search
{
  "query": {
    "match": {
      "info": "Frank 1305312786"
    }
  }
}

cross-fields

当multi_match的type等于cross-fields时, 它将所有fields中的字段当成一个大字段,并在这个大字段中进行检索。

同时也会解决逆向文档频率的问题, 因为cross-fields会取不同字段中最高的逆向文档频率(相关度最低的)的值。

cross-fields时,所有字段必须为同一种分析器。如果包括了不同分析器的字段,它们会以best_fields的相同方式被加入到查询结果中


// abc efg必须同时出现在FIELD1或者FIELD2文档中
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "multi_match": {
      "query": "abc efg",
      "fields": ["FIELD1", "FIELD2"],
      "type": "most_fields",
      "operator": "and"
    }
  }
}

// abc efg必须出现但是可以在FIELD1或者FIELD2不同的字段中
GET swmgame-activity-2018.06/my_index/_search
{
  "query": {
    "multi_match": {
      "query": "abc efg",
      "fields": ["FIELD1^2", "FIELD2"], // 可以提高字段的权重
      "type": "cross_fields",
      "operator": "and"
    }
  }
}

part3

image

<!–more–>

聚合

基本概念

桶指的是满足特定条件的文档的集合, (比如李航属于陕西桶), 聚合开始执行, 符合条件的文档会放入对应的桶。

指标

对桶内的文档进行统计计算, 例如:最小值, 平均值, 汇总等

指标与桶

桶是可以被嵌套的, 可以实现非常复杂的组合。

国家桶 -> 性别桶 -> 年龄段桶 -> 不同国家不同性别不同年龄段的平均工资

初步聚合

简单的聚合


// 对每一个省份的数据进行聚合
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0, 
  "aggs": {
    "province_info": {
      "terms": {
        "field": "ipInfo.province.keyword",
        "size": 10
      }
    }
  }
}

简单的指标


// 聚合每一个省份的文档, 并对每一个省份的得分求平均值
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "province_info": {
      "terms": {
        "field": "ipInfo.province.keyword",
        "size": 100
      },
      "aggs": {
        "avg_score": {
          "avg": {
            "field": "score"
          }
        }
      }
    }
  }
}

嵌套桶

对聚合的结果进一步聚合。每一层的聚合都可以添加单独的指标, 每一层聚合的指标之间相互独立。


// 首先对每一个国家进行聚合, 并计算每一个国家的平均得分
// 其次对每一个省份进行聚合, 并计算每一个省份的平均得分
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "country_info": {
      "terms": {
        "field": "ipInfo.country.keyword",
        "size": 10
      },
      "aggs": {
        "avg_score": {
          "avg": {
            "field": "score"
          }
        },
        "province_info": {
          "terms": {
            "field": "ipInfo.province.keyword",
            "size": 100
          },
          "aggs": {
            "avg_score": {
              "avg": {
                "field": "score"
              }
            }
          }
        }
      }
    }
  }
}

直方图

什么是直方图

直方图就是柱状图, 创建直方图需要指定一个区间, elasticsearch可以对每一个区间进行聚合操作


// 根据durationCallAll字段,  从最小值开始, 每10000为1个区间进行聚合
GET crm_statistics-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "day_duration_call_all": {
      "histogram": {
        "field": "durationCallAll",
        "interval": 10000,
        "min_doc_count": 0
      }
    }
  }
}

// 返回的部分结果
// ......
{
  "key": 20000,
  "doc_count": 3
},
{
  "key": 30000,
  "doc_count": 3
},
{
  "key": 40000,
  "doc_count": 9
}
// ......

按时间统计

根据时间统计, 可以根据时间类型的字段进行聚合, 区间可以是每一日, 每一周, 每一月, 每一季度等等


GET crm_statistics-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "at": {
      "date_histogram": {
        "field": "at", // 根据时间类型的字段at进行聚合
        "interval": "day", // 区间是每一天
        "min_doc_count": 0, // 可以强制返回空的桶
        "extended_bounds": { // 时间区间
          "min": "2018-05-30",
          "max": "2018-06-22"
        }
      }
    }
  }
}

返回空的桶

  • min_doc_count, 可以强制返回长度为空的桶
  • extended_boundss, 可以设置返回的时间区间(因为elasticsearch默认只返回最小值到最大值之间的桶)

扩展例子

🌰 按时间统计的直方图上进行聚合操作, 并计算度量指标的例子。

下面的DSL, 按时间进行统计直方图, 以每一天作为区间, 并且计算区间内, 每一个省份的平均分数, 以及每一个省份下每一个城市的平均得分

GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "at_date_histogram": {
      "date_histogram": {
        "field": "at",
        "interval": "day",
        "min_doc_count": 0,
        "extended_bounds": {
          "min": "2018-05-01",
          "max": "2018-06-22"
        }
      },
      "aggs": {
        "province_info": {
          "terms": {
            "field": "ipInfo.province.keyword",
            "size": 50
          },
          "aggs": {
            "avg_score": {
              "avg": {
                "field": "score"
              }
            },
            "city_info": {
              "terms": {
                "field": "ipInfo.city.keyword",
                "size": 100
              },
              "aggs": {
                "avg_score": {
                  "avg": {
                    "field": "score"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

限定范围聚合

没有指定query的情况下, 聚合操作针对的是整个索引


// 限定安徽和福建两个省份进行聚合操作
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0, 
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "ipInfo.province.keyword": {
              "value": "安徽"
            }
          }
        },
        {
          "term": {
            "ipInfo.province.keyword": {
              "value": "福建"
            }
          }
        }
      ]
    }
  },
  "aggs": {
    "city_info": {
      "terms": {
        "field": "ipInfo.city.keyword",
        "size": 100
      },
      "aggs": {
        "avg_socre": {
          "avg": {
            "field": "score"
          }
        }
      }
    }
  }
}

全局桶

使用全局桶(global)可以在查询范围内聚合, 也可以全局文档上聚合


GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "query": {
    "bool": {
      "filter": {
        "term": {
          "ipInfo.province.keyword": "安徽"
        }
      }
    }
  },
  "aggs": {
    // 查询限定的范围内聚合
    "city_info": {
      "terms": {
        "field": "ipInfo.city.keyword",
        "size": 100
      }
    },
    "all": {
      "global": {},
      // 在全局范围内聚合
      "aggs": {
        "city": {
          "terms": {
            "field": "ipInfo.city.keyword",
            "size": 1000
          },
          "aggs": {
            "all_avg_score": {
              "avg": {
                "field": "score"
              }
            }
          }
        }
      }
    }
  }
}

过滤和聚合

过滤

参考限定范围的聚合 🚗️ ✈️

过滤桶

对聚合的结果进行过滤

为什么使用过滤桶?

我们可能遇到这种情况, 我们想把查询条件a查询到数据显示到前端页面上, 但同时又想把查询条件a+b的聚合结果显示到页面上。所以在过滤的时候,我们并不能直接使用过滤条件a+b。而聚合桶就可以对聚合的结果进行过滤


// 查询显示的结果的过滤条件是 "安徽"
// 聚合显示的结果的过滤条件是 "安徽" + "蚌埠"
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 20,
  "query": {
    "bool": {
      "filter": {
        "term": {
          "ipInfo.province.keyword": "安徽"
        }
      }
    }
  },
  "aggs": {
    "city_info": {
      "filter": {
        "term": {
          "ipInfo.city.keyword": "阜阳"
        }
      }, 
      "aggs": {
        "city_info": {
          "terms": {
            "field": "ipInfo.city.keyword",
            "size": 20
          }
        }
      }
    }
  }
}

后过滤器

对查询的结果进行过滤

为什么需要后过滤器?

我们可能遇到这种情况, 我们想在查询a条件的情况下对结果作出聚合, 但是又想在查询a+b条件下下显示结果。这种情况下可以使用后过滤器, 对查询的结果进行过滤。


// 查询的条件是 福建 + 厦门
// 聚合的过滤条件是厦门
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 5,
  "query": {
    "bool": {
      "filter": {
        "term": {
          "ipInfo.province.keyword": "福建"
        } 
      }
    }
  },
  "post_filter": {
    "term": {
      "ipInfo.city.keyword": "厦门"
    }
  },
  "aggs": {
    "city_info": {
      "terms": {
        "field": "ipInfo.city.keyword",
        "size": 20
      }
    }
  }
}

聚合排序

内置排序

  • _count 按照文档的数量排序

  • _term 按词项的字符串值的字母顺序排序

  • _key 按每个桶的键值数值排序。只在histogram和date_histogram内使用

  • desc 降序

  • asc 升序


// 文档的内容升序进行排序
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0, 
  "aggs": {
    "province": {
      "terms": {
        "field": "ipInfo.province.keyword",
        "size": 50,
        "order": {
          "_count": "asc"
        }
      }
    }
  }
}

// 按照字符串的顺序进行排序, 只能用在term
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "game_id_info": {
      "terms": {
        "field": "gameId.keyword",
        "size": 50,
        "order": {
          "_term": "asc"
        }
      }
    }
  }
}

// 按照date_histogram或者histogram的key排序
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "date": {
      "date_histogram": {
        "field": "at",
        "interval": "day",
        "min_doc_count": 0,
        // 时间区间按照降序排序
        "order": {
          "_key": "desc"
        }
      }
    }
  }
}

度量排序

直接使用度量的key作为排序的字段

单度量值排序


GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "province_info": {
      "terms": {
        "field": "ipInfo.province.keyword",
        "size": 50,
        "order": {
          "avg_score": "asc"
        }
      },
      "aggs": {
        "avg_score": {
          "avg": {
            "field": "score"
          }
        }
      }
    }
  }
}

多度量值排序

使用点操作符号, 选择单个度量值进行排序


GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "province_info": {
      "terms": {
        "field": "ipInfo.province.keyword",
        "size": 50,
        "order": {
          "info.avg": "asc"
        }
      },
      "aggs": {
        "info": {
          "extended_stats": {
            "field": "score"
          }
        }
      }
    }
  }
}

近似聚合

对于一些复杂的操作,需要在精准性和实时性上作出权衡

近似聚合 —— 去重

cardinality可以实现去重操作, 但是数据量巨大的情况下精确性上可能存在误差, 但是可以设置更大的内存提供去重的精确性


// 查看有多少个城市
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "city_count": {
      "cardinality": {
        "field": "ipInfo.city.keyword"
      }
    }
  }
}

// 统计每一个省份的城市数量
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "country_info": {
      "terms": {
        "field": "ipInfo.country.keyword",
        "size": 10
      },
      "aggs": {
        "province_info": {
          "terms": {
            "field": "ipInfo.province.keyword",
            "size": 50
          },
          "aggs": {
            "city": {
              "terms": {
                "field": "ipInfo.city.keyword",
                "size": 50
              }
            },
            "city_count": {
              "cardinality": {
                "field": "ipInfo.city.keyword"
              }
            }
          }
        }
      }
    }
  }
}

precision_threshold

cardinality精确性与内存的使用量有关,通过配置precision_threshold参数,设置去重需要的固定内存使用量。内存使用量只与你配置的精确度相关。

precision_threshold 设置为100的时候,去重的需要的内存是 100 * 8 的字节。precision_threshold 接受的范围在0–40,000之间


// 统计每一个国家的数量
GET swmgame-activity-2018.06/my_index/_search
{
  "size": 0,
  "aggs": {
    "country_count": {
      "cardinality": {
        "field": "ipInfo.country.keyword"
      }
    }
  }
}

近似聚合 —— 百分位

什么是百分位

以下是百度百科的内容

统计学术语,如果将一组数据从小到大排序,并计算相应的累计百分位,则某一百分位所对应数据的值就称为这一百分位的百分位数。可表示为:一组n个观测值按数值大小排列。如,处于p%位置的值称第p百分位数。

百分位通常用第几百分位来表示,如第五百分位,它表示在所有测量数据中,测量值的累计频次达5%。以身高为例,身高分布的第五百分位表示有5%的人的身高小于此测量值,95%的身高大于此测量值。

高等院校的入学考试成绩经常以百分位数的形式报告。比如,假设某个考生在入学考试中的语文部分的原始分数为54分。相对于参加同一考试的其他学生来说,他的成绩如何并不容易知道。但是如果原始分数54分恰好对应的是第70百分位数,我们就能知道大约70%的学生的考分比他低,而约30%的学生考分比他高。

百分位计算


// 添加测试数据, 网络延迟数据
PUT test9_index
POST /test9_index/doc/_bulk
{ "index": {}}
{ "latency" : 100, "zone" : "US", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 80, "zone" : "US", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 99, "zone" : "US", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 102, "zone" : "US", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 75, "zone" : "US", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 82, "zone" : "US", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 100, "zone" : "EU", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 280, "zone" : "EU", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 155, "zone" : "EU", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 623, "zone" : "EU", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 380, "zone" : "EU", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 319, "zone" : "EU", "timestamp" : "2014-10-29" }

计算网络延迟的百分位和网络延迟的平均数


GET test9_index/doc/_search
{
  "size": 0,
  "aggs": {
    // 计算网络延迟的平均数
    "avg_ping": {
      "avg": {
        "field": "latency"
      }
    },
    // 计算网络延迟的百分位
    "percentiles_ping": {
      "percentiles": {
        "field": "latency"
      }
    }
  }
}

下面是返回的聚合结果,可以看到平均数有时并不能体现异常的数据。约有25%的用户的网络延迟是大于289的

"aggregations": {
  "avg_ping": {
    "value": 199.58333333333334
  },
  "percentiles_ping": {
    "values": {
      "1.0": 75.55,
      "5.0": 77.75,
      "25.0": 94.75,
      "50.0": 101,
      "75.0": 289.75,
      "95.0": 489.34999999999985,
      "99.0": 596.2700000000002
    }
  }
}

查看网络的延迟是否和地区有关,在聚合的基础上进行百分位计算。根据结果可以得知, 欧洲用户(EU)的网络延迟更高


GET test9_index/doc/_search
{
  "size": 0,
  "aggs": {
    "zone_info": {
      "terms": {
        "field": "zone.keyword",
        "size": 10
      },
      "aggs": {
        "zone_ping": {
          "percentiles": {
            "field": "latency"
          }
        }
      }
    }
  }
}

百分位等级

得知具体的数值所占的百分位。以下是查看ping为75, 623的用户所占的百分位。


GET test9_index/doc/_search
{
  "size": 0,
  "aggs": {
    "ping": {
      "percentile_ranks": {
        "field": "latency",
        "values": [
          75,
          623
        ]
      }
    }
  }
}

可以看到如下的结果, ping小于等于75的用户占到了4.16%, 有22.5%的用户ping是大于623的


"aggregations": {
  "ping": {
    "values": {
      "75.0": 4.166666666666666,
      "623.0": 87.5
    }
  }
}
回到顶部