EKL

概述:EKL

[TOC]

一、基础知识

Kibana 是一款开源的数据分析和可视化平台,是 Elasticsearch 的web管理后台。

Kibana Query Language (KQL)查询语法是Kibana为了简化ES查询设计的一套简单查询语法,Kibana支持索引字段和语法补全。

本文提及的工具均依赖JDK版本:JDK 1.8以上,没有安装jdk可以安装jdk,配置好Java环境变量。

ELK = Elasticsearch + Logstash + Kibana

  • Elasticsearch - 前面简介提到过,解决海量数据搜索问题。
  • Logstash - 解决数据同步问题,因为我们数据一般存储在Mysql之类的数据库中,需要将数据导入到ES中,Logstash就支持数据同步、数据过滤、转换功能。
  • Kibana - Elasticsearch数据可视化支持,例如:通过各种图表展示ES的查询结果,也可以在Kibana通过ES查询语句分析数据,起到类似ES Web后台的作用。

二、Elasticsearch (🌟 )

Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎, 国内简称ES,Elasticsearch是用java开发的,底层基于Lucene, Lucene是一种全文检索的搜索库,直接使用Lucene还是比较麻烦的,Elasticsearch在Lucene的基础上开发了一个强大的搜索引擎。

2.1 Install

Elasticsearch - Download

解压缩 & 启动(macOS)

1
2
3
4
5
6
7
8
# 解压
tar -zxvf elasticsearch-7.5.1-linux-x86_64.tar.gz

# 切换到安装目录
cd elasticsearch-7.5.1

# 启动
./bin/elasticsearch

访问

1
http://localhost:9200/

2.2 debug Elasticsearch by Restful api

elasticsearch是以http Restful api的形式提供接口,我们要操作ES,只要调用http接口就行,ES的默认端口是9200

2.2.1 curl demo

1
curl -X GET "localhost:9200/_cat/health?v&pretty"

2.2.2 postman demo

1
GET http://localhost:9200/_search

2.3 存储结构

Elasticsearch是NOSQL类型的数据库

2.3.1 index(索引)

在Elasticsearch中索引(index)类似mysql的表,代表文档数据的集合,文档指的是ES中存储的一条数据。

2.3.2 type(文档类型)

在新版的Elasticsearch中,已经不使用文档类型了,在Elasticsearch老的版本中文档类型,代表一类文档的集合,index(索引)类似mysql的数据库、文档类型类似Mysql的表。

index(索引)就类似mysql的表的概念,ES没有数据库的概念了。

提示:在Elasticsearch7.0以后的版本,已经废弃文档类型了,可以直接将index(索引)类比Mysql的表。

2.3.3 Document(文档)

Elasticsearch是面向文档的数据库,文档是最基本的存储单元,文档类似mysql表中的一行数据。

简单的说在ES中,文档指的就是一条JSON数据。

Elasticsearch中文档使用json格式存储,因此存储上比Mysql要灵活的多,Elasticsearch支持任意格式的json数据。

例如:一个订单数据,我们可以将复杂的Json结构保存到Elasticsearch中, mysql的就无法这样存储数据。

1
2
3
4
5
6
7
8
9
10
11
12
{
"id": 12,
"status": 1,
"total_price": 100,
"create_time": "2019-12-12 12:20:22",
"user" : { // 嵌套json对象
"id" : 11,
"username": "junmingguo",
"phone": "13500001111",
"address" : "上海长宁区001号"
}
}

文档中的任何json字段都可以作为查询条件。

文档的json格式没有严格限制,可以随意增加、减少字段,甚至每一个文档的格式都不一样也可以。

例子:在同一个索引存中,存储格式完全不一样的文档数据

1
2
3
{"id":1, "username":"junmingguo"}
{"id":1, "title":"好看的包包", "price": 30}
{"domain":"abc.com", "https": true}

提示:虽然文档的格式没有限制,可以随便存储任意格式数据,但是,实际业务中不会这么干,通常一个索引只会存储格式相同的数据,例如:订单索引,只会保存订单数据,不会保存商品数据,否则你会被自己搞死,自己都不知道里面存的是什么数据。

2.3.4 Field(文档字段)

文档由多个json字段(Field)组成, 这里的字段类似mysql中表的字段。

当然Elasticsearch中字段也有类型的,下面是常用的字段类型:

  • 数值类型(包括: long、integer、short、byte、double、float)
  • text - 支持全文搜索
  • keyword - 不支持全文搜索,例如:email、电话这些数据,作为一个整体进行匹配就可以,不需要分词处理。
  • date - 日期类型
  • boolean

后面的章节会专门介绍字段类型,Elasticsearch支持的字段类型远比这里介绍的多。

提示:Elasticsearch支持动态映射,我们可以不必预先定义文档的json结构和对应的字段类型,Elasticsearch会自动推断字段的类型。

2.3.5 mapping (映射)

Elasticsearch的mapping (映射)类似mysql中的表结构定义,每个索引都有一个映射规则,我们可以通过定义索引的映射规则,提前定义好文档的json结构和字段类型,如果没有定义索引的映射规则,Elasticsearch会在写入数据的时候,根据我们写入的数据字段推测出对应的字段类型,相当于自动定义索引的映射规则。

提示:虽然Elasticsearch的自动映射功能很方便,但是实际业务中,对于关键的字段类型,通常预先定义好,避免Elasticsearch自动生成的字段类型不是你想要的类型,例如: ES默认将字符串类型数据自动定义为text类型,但是关于手机号,我们希望是keyword类型,这个时候就需要通过mapping预先定义号对应的字段类型了。

2.3.6 类比mysql存储结构

Elasticsearch存储结构 MYSQL存储结构
index(索引)
文档 行,一行数据
Field(字段) 表字段
mapping (映射) 表结构定义

2.4 CRUD 操作

2.4.1 文档

在Elasticsearch中,文档其实就是一条JSON数据。

JSON数据格式可以非常复杂,也可以很简单。

JSON文档的例子:

1
2
3
4
5
6
7
8
9
10
11
12
{
"id": 12,
"status": 1,
"total_price": 100,
"create_time": "2019-12-12 12:20:22",
"user" : { // 嵌套json对象
"id" : 11,
"username": "jimmy",
"phone": "13500001111",
"address" : "上海长宁区001号"
}
}

ES中索引类似MYSQL表的概念,索引包含多个文档数据。

提示: ES 索引不要求,同一个索引中的所有JSON文档的格式都一样,甚至可以在同一个索引中保存完全不一样格式的JSON数据,当然不推荐这么做,建议同一个索引仅保存格式相同的数据,例如:订单索引,仅保存订单数据,不要保存其他业务数据。

2.4.2 文档元数据

文档元数据,指的是插入JSON文档的时候,Elasticsearch为这条数据,自动生成的系统字段

元数据的字段名都是以下划线开头的。

常见的元数据如下:

  • _index - 代表当前JSON文档所属的文档名字
  • _type - 代表当前JSON文档所属的类型,虽然新版ES废弃了type的用法,但是元数据还是可以看到。
  • _id - 文档唯一Id, 如果我们没有为文档指定id,系统会自动生成
  • _source - 代表我们插入进去的JSON数据
  • _version - 文档的版本号,每修改一次文档数据,字段就会加1, 这个字段新版的ES已经不使用了
  • _seq_no - 文档的版本号, 替代老的_version字段
  • _primary_term - 文档所在主分区,这个可以跟_seq_no字段搭配实现乐观锁。

下面是从ES查询出来的一条文档的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"_index" : "order",
"_type" : "_doc",
"_id" : "1",
"_version" : 1, // 老ES版本的文档版本号,最新版本已经不使用了
"_seq_no" : 0, // 新ES版本的文档版本号
"_primary_term" : 1, // 主分区id
"_source" : { // _source字段保存的就是我们插入到ES中的JSON数据
"id" : 1,
"status" : 1,
"total_price" : 100,
"create_time" : "2019-12-12 12:20:22",
"user" : {
"id" : 11,
"username" : "junmingguo",
"phone" : "13500001111",
"address" : "上海长宁区001号"
}
}
}

2.4.3 插入文档

在Elasticsearch插入一个JSON文档,又叫索引文档, 注意这里的索引跟前面提到的文档所属的索引名,不是一回事,很晕吧,其实主要翻译问题,我们将数据插入到ES的过程,其实就是创建索引的过程,所以插入文档,也叫做索引文档,这里索引是动词, 而文档属于哪个索引(index),这里的索引代表一个分类,有数据库的概念,是个名词。

索引文档 = ES插入数据

插入文档的语法:

1
2
3
4
5
PUT /{index}/{type}/{id}
{
"field": "value",
...
}

PUT代表发送一个http put请求, 后面的URL参数说明:

  • {index} - 索引名
  • {type} - 文档类型名 - 新版的Elasticsearch为了兼容老版本,只允许指定一个类型,随便设置一个类型名就行。
  • {id} - 文档的唯一id, 可以不指定, 如果不指定id, 需要使用POST发送请求

提示:通过curl命令,或者postman测试的话,要加上ES接口地址即可,ES默认地址是http://localhost:9200/。

例子:插入一条JSON数据,到order索引中,文档类型是_doc, 文档id是1

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT /order/_doc/1
{
"id": 1,
"status": 1,
"total_price": 100,
"create_time": "2019-12-12 12:20:22",
"user" : {
"id" : 11,
"username": "tizi365",
"phone": "13500001111",
"address" : "上海长宁区001号"
}
}

提示:将例子代码,直接贴到Kibana的console中,就可以执行, 在console页面右侧可以直接看到执行结果。

提示:ES支持动态映射,可以根据我们插入的JSON数据,自动推测出JSON字段的类型,所以我们不需要在ES中提前定义JSON的格式。

2.5 eg. 查询文档

根据文档ID查询文档。

