跳至主要內容

Elasticsearch - 基础教程

Zenghr大约 10 分钟

ES - 查询和聚合的基础使用

本文学习 Elasticsearch 基础的 API 使用以及使用官网提供的数据来操作学习:查询以及聚合操作

参考资料

Elasticsearch - 基础 API

在先前的文档中我们有说到 Elasticsearch 是在 Lucene 基础上,提供了 REST API 的操作接口,所以我们操作 Elasticsearch 的所有增删改查都是使用 GET、POST、PUT、DELETE 请求方式来区分

_cat 操作

# 查看所有节点
GET /_cat/indices
# 查看 es 健康状况
GET /_cat/health
# 查看主节点
GET /_cat/master
# 查看所有索引 - 类似 show database;
GET /_cat/indices

索引一个文档(保存)

保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识

PUT customer/_doc/1 - 在 customer 索引下的 _doc 类型下保存 1 号数据为

其中就是 type 是 _doc,这是 ES 团队选择的一种废弃Type的方案,使用 _doc 后面可以无缝对接 API

# 添加一个文档、一般 put 用于修改
PUT customer/_doc/1
{
  "name": "zenghr",
  "age": 18,
  "sex": 0
}

# post 会自动分配 id
POST customer/_doc
{
  "name": "test",
  "age": 18,
  "sex": 0
}

返回结果

{
  "_index" : "customer",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

PUT 和 POST 都可以
POST 新增:如果不指定 id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号
PUT 可以新增可以修改:PUT 必须指定 id;由于 PUT 需要指定 id,我们一般都用来做修改 操作,不指定 id 会报错

查询文档

GET customer/_doc/1
# 返回结果
{
  "_index" : "customer",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "zenghr",
    "age" : 18,
    "sex" : 0
  }
}
  • _index: 在哪个索引
  • _type: 在哪个类型
  • _id: 记录 id
  • _version: 版本号
  • _seq_no: 并发控制字段,每次更新就会+1,用来做乐观锁
  • _primary_term: 同上,主分片重新分配,如重启,就会变化
  • _source: 真正的内容
  • found: 数据是否存在

更新文档

# 修改文档
PUT customer/_doc/1
{
  "name": "test",
  "age": 18,
  "sex": 0
}
# 或者
POST customer/_doc/1
{
  "name": "test",
  "age": 18,
  "sex": 0
}
# 或者
POST customer/_doc/1/_update
{
  "doc": {
    "name": "test",
    "age": 18,
    "sex": 0
  }
}
# 执行返回结果
{
  "_index" : "customer",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

POST 和 PUT 的区别:

  • POST 操作: 会对比源文档数据,如果相同不会有什么操作,文档 version 不增加

  • PUT 操作: 总会将数据重新保存并增加 version 版本

带_update 对比元数据如果一样就不进行任何操作。 看场景:
对于大并发更新,不带 update;
对于大并发查询偶尔更新,带 update; 对比更新,重新计算分配规则

删除文档或索引

# 创建一个索引
PUT customer
# 删除文档
DELETE customer/_doc/1
# 删除一整个索引
DELETE customer

bulk 批量 API

在单个 API 调用中执行多个索引或删除操作,这减少了开销并且可以大大提高索引速度

官方批量操作文档open in new window

POST customer/_doc/_bulk
{"index":{"_id":"2"}}
{"name":"test1","age":18,"sex":0}
{"delete":{"_id":"2"}}
{"index":{"_id":"3"}}
{"name":"test2","age":18,"sex":0}



 


注意

请注意 delete 动作不能有请求体,它后面跟着的是另外一个操作

行与行之间要以换行符相隔 \n

批量操作语法

{ action: { metadata }}\n
{ request body        }\n
{ action: { metadata }}\n
{ request body        }\n
...

学习准备:导入测试数据

提示

为了方便我们学习 Elasticsearch 的 API 操作,官方提供了测试数据,下面我们需要批量导入测试数据

下载测试数据

数据的 索引(Index) 是 bank,测试数据 JOSN 文件下载地址open in new window

数据的格式如下:

{"index": {"_id": "1"}}
{
	"account_number": 1,
	"balance": 39225,
	"firstname": "Amber",
	"lastname": "Duke",
	"age": 32,
	"gender": "M",
	"address": "880 Holmes Lane",
	"employer": "Pyrami",
	"email": "amberduke@pyrami.com",
	"city": "Brogan",
	"state": "IL"
}

批量插入数据

查看测试数据的格式我们发现数据格式是 bulk 的数据语法,我们可以使用批量操作的 API _bulk 来插入数据

但是官方也提供了另外一种批量操作方式,可以直接导入 Json 文件,插入数据,这里我们使用导入文件的方式

accounts.json 拷贝至指定目录,我这里的目录是 Desktop/accounts.json

然后执行

curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@Desktop/accounts.json"

查询数据 - Kibana

为了方便测试,我们使用 kibana 的dev tool来进行学习测试:

00PrB5
00PrB5

进阶检索

ES 支持两种基本方式检索 :

  • 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)
  • 另一个是通过使用 REST request body 来发送它们(uri+请求体)