语法:

1
GET /{index}/{type}/{id}

例子:

1
GET /order/_doc/1

返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"_index" : "order",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"id" : 1,
"status" : 1,
"total_price" : 100,
"create_time" : "2019-12-12 12:20:22",
"user" : {
"id" : 11,
"username" : "junmingguo",
"phone" : "13500001111",
"address" : "上海长宁区001号"
}
}
}

2.6 eg. 更新文档

(1)更新整个文档

更新整个文档语法跟前面介绍的插入文档的语法一模一样,只要ID相同就会直接覆盖之前的文档。

(2)文档局部更新

如果我们只想更新json文档的某些字段,可以使用局部更新语法:

1
2
3
4
5
6
POST /{index}/_update/{id}
{
"doc":{ // 在doc字段中指定需要更新的字段
// 需要更新的字段列表
}
}

例子:更新order索引中,文档id等于1的json文档,更新status和total_price两个字段

1
2
3
4
5
6
7
POST /order/_update/1
{
"doc":{
"status":3,
"total_price":200
}
}

提示:虽然说elasticsearch支持通过Api更新文档,但是ES底层文档是不可变的,每次修改文档,本质上是创建一个新的文档,然后把老的文档标记成删除。

2.7 eg. 删除文档

删除语法:

1
DELETE /{index}/{type}/{id}

例子:

1
DELETE /order/_doc/1

说明:ES最强大的特性是搜索,本节没有介绍ES的文档搜索语法,后面的章节会单独介绍。

2.8 基于乐观锁的并发控制

Mysql 通过加锁确保数据原子性

Elasticsearch通过乐观锁确保文档的原子性

Elasticsearch的乐观锁是基于版本号实现的,CRUD的元数据中_seq_no、version,都代表当前文档的版本号,每次更新、删除文档的时候,版本号都会+1,ES就是根据版本号实现乐观锁。

提示:本教程使用的Elasticsearch7.5.x版本,已经不再使用_version字段作为乐观锁判断的依据,主要使用_seq_no作为版本号,结合_primary_term字段实现乐观锁控制。

首先插入一个文档

1
2
3
4
5
6
7
PUT /order/_doc/2
{
"id": 1,
"status": 1,
"total_price": 100,
"create_time": "2019-12-12 12:20:22"
}

然后我们查询下插入的文档,观察下版本号是多少?

1
GET /order/_doc/2

返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"_index" : "order",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"_seq_no" : 6, // 版本号是6
"_primary_term" : 1, // 所在主分区是1
"found" : true,
"_source" : {
"id" : 1,
"status" : 1,
"total_price" : 100,
"create_time" : "2019-12-12 12:20:22"
}
}

我们现在以指定的版本号更新数据

1
2
3
4
5
6
7
PUT /order/_doc/2?if_seq_no=6&if_primary_term=1
{
"id": 1,
"status": 2,
"total_price": 300,
"create_time": "2019-12-12 12:20:22"
}

提示:注意观察url,多了两个参数,if_seq_no参数,指定了当前文档的版本号是6,if_primary_term参数指定文档所在的主分区,如果ES发现文档当前的版本号跟我们指定的版本号相等,就会更新文档,否则直接忽略(报错)。

重复执行上面的语句,ES就会输出下面的错误提示:版本冲突 (version conflict)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[2]: version conflict, required seqNo [6], primary term [1]. current document has seqNo [7] and primary term [1]",
"index_uuid": "CRQJQH4uTA2ffetowYCMNA",
"shard": "0",
"index": "order"
}
],
"type": "version_conflict_engine_exception",
"reason": "[2]: version conflict, required seqNo [6], primary term [1]. current document has seqNo [7] and primary term [1]",
"index_uuid": "CRQJQH4uTA2ffetowYCMNA",
"shard": "0",
"index": "order"
},
"status": 409
}

我们可以通过版本号实现乐观锁,控制出现并发修改文档的时候,确保数据不会出问题。

2.9 数据类型和映射

2.9.1 ES数据基础类型

下面是ES支持的数据类型:

  • 字符串
    • 主要包括: text和keyword两种类型,keyword代表精确值不会参与分词,text类型的字符串会参与分词处理。
  • 数值
    • 包括: long, integer, short, byte, double, float
  • 布尔值
    • boolean
  • 时间
    • date
  • 数组
    • 数组类型不需要专门定义,只要插入的字段值是json数组就行。
  • GEO类型
    • 主要涉及地理信息检索、多边形区域的表达,后面GEO相关的章节单独讲解

提示:text类型,支持全文搜索,因为text涉及分词,所以可以配置使用什么分词器,尤其涉及中文分词,这些涉及全文搜索的内容,请参考后面的全文搜索章节。

2.9.2 精确值 & 全文类型

精确值通常指的就是数值类型、时间、布尔值、字符串的keyword类型,这些不可分割的数据类型,精确值搜索效率比较高,精确值匹配类似MYSQL中根据字段搜索,例如:拿一个手机号去搜索数据,对于每一个文档的手机号字段,要么相等,要么不等,不会做别的计算。

全文类型,指的就是text类型,会涉及分词处理,存储到ES中的数据不是原始数据,是一个个关键词。

例如:我们有一个title字段,数据类型是text, 我们插入”上海复旦大学”这个字符串,经过分词处理,可能变成:”上海”、”复旦大学”、”大学” 这些关键词,然后根据这些关键词建倒排索引。

提示:实际项目中,如果不需要模糊搜索的字符类型,可以选择keyword类型,例如:手机号、email、微信的openid等等,如果选text类型,可能会出现搜出一大堆相似的数据,而且不是精确的数据。

全文搜索后面的章节单独介绍。

2.9.3 自动映射 (dynamic mapping)

前面的章节我们没有预先定义文档的映射(数据类型),也可以插入数据,因为ES默认会自动检测我们插入的数据的类型,相当于自动定义文档类型(mapping)。

自动映射的缺点就是会出现ES映射的数据类型,不是我们想要的类型,例如:手机号,我们希望是一个精确值,使用keyword类型,ES映射成为了text类型,这就不符合业务预期了。

提示:实际项目中,我们通常会预先定义好ES的映射规则,也就是类似MYSQL一样,提前定义好表结构。

2.9.4 自定义文档的数据类型

语法:

1
2
3
4
5
6
7
8
9
10
PUT /{索引名字}
{
"mappings": { // 表示定义映射规则
"properties": { // 定义属性,也就是字段类型
"字段名1": { "type": "字段类型" },
"字段名2": { "type": "字段类型" }
...(提示:最后一行末尾不要加逗号)...
}
}
}

例子:创建一个订单索引,索引名字order

订单索引结构如下表:

字段名 ES类型 说明
id integer 订单id,整数
shop_id integer 店铺Id, 整数
user_id integer 用户id, 整数
order_no keyword 订单编号,字符串类型,精确值
create_at date 订单创建时间,日期类型
phone keyword 电话号码,字符串类型,精确值
address text 用户地址,字符串类型,需要模糊搜索

创建ES索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT /order
{
"mappings": {
"properties": {
"id": { "type": "integer" },
"shop_id": { "type": "integer" },
"user_id": { "type": "integer" },
"order_no": { "type": "keyword" },
"create_at": { "type": "date", "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"},
"phone": { "type": "keyword" },
"address": { "type": "text" }
}
}
}

2.9.5 查询索引的映射规则

如果想知道索引的映射规则(索引结构)是怎么样的,可以通过下面语法查询。

语法:

1
2
3
4
GET /索引名/_mapping
{

}

例子:查询上面的订单索引的映射规则

1
2
3
GET /order/_mapping
{
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"order" : {
"mappings" : {
"properties" : {
"address" : {
"type" : "text"
},
"create_at" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"id" : {
"type" : "integer"
},
"order_no" : {
"type" : "keyword"
},
"phone" : {
"type" : "keyword"
},
"shop_id" : {
"type" : "integer"
},
"user_id" : {
"type" : "integer"
}
}
}
}
}

2.9.6 JSON嵌套类型定义

例如下面json,我们如何在ES中定义。

1
2
3
4
5
6
7
8
{
"order_no" : "20200313120000123123",
"shop_id" : 12,
"user" : {
"id" : 100,
"nickname" : "dacui",
}
}

这里user属性是一个Object类型,json类型本身支持这种通过对象无线嵌套的结构。

ES的映射也支持Object类型,嵌套json的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT /order_v2
{
"mappings": {
"properties": { // 第一层json属性定义
"order_no": { "type": "keyword" },
"shop_id": { "type": "integer" },
"user": { // user属性是Object类型,可以单独定义属性类型
"properties" : { // 第二层user对象的属性定义
"id": { "type": "integer" },
"nickname": { "type": "text" }
}
}
}
}
}

通过上面例子,如果属性是Object类型只要使用properties单独定义即可支持多层Json对象嵌套。

2.10 查询语法

通过ES查询表达式(Query DSL),可以实现复杂的查询功能,ES查询表达式主要由JSON格式编写,可以灵活的组合各种查询语句。

2.10.1 查询基本语法结构

1
2
3
4
5
6
7
8
9
GET /{索引名}/_search
{
"from" : 0, // 返回搜索结果的开始位置
"size" : 10, // 分页大小,一次返回多少数据
"_source" :[ ...需要返回的字段数组... ],
"query" : { ...query子句... },
"aggs" : { ..aggs子句.. },
"sort" : { ..sort子句.. }
}

{索引名},支持支持一次搜索多个索引,多个索引使用逗号分隔,例子:

1
GET /order1,order2/_search

按前缀匹配索引名:

1
GET /order*/_search

搜索索引名以order开头的索引。

查询结果格式:

当我们执行查询语句,返回的JSON数据格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"took" : 5, // 查询消耗时间,单位毫秒
"timed_out" : false, // 查询是否超时
"_shards" : { // 本次查询参与的ES分片信息,查询中参与分片的总数,以及这些分片成功了多少个失败了多少个
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : { // hits字段包含我们搜索匹配的结果
"total" : { // 匹配到的文档总数
"value" : 1, // 找到1个文档
"relation" : "eq"
},
"max_score" : 1.0, // 匹配到的最大分值
"hits" : [
// 这里就是我们具体的搜索结果,是一个JSON文档数组
]
}
}

2.10.2 query子句

query子句主要用来编写类似SQL的Where语句,支持布尔查询(and/or)、IN、全文搜索、模糊匹配、范围查询(大于小于)。

2.10.3 aggs子句

aggs子句,主要用来编写统计分析语句,类似SQL的group by语句

2.10.4 sort子句

sort子句,用来设置排序条件,类似SQL的order by语句

2.10.5 ES查询分页

ES查询的分页主要通过from和size参数设置,类似MYSQL 的limit和offset语句

例子:

1
2
3
4
5
6
7
8
GET /order_v2/_search
{
"from": 0,
"size": 20,
"query": {
"match_all": {}
}
}

查询所有数据,从第0条数据开始,一次返回20条数据。

2.10.6 _source

_source用于设置查询结果返回什么字段,类似Select语句后面指定字段。

例子:

1
2
3
4
5
6
7
GET /order_v2/_search
{
"_source": ["order_no","shop_id"],
"query": {
"match_all": {}
}
}

仅返回,order_no和shop_id字段。

2.11 query查询语法

query子句主要用于编写查询条件,类似SQL中的where语句

2.11.1 匹配单个字段

通过match实现全文搜索,全文搜索的后面有单独的章节讲解,这里大家只要知道简单的用法就可以。

语法:

1
2
3
4
5
6
7
8
GET /{索引名}/_search
{
"query": {
"match": {
"{FIELD}": "{TEXT}"
}
}
}

说明:

  • {FIELD} - 就是我们需要匹配的字段名
  • {TEXT} - 就是我们需要匹配的内容

例子:

1
2
3
4
5
6
7
8
GET /article/_search
{
"query": {
"match" : {
"title" : "ES教程"
}
}
}

article索引中,title字段匹配ES教程的所有文档。

如果title字段的数据类型是text类型,搜索关键词会进行分词处理。

2.11.2 精确匹配单个字段

如果我们想要类似SQL语句中的等值匹配,不需要进行分词处理,例如:订单号、手机号、时间字段,不需要分值处理,只要精确匹配。

通过term实现精确匹配语法:

1
2
3
4
5
6
7
8
GET /{索引名}/_search
{
"query": {
"term": {
"{FIELD}": "{VALUE}"
}
}
}

说明:

  • {FIELD} - 就是我们需要匹配的字段名
  • {VALUE} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。

例子:

1
2
3
4
5
6
7
8
GET /order_v2/_search
{
"query": {
"term": {
"order_no": "202003131209120999"
}
}
}

搜索订单号order_no = “202003131209120999”的文档。

类似SQL语句:

1
select * from order_v2 where order_no = "202003131209120999"

2.11.3 通过terms实现SQL的in语句

如果我们要实现SQL中的in语句,一个字段包含给定数组中的任意一个值就匹配。

terms语法:

1
2
3
4
5
6
7
8
9
10
11
GET /order_v2/_search
{
"query": {
"terms": {
"{FIELD}": [
"{VALUE1}",
"{VALUE2}"
]
}
}
}

说明:

  • {FIELD} - 就是我们需要匹配的字段名
  • {VALUE1}, {VALUE2} …. {VALUE N} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。

例子:

1
2
3
4
5
6
7
8
GET /order_v2/_search
{
"query": {
"terms": {
"shop_id": [123,100,300]
}
}
}

搜索order_v2索引中,shop_id字段,只要包含[123,100,300]其中一个值,就算匹配。

类似SQL语句:

1
select * from order_v2 where shop_id in (123,100,300)

2.11.4 范围查询

通过range实现范围查询,类似SQL语句中的>, >=, <, <=表达式。

range语法:

1
2
3
4
5
6
7
8
9
10
11
GET /{索引名}/_search
{
"query": {
"range": {
"{FIELD}": {
"gte": 10,
"lte": 20
}
}
}
}

参数说明:

  • {FIELD} - 字段名
  • gte范围参数 - 等价于>=
  • lte范围参数 - 等价于 <=
  • 范围参数可以只写一个,例如:仅保留 “gte”: 10, 则代表 FIELD字段 >= 10

范围参数如下:

  • gt - 大于 ( > )
  • gte - 大于且等于 ( >= )
  • lt - 小于 ( < )
  • lte - 小于且等于 ( <= )

例子1:

1
2
3
4
5
6
7
8
9
10
11
GET /order_v2/_search
{
"query": {
"range": {
"shop_id": {
"gte": 10,
"lte": 200
}
}
}
}

查询order_v2索引中,shop_id >= 10 且 shop_id <= 200的文档

类似SQL:

1
select * from order_v2 where shop_id >= 10 and shop_id <= 200

例子2:

1
2
3
4
5
6
7
8
9
10
GET /order_v2/_search
{
"query": {
"range": {
"shop_id": {
"gte": 10
}
}
}
}

类似SQL:

1
select * from order_v2 where shop_id >= 10

2.11.5 bool组合查询

前面的例子都是设置单个字段的查询条件,如果需要编写类似SQL的Where语句,组合多个字段的查询条件,可以使用bool语句。

(1)bool查询基本语法结构

在ES中bool查询就是用来组合布尔查询条件,布尔查询条件,就是类似SQL中的and (且)、or (或)。

在SQL中,我们需要and和or,还有括号来组合查询条件,在ES中使用bool查询可用做到同样的效果。

bool语法结构:

1
2
3
4
5
6
7
8
9
10
GET /{索引名}/_search
{
"query": {
"bool": { // bool查询
"must": [], // must条件,类似SQL中的and, 代表必须匹配条件
"must_not": [], // must_not条件,跟must相反,必须不匹配条件
"should": [] // should条件,类似SQL中or, 代表匹配其中一个条件
}
}
}

可以任意选择must、must_not和should条件的参数都是一个数组,意味着他们都支持设置多个条件。

提示:前面介绍的单个字段的匹配语句,都可以用在bool查询语句中进行组合。

(2)must条件

类似SQL的and,代表必须匹配的条件。

语法:

1
2
3
4
5
6
7
8
9
10
11
12
GET /{索引名}/_search
{
"query": {
"bool": {
"must": [
{匹配条件1},
{匹配条件2},
...可以有N个匹配条件...
]
}
}
}

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /order_v2/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"order_no": "202003131209120999"
}
},
{
"term": {
"shop_id": 123
}
}
]
}
}
}

这里的Must条件,使用了term精确匹配。

等价SQL:

1
select * from order_v2 where order_no="202003131209120999" and shop_id=123

(3)must_not条件

跟must的作用相反。

语法:

1
2
3
4
5
6
7
8
9
10
11
12
GET /{索引名}/_search
{
"query": {
"bool": {
"must_not": [
{匹配条件1},
{匹配条件2},
...可以有N个匹配条件...
]
}
}
}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /order_v2/_search
{
"query": {
"bool": {
"must_not": [
{
"term": {
"shop_id": 1
}
},
{
"term": {
"shop_id": 2
}
}
]
}
}
}

等价sql:

1
select * from order_v2 where shop_id != 1 and shop_id != 2

(4)should条件

类似SQL中的 or, 只要匹配其中一个条件即可

语法:

1
2
3
4
5
6
7
8
9
10
11
12
GET /{索引名}/_search
{
"query": {
"bool": {
"should": [
{匹配条件1},
{匹配条件2},
…可以有N个匹配条件…
]
}
}
}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /order_v2/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"order_no": "202003131209120999"
}
},
{
"match": {
"order_no": "22222222222222222"
}
}
]
}
}
}

等价SQL:

1
select * from order_v2 where order_no="202003131209120999" or order_no="22222222222222222"

(5)bool综合例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
GET /order_v2/_search
{
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{
"term": {
"order_no": "2020031312091209991"
}
},
{
"range": {
"shop_id": {
"gte": 10,
"lte": 200
}
}
}
]
}
},
{
"terms": {
"tag": [
1,
2,
3,
4,
5,
12
]
}
}
]
}
}
}

等价SQL:

1
select * from order_v2 where (order_no='202003131209120999' and (shop_id>=10 and shop_id<=200)) or tag in (1,2,3,4,5)

2.12 全文搜索

全文搜索是ES的关键特性之一,平时我们使用SQL的like语句,搜索一些文本、字符串是否包含指定的关键词,但是如果两篇文章,都包含我们的关键词,具体那篇文章内容的相关度更高? 这个SQL的like语句是做不到的,更别说like语句的性能问题了。

ES通过分词处理、相关度计算可以解决这个问题,ES内置了一些相关度算法,例如:TF/IDF算法,大体上思想就是,如果一个关键词在一篇文章出现的频率高,并且在其他文章中出现的少,那说明这个关键词与这篇文章的相关度很高。

分词的目的:

主要就是为了提取搜索关键词,理解搜索的意图,我们平时在百度搜索内容的时候,输入的内容可能很长,但不是每个字都对搜索有帮助,所以通过分词算法,我们输入的搜索关键词,会进一步分解成多个关键词,例如:搜索输入 “上海陆家嘴在哪里?”,分词算法可能分解出:“上海、陆家嘴、哪里”,具体会分解出什么关键词,跟具体的分词算法有关。

2.12.1 默认全文搜索

默认情况下,使用全文搜索很简单,只要将字段类型定义为text类型,然后用match语句匹配即可。