检索信息

搜索API的最基础的形式是没有指定任何查询的空搜索,它简单地返回集群中所有索引下的所有文档:

GET /bank/_doc/_search
# 请求参数方式检索
GET bank/_search?q=*&sort=account_number:asc

返回的结果像这样:

{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "account_number" : 1,
          "balance" : 39225,
          "firstname" : "Amber",
          "lastname" : "Duke",
          "age" : 32,
          "gender" : "M",
          "address" : "880 Holmes Lane",
          "employer" : "Pyrami",
          "email" : "amberduke@pyrami.com",
          "city" : "Brogan",
          "state" : "IL"
        }
      },
      // ...
    ]
  }
}

相关字段解释

  • took – Elasticsearch运行查询所花费的时间(以毫秒为单位)
  • timed_out –搜索请求是否超时
  • _shards - 搜索了多少个碎片,以及成功,失败或跳过了多少个碎片的细目分类。
  • max_score – 找到的最相关文档的分数
  • hits.total.value - 找到了多少个匹配的文档
  • hits.sort - 文档的排序位置(不按相关性得分排序时)
  • hits._score - 文档的相关性得分(使用match_all时不适用)
  • hits.hits - 实际的搜索结果数组(默认为前 10 的文档)

uri+请求体进行检索

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ]
}

Query DSL

查询表达式(Query DSL)是一种非常灵活又富有表现力的 查询语言。 Elasticsearch 使用它可以以简单的 JSON 接口来展现 Lucene 功能的绝大部分

基本语法格式

要使用这种查询表达式,只需将查询语句传递给 query 参数:

GET /bank/_search
{
    "query": YOUR_QUERY_HERE
}

空查询(empty search){}— 在功能上等价于使用 match_all 查询, 正如其名字一样,匹配所有文档:

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

查询语句结构

一个查询语句的典型结构:

{
    "query_name": {
        "argument": value,
        "argument": value,...
    }
}

如果是针对某个字段,那么它的结构如下:

{
    "query_name": {
        "field_name": {
            "argument": value,
            "argument": value,...
        }
    }
}

举个例子,你可以使用 match 查询语句 来查询 bank 索引下 email 字段中包含 cedward.com 的 账号信息:

GET bank/_search
{
  "query": {
    "match": {
      "email": "cedward.com"
    }
  }
}
  • query 定义如何查询
  • match_all 查询类型【代表查询所有的所有】,es 中可以在 query 中组合非常多的查
    询类型完成复杂查询
  • 除了 query 参数之外,我们也可以传递其它的参数以改变查询结果。如 sort,size
  • from+size限定,完成分页功能
  • sort排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准

返回部分字段

如果只想要返回部分的字段,可以使用 _source 指定字段

# 返回部分字段
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 5,
  "_source": [
    "age",
    "balance"
  ]
}

match【匹配查询】

如果要在字段中搜索特定字词,可以使用match

  • 全文检索
GET bank/_search
{
  "query": {
    "match": {
      "email": "cedward.com"
    }
  }
}
UphieL
UphieL

最终查询出 email 中包含 cedward.com 单词的所有记录
match 当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分

  • 多个单词 - 分词 + 全文检索
GET bank/_search
{
  "query": {
    "match": {
      "email": "cedward.com pyrami.com"
    }
  }
}

最终查询出 email 中包含 cedward.comopen in new window 或者 pyrami.comopen in new window 或者 cedward.comopen in new window pyrami.comopen in new window 的所有记录,并给出相关性得分