ES对于text类型的字段,在插入数据的时候,会进行分词处理,然后根据分词的结果索引文档,当我们搜索text类型字段的时候,也会先对搜索关键词进行分词处理、然后根据分词的结果去搜索。

ES默认的分词器是standard,对英文比较友好,例如:hello world 会被分解成 hello和world两个关键词,如果是中文会分解成一个一个字,例如:上海大学 会分解成: 上、海、大、学。

在ES中,我们可以通过下面方式测试分词效果:

语法:

1
2
3
4
5
GET /_analyze
{
"text": "需要分词的内容",
"analyzer": "分词器"
}

例如:

1
2
3
4
5
GET /_analyze
{
"text": "hello wolrd",
"analyzer": "standard"
}

使用standard分词器,对hello world进行分词,下面是输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"tokens" : [
{
"token" : "hello",
"start_offset" : 0,
"end_offset" : 5,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "wolrd",
"start_offset" : 6,
"end_offset" : 11,
"type" : "<ALPHANUM>",
"position" : 1
}
]
}

token就是分解出来的关键词。

下面是对中文分词的结果:

1
2
3
4
5
GET /_analyze
{
"text": "上海大学",
"analyzer": "standard"
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"tokens" : [
{
"token" : "上",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "海",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "大",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "学",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
}
]
}

明显被切割成一个个的字了。

中文关键词被分解成一个个的字的主要问题就是搜索结果可能不太准确。

例如:

搜索:上海大学

分词结果:上、海、大、学

下面的内容都会被搜到:

  • 上海大学
  • 海上有条船
  • 上海有好吃的
  • 这条船又大又好看

基本上包含这四个字的内容都会被搜到,区别就是相关度的问题,这里除了第一条是相关的,后面的内容基本上跟搜索目的没什么关系。

2.12.2 中文分词器

ES默认的analyzer(分词器),对英文单词比较友好,对中文分词效果不好。不过ES支持安装分词插件,增加新的分词器。

(1)如何指定analyzer

默认的分词器不满足需要,可以在定义索引映射的时候,指定text字段的分词器
(analyzer)。

例子:

1
2
3
4
5
6
7
8
9
10
11
PUT /article
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "smartcn"
}
}
}
}

只要在定义text字段的时候,增加一个analyzer配置,指定分词器即可,这里指定的分词器是smartcn,后面会介绍怎么安装smartcn插件。

目前中文分词器比较常用的有:smartcn和ik两种, 下面分别介绍这两种分词器。

(2)smartcn分词器

smartcn是目前ES官方推荐的中文分词插件,不过目前不支持自定义词库。

插件安装方式:

1
{ES安装目录}/bin/elasticsearch-plugin install analysis-smartcn

安装完成后,重启ES即可。

smartcn的分词器名字就叫做:smartcn

(3)smartcn中文分词效果

测试分词效果:

1
2
3
4
5
GET /_analyze
{
"text": "红烧牛肉面",
"analyzer": "smartcn"
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"tokens" : [
{
"token" : "红烧",
"start_offset" : 0,
"end_offset" : 2,
"type" : "word",
"position" : 0
},
{
"token" : "牛肉面",
"start_offset" : 2,
"end_offset" : 5,
"type" : "word",
"position" : 1
}
]
}

红烧牛肉面,被切割为: 红烧、牛肉面 两个词。

(4)ik分词器

ik支持自定义扩展词库,有时候分词的结果不满足我们业务需要,需要根据业务设置专门的词库,词库的作用就是自定义一批关键词,分词的时候优先根据词库设置的关键词分割内容,例如:词库中包含 “上海大学” 关键词,如果对“上海大学在哪里?”进行分词,“上海大学” 会做为一个整体被切割出来。

安装ik插件:

1
2
// 到这里找跟自己ES版本一致的插件地址
https://github.com/medcl/elasticsearch-analysis-ik/releases

我本地使用的ES版本是7.5.1,所以选择的Ik插件版本地址是:

1
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip

当然你也可以尝试根据我给出这个地址,直接修改版本号,试试看行不行。

安装命令

1
{ES安装目录}/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip

(5)ik中文分词效果

ik分词插件支持 ik_smart 和 ik_max_word 两种分词器

  • ik_smart - 粗粒度的分词
  • ik_max_word - 会尽可能的枚举可能的关键词,就是分词比较细致一些,会分解出更多的关键词

例1:

1
2
3
4
5
GET /_analyze
{
"text": "上海人民广场麻辣烫",
"analyzer": "ik_max_word"
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{
"tokens" : [
{
"token" : "上海人",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "上海",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "人民",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "广场",
"start_offset" : 4,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "麻辣烫",
"start_offset" : 6,
"end_offset" : 9,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "麻辣",
"start_offset" : 6,
"end_offset" : 8,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "烫",
"start_offset" : 8,
"end_offset" : 9,
"type" : "CN_CHAR",
"position" : 6
}
]
}

例2:

1
2
3
4
5
GET /_analyze
{
"text": "上海人民广场麻辣烫",
"analyzer": "ik_smart"
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"tokens" : [
{
"token" : "上海",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "人民",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "广场",
"start_offset" : 4,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "麻辣烫",
"start_offset" : 6,
"end_offset" : 9,
"type" : "CN_WORD",
"position" : 3
}
]
}

(6)ik自定义词库

自定义扩展词库步骤如下:

一、创建配词库文件,以dic作为扩展名

例如:词库文件:{ES安装目录}/analysis-ik/config/demo.dic

1
2
3
上海大学
复旦大学
人民广场

一行一个词条即可

提示:config目录不存在创建一个即可。

二、创建或者修改配置文件

配置文件路径:{ES安装目录}/analysis-ik/config/IKAnalyzer.cfg.xml

IKAnalyzer.cfg.xml配置文件不存在,就创建一个。

配置文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">{ES安装目录}/analysis-ik/config/demo.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典,没有可用删掉配置-->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典,这个配置需要结合下面配置一起使用,没有可用删掉配置 -->
<entry key="remote_ext_dict">location</entry>
<!--用户可以在这里配置远程扩展停止词字典,没有可用删掉-->
<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>

三、重启ES即可

提示:Ik新增扩展词库,支持热更新,不用重启ES,使用remote_ext_dict和remote_ext_stopwords配置远程词库地址即可,词库地址需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,ES靠这两个头识别是否需要更新词库,不了解这两个HTTP头,可以搜一下。

2.13 sort排序子句

ES的默认排序是根据相关性分数排序,如果我们想根据查询结果中的指定字段排序,需要使用sort Processors处理。

sort语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /{索引名}/_search
{
"query": {
...查询条件....
},
"sort": [
{
"{Field1}": { // 排序字段1
"order": "desc" // 排序方向,asc或者desc, 升序和降序
}
},
{
"{Field2}": { // 排序字段2
"order": "desc" // 排序方向,asc或者desc, 升序和降序
}
}
....多个排序字段.....
]
}

sort子句支持多个字段排序,类似SQL的order by。

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /order_v2/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"order_no": {
"order": "desc"
}
},
{
"shop_id": {
"order": "asc"
}
}
]
}

查询order_v2索引的所有结果,结果根据order_no字段降序,order_no相等的时候,再根据shop_id字段升序排序。

类似SQL:

1
select * from order_v2 order by order_no desc, shop_id asc

例子2:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /order_v2/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"user.id": { // 嵌套json对象,使用 点 连接字段名即可
"order": "desc"
}
}
]
}

2.14 聚合查询(aggs)

ES中的聚合查询,类似SQL的SUM/AVG/COUNT/GROUP BY分组查询,主要用于统计分析场景。

下面先介绍ES聚合查询的核心流程和核心概念。

2.14.1 ES聚合查询流程

ES聚合查询类似SQL的GROUP by,一般统计分析主要分为两个步骤:

  • 分组
  • 组内聚合

对查询的数据首先进行一轮分组,可以设置分组条件,例如:新生入学,把所有的学生按专业分班,这个分班的过程就是对学生进行了分组。

组内聚合,就是对组内的数据进行统计,例如:计算总数、求平均值等等,接上面的例子,学生都按专业分班了,那么就可以统计每个班的学生总数, 这个统计每个班学生总数的计算,就是组内聚合计算。

提示:分组类似SQL的group by语句设定的条件,组内聚合,就是在select编写的avg、sum、count统计函数;熟悉SQL语句都知道sum、count这些统计函数不一定要跟group by语句配合使用,单独使用统计函数等同于将所有数据分成一个组,直接对所有数据进行统计。

2.14.2 核心概念

通过上面的聚合查询流程,下面是ES聚合的核心概念就很容易理解了

(1)桶

满足特定条件的文档的集合,叫做桶。

桶的就是一组数据的集合,对数据分组后,得到一组组的数据,就是一个个的桶。

提示:桶等同于组,分桶和分组是一个意思,ES使用桶代表一组相同特征的数据。

ES中桶聚合,指的就是先对数据进行分组,ES支持多种分组条件,例如:支持类似SQL的group by根据字段分组,当然ES比SQL更强大,支持更多的分组条件,以满足各种统计需求。

(2)指标

指标指的是对文档进行统计计算方式,又叫指标聚合。

桶内聚合,说的就是先对数据进行分组(分桶),然后对每一个桶内的数据进行指标聚合。

说白了就是,前面将数据经过一轮桶聚合,把数据分成一个个的桶之后,我们根据上面计算指标对桶内的数据进行统计。

常用的指标有:SUM、COUNT、MAX等统计函数。

借助SQL的统计语句理解桶和指标:

1
2
3
SELECT COUNT(*) 
FROM order
GROUP BY shop_id

说明:

  • COUNT(*) 相当于指标, 也叫统计指标。
  • GROUP BY shop_id 相当于分桶的条件,也可以叫分组条件,相同shop_id的数据都分到一个桶内。

这条SQL语句的作用就是统计每一个店铺的订单数,所以SQL统计的第一步是根据group by shop_id这个条件,把shop_id(店铺ID)相同的数据分到一个组(桶)里面,然后每一组数据使用count(*)统计函数(指标)计算总数,最终得到每一个店铺的订单总数,ES也是类似的过程。

2.14.3 ES聚合查询语法

大家可以先大致了解下ES聚合查询的基本语法结构,后面的章节会介绍具体的用法。

1
2
3
4
5
6
7
8
9
10
11
{
"aggregations" : {
"<aggregation_name>" : {
"<aggregation_type>" : {
<aggregation_body>
}
[,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套聚合查询,支持多层嵌套
}
[,"<aggregation_name_2>" : { ... } ]* // 多个聚合查询,每个聚合查询取不同的名字
}
}

说明:

  • aggregations - 代表聚合查询语句,可以简写为aggs
  • - 代表一个聚合计算的名字,可以随意命名,因为ES支持一次进行多次统计分析查询,后面需要通过这个名字在查询结果中找到我们想要的计算结果。
  • - 聚合类型,代表我们想要怎么统计数据,主要有两大类聚合类型,桶聚合和指标聚合,这两类聚合又包括多种聚合类型,例如:指标聚合:sum、avg, 桶聚合:terms、Date histogram等等。
  • - 聚合类型的参数,选择不同的聚合类型,有不同的参数。
  • aggregation_name_2 - 代表其他聚合计算的名字,意思就是可以一次进行多种类型的统计。

下面看个简单的聚合查询的例子:

假设存在一个order索引,存储了每一笔汽车销售订单,里面包含了汽车颜色字段color.

1
2
3
4
5
6
7
8
9
10
11
GET /order/_search
{
"size" : 0, // 设置size=0的意思就是,仅返回聚合查询结果,不返回普通query查询结果。
"aggs" : { // 聚合查询语句的简写
"popular_colors" : { // 给聚合查询取个名字,叫popular_colors
"terms" : { // 聚合类型为,terms,terms是桶聚合的一种,类似SQL的group by的作用,根据字段分组,相同字段值的文档分为一组。
"field" : "color" // terms聚合类型的参数,这里需要设置分组的字段为color,根据color分组
}
}
}
}

上面使用了terms桶聚合,而且没有明确指定指标聚合函数,默认使用的是Value Count聚合指标统计文档总数, 整个统计的意思是统计每一种汽车颜色的销量。

等价SQL如下:

1
select count(color) from order group by color

查询结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
...
"hits": { // 因为size=0,所以query查询结果为空
"hits": []
},
"aggregations": { // 聚合查询结果
"popular_colors": { // 这个就是popular_colors聚合查询的结果,这就是为什么需要给聚合查询取个名字的原因,如果有多个聚合查询,可以通过名字查找结果
"buckets": [ // 因为是桶聚合,所以看到返回一个buckets数组,代表分组的统计情况,下面可以看到每一种颜色的销量情况
{
"key": "red",
"doc_count": 4 // 红色的汽车销量为4
},
{
"key": "blue",
"doc_count": 2
},
{
"key": "green",
"doc_count": 2
}
]
}
}
}

2.15 指标聚合(metrics)

ES指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用。

常用的统计函数如下:

  • Value Count - 类似sql的count函数,统计总数
  • Cardinality - 类似SQL的count(DISTINCT 字段), 统计不重复的数据总数
  • Avg - 求平均值
  • Sum - 求和
  • Max - 求最大值
  • Min - 求最小值

下面分别介绍Elasticsearch常用统计函数的用法。

2.15.1 Value Count

值聚合,主要用于统计文档总数,类似SQL的count函数。

例子:

1
2
3
4
5
6
7
8
9
10
GET /sales/_search?size=0
{
"aggs": {
"types_count": { // 聚合查询的名字,随便取个名字
"value_count": { // 聚合类型为:value_count
"field": "type" // 计算type这个字段值的总数
}
}
}
}

等价SQL:

1
select count(type) from sales

返回结果:

1
2
3
4
5
6
7
8
{
...
"aggregations": {
"types_count": { // 聚合查询的名字
"value": 7 // 统计结果
}
}
}

2.15.2 Cardinality

基数聚合,也是用于统计文档的总数,跟Value Count的区别是,基数聚合会去重,不会统计重复的值,类似SQL的count(DISTINCT 字段)用法。

例子:

1
2
3
4
5
6
7
8
9
10
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : { // 聚合查询的名字,随便取一个
"cardinality" : { // 聚合查询类型为:cardinality
"field" : "type" // 根据type这个字段统计文档总数
}
}
}
}

等价SQL:

1
select count(DISTINCT type) from sales

返回结果:

1
2
3
4
5
6
7
8
{
...
"aggregations" : {
"type_count" : { // 聚合查询的名字
"value" : 3 // 统计结果
}
}
}

提示:前面提到基数聚合的作用等价于SQL的count(DISTINCT 字段)的用法,其实不太准确,因为SQL的count统计结果是精确统计不会丢失精度,但是ES的cardinality基数聚合统计的总数是一个近似值,会有一定的误差,这么做的目的是为了性能,因为在海量的数据中精确统计总数是非常消耗性能的,但是很多业务场景不需要精确的结果,只要近似值,例如:统计网站一天的访问量,有点误差没关系。

2.15.3 Avg

求平均值

例子:

1
2
3
4
5
6
7
8
9
10
POST /exams/_search?size=0
{
"aggs": {
"avg_grade": { // 聚合查询名字,随便取一个名字
"avg": { // 聚合查询类型为: avg
"field": "grade" // 统计grade字段值的平均值
}
}
}
}

返回结果:

1
2
3
4
5
6
7
8
{
...
"aggregations": {
"avg_grade": { // 聚合查询名字
"value": 75.0 // 统计结果
}
}
}

2.15.4 Sum

求和计算

例子:

1
2
3
4
5
6
7
8
9
10
POST /sales/_search?size=0
{
"aggs": {
"hat_prices": { // 聚合查询名字,随便取一个名字
"sum": { // 聚合类型为:sum
"field": "price" // 计算price字段值的总和
}
}
}
}

返回结果:

1
2
3
4
5
6
7
8
{
...
"aggregations": {
"hat_prices": { // 聚合查询名字
"value": 450.0 // 统计结果
}
}
}

2.15.5 Max

求最大值

例子:

1
2
3
4
5
6
7
8
9
10
POST /sales/_search?size=0
{
"aggs": {
"max_price": { // 聚合查询名字,随便取一个名字
"max": { // 聚合类型为:max
"field": "price" // 求price字段的最大值
}
}
}
}

返回结果:

1
2
3
4
5
6
7
8
{
...
"aggregations": {
"max_price": { // 聚合查询名字
"value": 200.0 // 最大值
}
}
}

2.15.6 Min

求最小值

例子:

1
2
3
4
5
6
7
8
9
10
POST /sales/_search?size=0
{
"aggs": {
"min_price": { // 聚合查询名字,随便取一个
"min": { // 聚合类型为: min
"field": "price" // 求price字段值的最小值
}
}
}
}

返回:

1
2
3
4
5
6
7
8
9
{
...

"aggregations": {
"min_price": { // 聚合查询名字
"value": 10.0 // 最小值
}
}
}

2.15.7 综合例子

前面的例子,仅仅介绍聚合指标单独使用的情况,实际应用中经常先通过query查询,搜索索引中的数据,然后对query查询的结果进行统计分析。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
GET /sales/_search
{
"size": 0, // size = 0,代表不想返回query查询结果,只要统计结果
"query": { // 设置query查询条件,后面的aggs统计,仅对query查询结果进行统计
"constant_score": {
"filter": {
"match": {
"type": "hat"
}
}
}
},
"aggs": { // 统计query查询结果, 默认情况如果不写query语句,则代表统计所有数据
"hat_prices": { // 聚合查询名字,计算price总和
"sum": {
"field": "price"
}
},
"min_price": { // 聚合查询名字,计算price最小值
"min": {
"field": "price"
}
},
"max_price": { // 聚合查询名字,计算price最大值
"max": {
"field": "price"
}
}
}
}

返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
...
"aggregations": {
"hat_prices": { // 求和
"value": 450.0
},
"min_price": { // 最小值
"value": 10.0
},
"max_price": { // 最大值
"value": 200.0
}
}
}

2.16 分组聚合查询(bucket)

Elasticsearch桶聚合,目的就是数据分组,先将数据按指定的条件分成多个组,然后对每一个组进行统计。 组的概念跟桶是等同的,在ES中统一使用桶(bucket)这个术语。

ES桶聚合的作用跟SQL的group by的作用是一样的,区别是ES支持更加强大的数据分组能力,SQL只能根据字段的唯一值进行分组,分组的数量跟字段的唯一值的数量相等,例如: group by 店铺id, 去掉重复的店铺ID后,有多少个店铺就有多少个分组。

ES常用的桶聚合如下:

  • Terms聚合 - 类似SQL的group by,根据字段唯一值分组
  • Histogram聚合 - 根据数值间隔分组,例如: 价格按100间隔分组,0、100、200、300等等
  • Date histogram聚合 - 根据时间间隔分组,例如:按月、按天、按小时分组
  • Range聚合 - 按数值范围分组,例如: 0-150一组,150-200一组,200-500一组。

提示:桶聚合一般不单独使用,都是配合指标聚合一起使用,对数据分组之后肯定要统计桶内数据,在ES中如果没有明确指定指标聚合,默认使用Value Count指标聚合,统计桶内文档总数。

2.16.1 Terms聚合

terms聚合的作用跟SQL中group by作用一样,都是根据字段唯一值对数据进行分组(分桶),字段值相等的文档都分到同一个桶内。

例子:

1
2
3
4
5
6
7
8
9
10
GET /order/_search?size=0
{
"aggs": {
"shop": { // 聚合查询的名字,随便取个名字
"terms": { // 聚合类型为: terms
"field": "shop_id" // 根据shop_id字段值,分桶
}
}
}
}

等价SQL:

1
select shop_id, count(*) from order group by shop_id

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
...
"aggregations" : {
"shop" : { // 聚合查询名字
"buckets" : [ // 桶聚合结果,下面返回各个桶的聚合结果
{
"key" : "1", // key分桶的标识,在terms聚合中,代表的就是分桶的字段值
"doc_count" : 6 // 默认的指标聚合是统计桶内文档总数
},
{
"key" : "5",
"doc_count" : 3
},
{
"key" : "9",
"doc_count" : 2
}
]
}
}
}

2.16.2 Histogram聚合

histogram(直方图)聚合,主要根据数值间隔分组,使用histogram聚合分桶统计结果,通常用在绘制条形图报表。

例子:

1
2
3
4
5
6
7
8
9
10
11
POST /sales/_search?size=0
{
"aggs" : {
"prices" : { // 聚合查询名字,随便取一个
"histogram" : { // 聚合类型为:histogram
"field" : "price", // 根据price字段分桶
"interval" : 50 // 分桶的间隔为50,意思就是price字段值按50间隔分组
}
}
}
}

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
...
"aggregations": {
"prices" : { // 聚合查询名字
"buckets": [ // 分桶结果
{
"key": 0.0, // 桶的标识,histogram分桶,这里通常是分组的间隔值
"doc_count": 1 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key": 50.0,
"doc_count": 1
},
{
"key": 100.0,
"doc_count": 0
},
{
"key": 150.0,
"doc_count": 2
}
]
}
}
}

2.16.3 Date histogram聚合

类似histogram聚合,区别是Date histogram可以很好的处理时间类型字段,主要用于根据时间、日期分桶的场景。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
POST /sales/_search?size=0
{
"aggs" : {
"sales_over_time" : { // 聚合查询名字,随便取一个
"date_histogram" : { // 聚合类型为: date_histogram
"field" : "date", // 根据date字段分组
"calendar_interval" : "month", // 分组间隔:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)
"format" : "yyyy-MM-dd" // 设置返回结果中桶key的时间格式
}
}
}
}

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
...
"aggregations": {
"sales_over_time": { // 聚合查询名字
"buckets": [ // 桶聚合结果
{
"key_as_string": "2015-01-01", // 每个桶key的字符串标识,格式由format指定
"key": 1420070400000, // key的具体字段值
"doc_count": 3 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key_as_string": "2015-02-01",
"key": 1422748800000,
"doc_count": 2
},
{
"key_as_string": "2015-03-01",
"key": 1425168000000,
"doc_count": 2
}
]
}
}
}

2.16.4 Range聚合

range聚合,按数值范围分桶。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /_search
{
"aggs" : {
"price_ranges" : { // 聚合查询名字,随便取一个
"range" : { // 聚合类型为: range
"field" : "price", // 根据price字段分桶
"ranges" : [ // 范围配置
{ "to" : 100.0 }, // 意思就是 price <= 100的文档归类到一个桶
{ "from" : 100.0, "to" : 200.0 }, // price>100 and price<200的文档归类到一个桶
{ "from" : 200.0 } // price>200的文档归类到一个桶
]
}
}
}
}

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
...
"aggregations": {
"price_ranges" : { // 聚合查询名字
"buckets": [ // 桶聚合结果
{
"key": "*-100.0", // key可以表达分桶的范围
"to": 100.0, // 结束值
"doc_count": 2 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key": "100.0-200.0",
"from": 100.0, // 起始值
"to": 200.0, // 结束值
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3
}
]
}
}
}

大家仔细观察的话,发现range分桶,默认key的值不太友好,尤其开发的时候,不知道key长什么样子,处理起来比较麻烦,我们可以为每一个分桶指定一个有意义的名字。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /_search
{
"aggs" : {
"price_ranges" : {
"range" : {
"field" : "price",
"keyed" : true,
"ranges" : [
// 通过key参数,配置每一个分桶的名字
{ "key" : "cheap", "to" : 100 },
{ "key" : "average", "from" : 100, "to" : 200 },
{ "key" : "expensive", "from" : 200 }
]
}
}
}
}

2.16.5 综合例子

前面的例子,都是单独使用aggs聚合语句,代表直接统计所有的文档,实际应用中,经常需要配合query语句,先搜索目标文档,然后使用aggs聚合语句对搜索结果进行统计分析。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET /cars/_search
{
"size": 0, // size=0代表不需要返回query查询结果,仅仅返回aggs统计结果
"query" : { // 设置查询语句,先赛选文档
"match" : {
"make" : "ford"
}
},
"aggs" : { // 然后对query搜索的结果,进行统计
"colors" : { // 聚合查询名字
"terms" : { // 聚合类型为:terms 先分桶
"field" : "color"
},
"aggs": { // 通过嵌套聚合查询,设置桶内指标聚合条件
"avg_price": { // 聚合查询名字
"avg": { // 聚合类型为: avg指标聚合
"field": "price" // 根据price字段计算平均值
}
},
"sum_price": { // 聚合查询名字
"sum": { // 聚合类型为: sum指标聚合
"field": "price" // 根据price字段求和
}
}
}
}
}
}

聚合查询支持多层嵌套。

2.17 聚合后排序

类似terms、histogram、date_histogram这类桶聚合都会动态生成多个桶,如果生成的桶特别多,我们如何确定这些桶的排序顺序,如何限制返回桶的数量。

2.17.1 多桶排序

默认情况,ES会根据doc_count文档总数,降序排序。

ES桶聚合支持两种方式排序:

  • 内置排序
  • 按度量指标排序

(1)内置排序

内置排序参数:

  • _count - 按文档数排序。对 terms 、 histogram 、 date_histogram 有效
  • _term - 按词项的字符串值的字母顺序排序。只在 terms 内使用
  • _key - 按每个桶的键值数值排序, 仅对 histogram 和 date_histogram 有效

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /cars/_search
{
"size" : 0,
"aggs" : {
"colors" : { // 聚合查询名字,随便取一个
"terms" : { // 聚合类型为: terms
"field" : "color",
"order": { // 设置排序参数
"_count" : "asc" // 根据_count排序,asc升序,desc降序
}
}
}
}
}

(2)按度量排序

通常情况下,我们根据桶聚合分桶后,都会对桶内进行多个维度的指标聚合,所以我们也可以根据桶内指标聚合的结果进行排序。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /cars/_search
{
"size" : 0,
"aggs" : {
"colors" : { // 聚合查询名字
"terms" : { // 聚合类型: terms,先分桶
"field" : "color", // 分桶字段为color
"order": { // 设置排序参数
"avg_price" : "asc" // 根据avg_price指标聚合结果,升序排序。
}
},
"aggs": { // 嵌套聚合查询,设置桶内聚合指标
"avg_price": { // 聚合查询名字,前面排序引用的就是这个名字
"avg": {"field": "price"} // 计算price字段平均值
}
}
}
}
}

2.17.2 限制返回桶的数量

如果分桶的数量太多,可以通过给桶聚合增加一个size参数限制返回桶的数量。

例子:

1
2
3
4
5
6
7
8
9
10
11
GET /_search
{
"aggs" : {
"products" : { // 聚合查询名字
"terms" : { // 聚合类型为: terms
"field" : "product", // 根据product字段分桶
"size" : 5 // 限制最多返回5个桶
}
}
}
}

三、Kibana (🌟)

3.1 Install

使用Kibana工具操作ES,Kibana以Web后台的形式提供了一个可视化操作ES的系统,支持根据ES数据绘制图表,支持ES查询语法自动补全等高级特性。

Kibana download

解压缩 & 启动(macOS)

1
2
3
4
5
6
7
8
# 解压
tar -zxvf kibana-7.5.1-darwin-x86_64.tar.gz

# 切换到安装目录
cd kibana-7.5.1-darwin-x86_64

# 启动
./bin/kibana

访问

1
http://localhost:5601/

点击Explore on my own进入后台

3.2 debug ES by Kibana’console

点击左侧 Dev Tools 进入 Cosole 后台

3.3 新增记录

1
2
3
method:POST
url:localhost:9200/school/student/1
data:{"name": "张三"}

![image-20210407195037328](/Users/junmingguo/Library/Application Support/typora-user-images/image-20210407195037328.png)

3.4 导入样例数据

kibana首页的Add sample data,以航班数据为例,介绍航班数据字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"_index": "kibana_sample_data_flights", // 航班索引名称
"_type": "_doc",
"_id": "MEkjEHMBJlts2AvaxCbj",
"_version": 1,
"_score": null,
"_source": {
"FlightNum": "SQIY3O9", // 航班编号
"DestCountry": "GB", // 目标国家简称
"OriginWeather": "Clear", // 出发天气
"OriginCityName": "Osaka", // 出发城市
"AvgTicketPrice": 644.8724392684487, // 平均价格
"DistanceMiles": 5948.821557153384, // 距离,英里
"FlightDelay": true, // 航班是否延误
"DestWeather": "Clear", // 目的地天气
"Dest": "London Gatwick Airport", // 目的地
"FlightDelayType": "Carrier Delay", // 航班延误原因
"OriginCountry": "JP", // 出发国家
"timestamp": "2020-07-02T15:28:44", // 时间
"DestLocation": { // 目的地经纬度
"lat": "51.14810181",
"lon": "-0.190277994"
},
"OriginLocation": { // 出发地经纬度
"lat": "34.4272995",
"lon": "135.2440033"
},
"DestCityName": "London", // 目的地城市
...忽略....
}
}