match_phrase【短语匹配】

将需要匹配的值当成一个 整体单词(不分词) 进行检索

GET /bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill lane"
    }
  }
}

查出 address 中包含 mill road 的所有记录,并给出相关性得分

multi_match【多字段匹配】

GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill",
      "fields": [
        "state",
        "address"
      ]
    }
  }
}

state 或者 address 包含 mill

bool【复合查询】

bool 用来做复合查询:
复合语句可以合并 任何 其它查询语句,包括复合语句,了解这一点是很重要的。这就意味 着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑

  • must - 必须达到must列举的所有条件
DVw0gY
DVw0gY
  • should: 应该达到 should 列举的条件,如果达到会 增加相关文档的评分 ,并不会改变 查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会 被作为默认匹配条件而去改变查询结果
Q2WPhu
Q2WPhu

query 只有 should 的情况

mAxF6y
mAxF6y
  • must_not 必须不是指定的情况
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        },
        {
          "match": {
            "gender": "M"
          }
        }
      ],
      "should": [
        {
          "match": {
            "address": "lane"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "email": "neteria.com"
          }
        }
      ]
    }
  }
}

address 包含 mill,并且 gender 是 M,如果 address 里面有 lane 最好不过,但是 email 必 须不包含 neteria.comopen in new window

事件描述
must子句(查询)必须出现在匹配的文档中,并将有助于得分
filter子句(查询)必须出现在匹配的文档中,但它以不评分、过滤模式来进行,只是根据过滤标准来排除或包含文档
should子句(查询)应该出现在匹配的文档中,出现则得分,没有也不要紧
must_not子句(查询)不能出现在匹配的文档中

range【区间查询】

range 查询找出那些落在指定区间内的数字或者时间

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}

被允许的操作符如下:

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

filter【结果过滤】

并不是所有的查询都需要产生分数,特别是那些仅用于 "filtering"(过滤)的文档。为了不计算分数 Elasticsearch 会自动检查场景并且优化查询的执行

增加带过滤器(filtering)的查询

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        }
      ],
      "should": [
        {
          "match": {
            "address": "lane"
          }
        }
      ],
      "filter": {
        "range": {
          "balance": {
            "gte": 10000,
            "lte": 20000
          }
        }
      }
    }
  }
}

过滤 balance 大于等于 10000 并且 小于等于 20000 的数据,并且address 包含 mill,如果 address 里面有 lane 最好不过

CyBWLf
CyBWLf

term 查询

和 match 一样。匹配某个属性的值。全文检索字段用 match,其他非 text 字段匹配用 term

term 查询被用于 精确值匹配 ,这些精确值可能是数字、时间、布尔或者那些 not_analyzed 的字符串:

{ "term": { "age":    26           }}
{ "term": { "date":   "2014-09-01" }}
{ "term": { "public": true         }}
{ "term": { "tag":    "full_text"  }}

Aggregation【聚合查询】

提示

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 SQL GROUP BYSQL 聚合函数。在 Elasticsearch 中,您有执行搜索返回 hits(命中结果),并且同时返 回聚合结果,把一个响应中的所有 hits(命中结果)分隔开的能力。这是非常强大且有效的, 您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用 一次简洁和简化的 API 来避免网络往返

简单聚合

比如我们希望计算出account每个州的统计数量, 使用aggs关键字对state字段聚合,被聚合的字段无需对分词统计,所以使用state.keyword对整个字段统计

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}
wFOuAC
wFOuAC

因为无需返回条件的具体数据, 所以设置size=0,返回hits为空。

doc_count表示bucket中每个州的数据条数

复杂查询 - 聚合排序

  • 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资,并且通过聚合结果排序
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_avg": {
      "terms": {
        "field": "age",
        "size": 1000,
        "order": {
          "banlances_avg": "desc"
        }
      },
      "aggs": {
        "banlances_avg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 1000
}
A9ERdl
A9ERdl
  • 查出所有年龄分布,并且这些年龄段中 M 的平均薪资和 F 的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_agg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "gender_agg": {
          "terms": {
            "field": "gender.keyword",
            "size": 100
          },
          "aggs": {
            "balance_avg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "balance_avg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 1000
}
MKwEkH
MKwEkH