查询数据:通过左侧栏的Discover检索数据,需要创建一个索引模式(index pattern),索引模式是支持通配符的索引名表达式,切换索引模式,可以添加多个Sample data,多自动创建多个索引模式

位置:Discover>kibana_sample_data_flights [change]

kibana的discover支持Kibana Query Language (KQL)语法,就是一套简单的查询语法,通过KQL可以编写查询条件查询数据。提示:Kibana Query Language (KQL)语法不等同于Elasticsearch的查询语法。

3.5 KQL语法小结

Kibana Query Language (KQL)查询语法是Kibana为了简化ES查询设计的一套简单查询语法,Kibana支持索引字段和语法补全,可以非常方便的查询数据。

3.5.1 等值匹配(equals)

用于查询字段值,语法:

1
2
字段名: 匹配值
eg. FlightNum: 4H2KUBH

查询FlightNum字段匹配”4H2KUBH”字符串的文档。

等值匹配也支持通配符(*),通过通配符实现模糊搜索

1
eg. FlightNum: 4H*BH

3.5.2 存在检测(exists)

匹配包含指定字段的文档,语法:

1
2
字段名:*
eg. FlightNum:*

匹配包含FlightNum字段的文档。

3.5.3 关系运算符

关系运算符只能用在数值和时间类型的字段

支持关系运算符如下:

  • <= 小于等于
  • >= 大于等于
  • < 小于
  • > 大于
1
eg. AvgTicketPrice >= 300

3.5.4 逻辑运算符

支持逻辑运算符如下:

  • and
  • or
1
eg. AvgTicketPrice > 300 and AvgTicketPrice < 600 

AvgTicketPrice大于300且AvgTicketPrice小于600

3.6 查询航班样例数据

  • 查询航班编号等于LVOO2CA的航班信息
1
FlightNum: LVOO2CA
  • 同时查询下面索引的数据
    • tizi365_log_202005
    • tizi365_log_202006
    • tizi365_log_202007

可以使用tizi365_log_*作为索引模式,可以匹配以tizi365_log_为前缀的所有索引名

  • 查询航班机票平均价格大于300且小于600的航班信息
1
AvgTicketPrice > 300 and AvgTicketPrice < 600 
  • 查询航班编号等于 1MAEYXT 的航班信息
1
FlightNum : 1MAEYXT

3.7 Discover 菜单栏

Discover > Selected fields (显示当前选择的字段)

​ > Available fields (可选择的字段)

3.8 图表可视化

结合ES聚合查询语句生成图表,Visualize可查看已创建的图表

3.9 debug ES 查询语句

Dev tools中查询数据

1
2
3
4
5
6
7
8
GET /kibana_sample_data_flights/_search
{
"query": {
"match": {
"FlightNum": "SQIY309"
}
}
}

3.10 配置

默认安装,Kibana会自动连接本地安装的Elasticsearch,本节详细介绍Kibana的配置项。

配置文件路径:${kibana安装目录}/config/kibana.yml

Kibana使用Yaml格式描述配置项。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 端口号
server.port: 5601

# 绑定地址
server.host: "localhost"

# ES服务地址
elasticsearch.hosts: ["http://localhost:9200"]

# ES账号 ES没有设置账号密码可以不配置。
#elasticsearch.username: "kibana"
# ES密码
#elasticsearch.password: "pass"

参数配置说明

  • server.port:
    默认值: 5601 Kibana 由后端服务器提供服务,该配置指定使用的端口号。
  • server.host:
    默认值: “localhost” 指定后端服务器的主机地址。
  • server.basePath:
    如果启用了代理,指定 Kibana 的路径,该配置项只影响 Kibana 生成的 URLs,转发请求到 Kibana 时代理会移除基础路径值,该配置项不能以斜杠 (/)结尾。
  • server.maxPayloadBytes:
    默认值: 1048576 服务器请求的最大负载,单位字节。
  • server.name:
    默认值: “您的主机名” Kibana 实例对外展示的名称。
  • server.defaultRoute:
    默认值: “/app/kibana” Kibana 的默认路径,该配置项可改变 Kibana 的登录页面。
  • elasticsearch.url:
    默认值: “http://localhost:9200" 用来处理所有查询的 Elasticsearch 实例的 URL 。
  • elasticsearch.preserveHost:
    默认值: true 该设置项的值为 true 时,Kibana 使用 server.host 设定的主机名,该设置项的值为 false 时,Kibana 使用主机的主机名来连接 Kibana 实例。
  • kibana.index:
    默认值: “.kibana” Kibana 使用 Elasticsearch 中的索引来存储保存的检索,可视化控件以及仪表板。如果没有索引,Kibana 会创建一个新的索引。
  • kibana.defaultAppId:
    默认值: “discover” 默认加载的应用。
  • tilemap.url:
    Kibana 用来在 tile 地图可视化组件中展示地图服务的 URL。默认时,Kibana 从外部的元数据服务读取 url,用户也可以覆盖该参数,使用自己的 tile 地图服务。例如:”https://tiles.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana"
  • tilemap.options.minZoom:
    默认值: 1 最小缩放级别。
  • tilemap.options.maxZoom:
    默认值: 10 最大缩放级别。
  • tilemap.options.attribution:
    默认值: “© Elastic Tile Service“ 地图属性字符串。
  • tilemap.options.subdomains:
    服务使用的二级域名列表,用 {s} 指定二级域名的 URL 地址。
  • elasticsearch.username: 和 elasticsearch.password:
    Elasticsearch 设置了基本的权限认证,该配置项提供了用户名和密码,用于 Kibana 启动时维护索引。Kibana 用户仍需要 Elasticsearch 由 Kibana 服务端代理的认证。
  • server.ssl.enabled
    默认值: “false” 对到浏览器端的请求启用 SSL,设为 true 时, server.ssl.certificate 和 server.ssl.key 也要设置。
  • server.ssl.certificate: 和 server.ssl.key:
    PEM 格式 SSL 证书和 SSL 密钥文件的路径。
  • server.ssl.keyPassphrase
    解密私钥的口令,该设置项可选,因为密钥可能没有加密。
  • server.ssl.certificateAuthorities
    可信任 PEM 编码的证书文件路径列表。
  • server.ssl.supportedProtocols
    默认值: TLSv1、TLSv1.1、TLSv1.2 版本支持的协议,有效的协议类型: TLSv1 、 TLSv1.1 、 TLSv1.2 。
  • server.ssl.cipherSuites
    默认值: ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-SHA384, DHE-RSA-AES256-SHA384, ECDHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA256, HIGH,!aNULL, !eNULL, !EXPORT, !DES, !RC4, !MD5, !PSK, !SRP, !CAMELLIA. 具体格式和有效参数可通过OpenSSL cipher list format documentation 获得。
  • elasticsearch.ssl.certificate: 和 elasticsearch.ssl.key:
    可选配置项,提供 PEM格式 SSL 证书和密钥文件的路径。这些文件确保 Elasticsearch 后端使用同样的密钥文件。
  • elasticsearch.ssl.keyPassphrase
    解密私钥的口令,该设置项可选,因为密钥可能没有加密。
  • elasticsearch.ssl.certificateAuthorities:
    指定用于 Elasticsearch 实例的 PEM 证书文件路径。
  • elasticsearch.ssl.verificationMode:
    默认值: full 控制证书的认证,可用的值有 none 、 certificate 、 full 。 full 执行主机名验证,certificate 不执行主机名验证。
  • elasticsearch.pingTimeout:
    默认值: elasticsearch.requestTimeout setting 的值,等待 Elasticsearch 的响应时间。
  • elasticsearch.requestTimeout:
    默认值: 30000 等待后端或 Elasticsearch 的响应时间,单位微秒,该值必须为正整数。
  • elasticsearch.requestHeadersWhitelist:
    默认值: [ ‘authorization’ ] Kibana 客户端发送到 Elasticsearch 头体,发送 no 头体,设置该值为[]。
  • elasticsearch.customHeaders:
    默认值: {} 发往 Elasticsearch的头体和值, 不管 elasticsearch.requestHeadersWhitelist 如何配置,任何自定义的头体不会被客户端头体覆盖。
  • elasticsearch.shardTimeout:
    默认值: 0 Elasticsearch 等待分片响应时间,单位微秒,0即禁用。
  • elasticsearch.startupTimeout:
    默认值: 5000 Kibana 启动时等待 Elasticsearch 的时间,单位微秒。
  • pid.file:
    指定 Kibana 的进程 ID 文件的路径。
  • logging.dest:
    默认值: stdout 指定 Kibana 日志输出的文件。
  • logging.silent:
    默认值: false 该值设为 true 时,禁止所有日志输出。
  • logging.quiet:
    默认值: false 该值设为 true 时,禁止除错误信息除外的所有日志输出。
  • logging.verbose
    默认值: false 该值设为 true 时,记下所有事件包括系统使用信息和所有请求的日志。
  • ops.interval
    默认值: 5000 设置系统和进程取样间隔,单位微妙,最小值100。
  • status.allowAnonymous
    默认值: false 如果启用了权限,该项设置为 true 即允许所有非授权用户访问 Kibana 服务端 API 和状态页面。
  • cpu.cgroup.path.override
    如果挂载点跟 /proc/self/cgroup 不一致,覆盖 cgroup cpu 路径。
  • cpuacct.cgroup.path.override
    如果挂载点跟 /proc/self/cgroup 不一致,覆盖 cgroup cpuacct 路径。
  • console.enabled
    默认值: true 设为 false 来禁用控制台,切换该值后服务端下次启动时会重新生成资源文件,因此会导致页面服务有点延迟。
  • elasticsearch.tribe.url:
    Elasticsearch tribe 实例的 URL,用于所有查询。
  • elasticsearch.tribe.username: 和 elasticsearch.tribe.password:
    Elasticsearch 设置了基本的权限认证,该配置项提供了用户名和密码,用于 Kibana 启动时维护索引。Kibana 用户仍需要 Elasticsearch 由 Kibana 服务端代理的认证。
  • elasticsearch.tribe.ssl.certificate: 和 elasticsearch.tribe.ssl.key:
    可选配置项,提供 PEM 格式 SSL 证书和密钥文件的路径。这些文件确保 Elasticsearch 后端使用同样的密钥文件。
  • elasticsearch.tribe.ssl.keyPassphrase
    解密私钥的口令,该设置项可选,因为密钥可能没有加密。
  • elasticsearch.tribe.ssl.certificateAuthorities:
    指定用于 Elasticsearch tribe 实例的 PEM 证书文件路径。
  • elasticsearch.tribe.ssl.verificationMode:
    默认值: full 控制证书的认证,可用的值有 none 、 certificate 、 full 。 full 执行主机名验证, certificate 不执行主机名验证。
  • elasticsearch.tribe.pingTimeout:
    默认值: elasticsearch.tribe.requestTimeout setting 的值,等待 Elasticsearch 的响应时间。
  • elasticsearch.tribe.requestTimeout:
    Default: 30000 等待后端或 Elasticsearch 的响应时间,单位微秒,该值必须为正整数。
  • elasticsearch.tribe.requestHeadersWhitelist:
    默认值: [ ‘authorization’ ] Kibana 发往 Elasticsearch 的客户端头体,发送 no 头体,设置该值为[]。
  • elasticsearch.tribe.customHeaders:
    默认值: {} 发往 Elasticsearch的头体和值,不管 elasticsearch.tribe.requestHeadersWhitelist 如何配置,任何自定义的头体不会被客户端头体覆盖

3.11 Dev Tools

Kibana开发工具(Dev Tools)主要提供了下面三种功能

  • 调试Elasticsearch查询表达式
  • 分析Elasticsearch查询表达式性能
  • 调试grok

3.11.1 Console

通过Console(控制台)调试Elasticsearch查询表达式

3.11.2 Search Profiler 搜索性能分析

分析Elasticsearch查询表达式性能

  1. 输入需要分析的索引模式
  2. 输入需要分析的ES表达式
1
2
3
4
5
6
7
8
eg. 
{
"query": {
"match": {
"FlightNum": "SQIY309"
}
}
}
  1. Click Profile 开始分析

3.11.3 Grok Debugger

主要用于支持在线调试Logstash grok表达式

四、Logstash (🌟)

Logstash是一个数据同步工具,在ELK(Elasticsearch + Logstash + Kibana)技术栈中解决数据同步问题。日常项目中数据主要存储在MYSQL、日志文件中,通过Logstash可以将MYSQL、日志文件、redis等多种数据源的数据同步到ES,这样就可以通过ES搜索数据。

MYSQL同步数据到Elasticsearch,主要有下面几种策略:

  • 双写策略,更新MYSQL数据的同时通过ES API直接写入数据到ES (同步方式)
  • 通过Logstash同步数据到ES (异步方式)
  • 通过订阅MYSQL Binlog,将数据同步到ES (异步方式)

这里主要介绍Logstash如何同步数据。

4.1 Install

1
2
3
4
5
6
7
8
9
10
11
# 下载最新版本的压缩包(linux/mac系统下载tar.gz, windows下载zip)
https://www.elastic.co/cn/downloads/logstash

# 解压
tar -zxvf logstash-7.7.1.tar.gz

# 切换到安装目录
cd logstash-7.7.1

# 执行命令
bin/logstash -e 'input { stdin { } } output { stdout {} }'

logstash启动后在控制台输入abc按回车,可以看到类似下面的输出

1
2
3
4
5
6
7
abc
{
"@timestamp" => 2020-06-09T15:45:38.147Z,
"message" => "abc",
"@version" => "1",
"host" => "JUNMINGGUO-MB0"
}

添加config.reload.automatic命令参数,自动加载配置,不需要重新启动logstash

1
bin/logstash -f tizi.conf --config.reload.automatic

配置文件

可以将Logstash的配置都写入一个配置文件中,下面是配置文件的格式,主要有三部分组成

1
2
3
4
5
6
7
8
9
# 输入插件配置, 主要配置需要同步的数据源,例如:MYSQL
input {
}
# 过滤器插件配置, 主要用于对输入的数据进行过滤,格式化操作,filter是可选的。
filter {
}
# 输出插件配置,主要配置同步数据的目的地,例如同步到ES
output {
}

提示:logstash的input、filter、output都是由各种插件组成。

例子:创建一个myconf.conf配置文件,内容如下:

1
2
3
4
5
6
input {
stdin {}
}
output {
stdout { codec => rubydebug }
}

说明:

这个配置文件的意思是,从控制台标准输入(stdin)接收输入,然后直接将结果在控制台标准输出(stdout)打印出来。

通过配置文件启动logstash

1
bin/logstash -f myconf.conf

同步nginx日志到ES

下面是将Nginx的访问日志同步到ES中的配置

配置文件名:myconf.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
input {
# 实时监控日志文件的内容,类似tail -f 命令的作用
file {
# nginx日志文件路径
path => [ "/data/nginx/logs/nginx_access.log" ]
start_position => "beginning"
ignore_older => 0
}
}
# 配置过滤器对日志文件进行格式化
filter {
# 使用grok插件对日志内容进行格式化,提取日志内容,方便转换成json格式
# %COMBINEDAPACHELOG 是grok插件内置的apache日志内容处理模板,其实就是一些表达式,用来格式日志文本内容,也可以格式化Nginx日志
grok {
match => {
"message" => "%{COMBINEDAPACHELOG}"
}
}
}
# 配置输出目的地,这里配置同步到ES中
output {
elasticsearch {
# es服务器地址
hosts => ["127.0.0.1:9200"]
# 目标索引
index => "nginx-access"
}
}

启动logstash

1
bin/logstash -f myconf.conf

4.2 工作原理

Logstash同步数据,主要有三个核心环节:inputs → filters → outputs,流程如下图。

img

inputs模块负责收集数据,filters模块可以对收集到的数据进行格式化、过滤、简单的数据处理,outputs模块负责将数据同步到目的地,Logstash的处理流程,就像管道一样,数据从管道的一端,流向另外一端。

提示:inputs/filters/outputs是通过插件机制扩展各种能力。

4.2.1 inputs

inputs可以收集多种数据源的数据,下面是常见的数据源:

  • file - 扫描磁盘中的文件数据,例如: 扫描日志文件。
  • mysql - 扫描Mysql的表数据
  • redis
  • Filebeat - 轻量级的文件数据采集器,可以取代file的能力。
  • 消息队列kafka、rabbitmq等 - 支持从各种消息队列读取数据。

4.2.2 filters

filters是一个可选模块,可以在数据同步到目的地之前,对数据进行一些格式化、过滤、简单的数据处理操作。

常用的filters功能:

  • grok - 功能强大文本处理插件,主要用于格式化文本内容。
  • drop - 丢弃一些数据

4.2.3 outputs

Logstatsh的最后一个处理节点,outputs负责将数据同步到目的地。

下面是常见的目的地:

  • elasticsearch
  • file - 也可以将数据同步到一个文件中

4.2.4 Codecs

codecs就是编码器,负责对数据进行序列号处理,主要就是json和文本两种编码器。

4.3 Filebeat 教程

logstash虽然也支持从磁盘文件中收集数据,但是logstash自己本身还是比较重,对资源的消耗也比较大,尤其是在容器化环境,每个容器都部署logstash也太浪费资源,因此出现了轻量级的日志文件数据收集方案Filebeat,Filebeat将收集到的文件数据传给Logstatsh处理即可。

4.3.1 Filebeat部署架构

img

可以在每一台服务器或者每一个容器中安装Filebeat,Filebeat负责收集日志数据,然后将日志数据交给Logstash处理,Logstash在将数据导入ES。

4.3.2 Install

下载安装包,然后解压即可。

官网下载地址:

1
https://www.elastic.co/cn/downloads/beats/filebeat

下面以7.7.1版本为例

mac

1
2
3
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.7.1-darwin-x86_64.tar.gz

tar xzvf filebeat-7.7.1-darwin-x86_64.tar.gz

linux

1
2
3
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.7.1-linux-x86_64.tar.gz

tar xzvf filebeat-7.7.1-linux-x86_64.tar.gz

4.3.3 Filebeat配置

Filebeat的配置结构类似Logstash,也需要配置input和output,分别配置输入和输出,Filebeat使用yaml格式编写配置文件。

默认配置文件路径:

1
2
3
${安装目录}/filebeat.yml
/etc/filebeat/filebeat.yml
/usr/share/filebeat/filebeat.yml

因为我们使用的是tar安装包安装,所以选择${安装目录}/filebeat.yml 路径。

配置例子:

1
2
3
4
5
6
7
8
9
10
# 配置采集数据源
filebeat.inputs:
- type: log
paths:
- /var/log/messages
- /var/log/*.log
# 配置输出目标,这里将数据投递给logstash
output.logstash:
# logstash地址
hosts: ["127.0.0.1:5044"]

说明:

type为log类型,表示收集日志文件数据,paths是一个文件路径数组,这里扫描/var/log/messages文件和/var/log/目录下所有以log为扩展名的日志文件。

4.3.4 Logstash beat配置

配置Logstash的input,让Logstash可以接收Filebeat投递过来的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
input {
# 配置接收Filebeat数据源,监听端口为5044
# Filebeat的output.logstash地址保持跟这里一致
beats {
port => 5044
}
}

output {
# 将数据导入到ES中
elasticsearch {
hosts => ["http://localhost:9200"]
index => "tizi365"
}
}

4.3.5 启动Filebeat

进入filebeat安装目录

1
./filebeat -c filebeat.yml

如果配置PATH,直接启动即可。