Elasticsearch:ES|QL 查询展示

这篇文章是继我昨天完成的文章 "Elasticsearch:ES|QL 函数及操作符" 的另外一篇文章。我将继续使用之前文章 "Elasticsearch:ES|QL 快速入门" 中的例子来结合 ES|QL 函数来做更进一步的展示。希望能对之前的文章做一个更进一步的展示。在这里,我将主要使用 Dev Tools 来进行展示。

特别值得注意的是:在进行如下的例子之前,你需要至少安装 Elastic Stack 8.11 及以上版本。

准备数据

我们还是仿照之前的文章里的例子来进行展示。我个人比较喜欢较少的文档做为例子来进行展示。这里的原因是,文档较少,容易设置,同时容易看清。只要能说明问题,就是好的例子。针对之前的 DSL 查询,我们可以参阅以前的文章 "开始使用 Elasticsearch (2)"。里面展示的文档也不是很多。

我们先到 Dev Tools 里打入如下的命令:

json 复制代码
1.  PUT sample_data
2.  {
3.    "mappings": {
4.      "properties": {
5.        "client.ip": {
6.          "type": "ip"
7.        },
8.        "message": {
9.          "type": "keyword"
10.        }
11.      }
12.    }
13.  }
less 复制代码
1.  PUT sample_data/_bulk
2.  {"index": {}}
3.  {"@timestamp": "2023-10-23T12:15:03.360Z", "client.ip": "172.21.2.162", "message": "Connected to 10.1.0.3", "event.duration": 3450233}
4.  {"index": {}}
5.  {"@timestamp": "2023-10-23T12:27:28.948Z", "client.ip": "172.21.2.113", "message": "Connected to 10.1.0.2", "event.duration": 2764889}
6.  {"index": {}}
7.  {"@timestamp": "2023-10-23T13:33:34.937Z", "client.ip": "172.21.0.5", "message": "Disconnected", "event.duration": 1232382}
8.  {"index": {}}
9.  {"@timestamp": "2023-10-23T13:51:54.732Z", "client.ip": "172.21.3.15", "message": "Connection error", "event.duration": 725448}
10.  {"index": {}}
11.  {"@timestamp": "2023-10-23T13:52:55.015Z", "client.ip": "172.21.3.15", "message": "Connection error", "event.duration": 8268153}
12.  {"index": {}}
13.  {"@timestamp": "2023-10-23T13:53:55.832Z", "client.ip": "172.21.3.15", "message": "Connection error", "event.duration": 5033755}
14.  {"index": {}}
15.  {"@timestamp": "2023-10-23T13:55:01.543Z", "client.ip": "172.21.3.15", "message": "Connected to 10.1.0.1", "event.duration": 1756467}

我们可以把上面的最后的 bulk 命令运行两遍,这样会有更多的数据来进行展示。

我们有两种方法可以运行查询:

  • 在 Dev Tools 中运行
  • 在 Discover 中运行

我们为刚才创建的索引 sample_data 创建 data view:

虽然这个操作针对 Dev Tools 里的查询是不必要的。

在 Dev Tools 里进行查询

基本语法

ES|QL 查询由一个源命令组成,后跟一系列可选的处理命令,并用竖线字符分隔:|。 例如:

markdown 复制代码
1.  source-command
2.  | processing-command1
3.  | processing-command2

查询的结果是最终处理命令生成的表。

为了便于阅读,本文档将每个处理命令放在一个新行中。 但是,你可以将 ES|QL 查询编写为一行。 以下查询与前一个查询相同:

复制代码
source-command | processing-command1 | processing-command2

如下命令得到总的文档个数:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS COUNT(*)
6.    """
7.  }

它类似于之前的如下命令:

bash 复制代码
GET sample_data/_count

ES|QL 源命令

ES|QL 源命令会生成一个表,通常包含来自 Elasticsearch 的数据。

ES|QL 支持以下源命令:

  • FROM
  • ROW
  • SHOW
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      SHOW INFO
5.    """
6.  }

上面的命令和下面的命令是一样的结果。它不分大小写:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      show info
5.    """
6.  }

我们可以有如下的注释:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      SHOW INFO // Get the info
5.      /* Then get rid of the warning and only select the version field */
6.      | LIMIT 1
7.      | KEEP version
8.    """
9.  }

我们在哪里放置 pipe 符号呢?

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      SHOW INFO | LIMIT 1 | KEEP version
5.    """
6.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      SHOW INFO | 
5.      LIMIT 1 | 
6.      KEEP version
7.    """
8.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      SHOW FUNCTIONS
5.    """
6.  }

我们需要在 Kibana 的界面中,进入到 Dev Tools。通常一个 ES|QL query API 的命令格式是这样的:

python 复制代码
1.  POST /_query?format=txt
2.  {
3.    "query": """
4.    """
5.  }

在两组 """ """之间输入实际的 ES|QL 查询。 例如:

python 复制代码
1.  POST /_query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.    """
6.  }

你可以链接处理命令,并用竖线字符分隔:|。 每个处理命令都作用于前一个命令的输出表。 查询的结果是最终处理命令生成的表

我们可以通过使用 LIMIT 来限定返回的文档数:

python 复制代码
1.  POST /_query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.    """
7.  }

这个相当于 DSL 的如下查询:

ini 复制代码
GET sample_data/_search?size=5

如果未指定,LIMIT 默认为 500。无论 LIMIT 值如何,单个查询都不会返回超过 10,000 行。

在上面我们使用 format=txt 的格式来进行返回。我们可以使用 JSON 的格式:

python 复制代码
1.  POST /_query?format=json
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.    """
7.  }

同样我们也可以使用 CSV 格式作为输出:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.    """
7.  }

上述命令类似于 DSL:

markdown 复制代码
1.  GET sample_data/_search
2.  {
3.    "size": 5
4.  }

在输出的时候,我们可以使用字段来进行排序,比如按照时间字段 @timestamp 来进行降序排序:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.      | sort @timestamp desc
7.    """
8.  }

这个相当于 DSL 的如下查询:

markdown 复制代码
1.  GET sample_data/_search?size=5
2.  {
3.    "sort": [
4.      {
5.        "@timestamp": {
6.          "order": "desc"
7.        }
8.      }
9.    ]
10.  }

但是结果不完全一样。更加贴近的结果是:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | sort @timestamp desc
6.      | LIMIT 5    
7.    """
8.  }

我们运行如下的查询:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | WHERE client.ip = "172.21.2.162"
6.      | LIMIT 10
7.    """
8.  }

在上面 "=" 是一个赋值操作符。两个数据的类型不一样,不能赋值。

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | WHERE client.ip == "172.21.2.162"
6.      | LIMIT 10
7.    """
8.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | WHERE client.ip == TO_IP("172.21.2.162")
6.      | LIMIT 10
7.    """
8.  }

我们可以通过类型转换来得到相同的数据类型:

你也可以做如下的查询:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | WHERE TO_STRING(client.ip) == "172.21.2.162"
6.      | LIMIT 10
7.    """
8.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | WHERE client.ip IS NOT NULL
6.      | LIMIT 10
7.    """
8.  }

我们可以看到和如下查询的区别:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | sort @timestamp desc
6.      | LIMIT 5
7.    """
8.  }

在上面,我们交互了 sortLIMIT 的顺序,我们可以看到查询结果的变化:

上述命令类似于 DSL:

markdown 复制代码
1.  GET sample_data/_search
2.  {
3.    "size": 5,
4.    "sort": [
5.      {
6.        "@timestamp": {
7.          "order": "desc"
8.        }
9.      }
10.    ]
11.  }

我们可以使用 keep 来返回我们想要的字段:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.      | sort @timestamp desc
7.      | keep @timestamp, event.duration
8.    """
9.  }
perl 复制代码
1.  GET sample_data/_search?size=5
2.  {
3.    "_source": ["@timestamp", "event.duration"], 
4.    "sort": [
5.      {
6.        "@timestamp": {
7.          "order": "desc"
8.        }
9.      }
10.    ]
11.  }

或者:

arduino 复制代码
1.  GET sample_data/_search?size=5
2.  {
3.    "_source": false, 
4.    "sort": [
5.      {
6.        "@timestamp": {
7.          "order": "desc"
8.        }
9.      }
10.    ],
11.    "fields": [
12.      "@timestamp",
13.      "event.duration"
14.    ]
15.  }

查询数据

我们可以针对数据来进行查询:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.      | sort @timestamp desc
7.      | WHERE event.duration > 3000000
8.    """
9.  }

我们甚至可以含有多个 WHERE 查询:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.      | sort @timestamp desc
7.      | WHERE event.duration > 3000000
8.      | WHERE message LIKE "Connection *"
9.    """
10.  }

这个类似于 DSL 的如下查询:

bash 复制代码
1.  GET sample_data/_search
2.  {
3.    "size": 5,
4.    "query": {
5.      "bool": {
6.        "must": [
7.          {
8.            "wildcard": {
9.              "message": {
10.                "value": "Connection *"
11.              }
12.            }
13.          }
14.        ],
15.        "filter": [
16.          {
17.            "range": {
18.              "event.duration": {
19.                "gt": 3000000
20.              }
21.            }
22.          }
23.        ]
24.      }
25.    }
26.  }

确切地说和下面的类似:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | sort @timestamp desc
6.      | WHERE event.duration > 3000000
7.      | WHERE message LIKE "Connection *"
8.      | LIMIT 5
9.    """
10.  }

我们可以也可以使用 DROP 来删除我们不需要的列,比如 client.ip:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | LIMIT 5
6.      | sort @timestamp desc
7.      | WHERE event.duration > 3000000
8.      | WHERE message LIKE "Connection *"
9.      | DROP client.ip
10.    """
11.  }

在上面,我们删除了 client.ip 这个字段。这个和下面的 DSL 类似:

bash 复制代码
1.  GET sample_data/_search
2.  {
3.    "size": 5,
4.    "_source": {
5.      "excludes": [
6.        "client.ip"
7.      ]
8.    },
9.    "query": {
10.      "bool": {
11.        "must": [
12.          {
13.            "wildcard": {
14.              "message": {
15.                "value": "Connection *"
16.              }
17.            }
18.          }
19.        ],
20.        "filter": [
21.          {
22.            "range": {
23.              "event.duration": {
24.                "gt": 3000000
25.              }
26.            }
27.          }
28.        ]
29.      }
30.    }
31.  }

针对 ip 进行搜索:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.     | WHERE CIDR_MATCH(client.ip, "172.21.3.0/32", "172.21.3.15/32")
6.    """
7.  }

文本搜索

我们可以使用 ES|QL 针对文字进行搜索。由于目前的一些限制,它把 text 当做是 keyword。具体请详细查看文章 "Elasticsearch:ES|QL 的限制"。在目前的版中中,我们仅可以针对 keyword 进行搜索:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | WHERE message LIKE "Connected*"
6.  """
7.  }

但是如下的查询是没有任何结果的:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | WHERE message LIKE "Connected"
6.  """
7.  }

或:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | WHERE message LIKE "connected*"
6.  """
7.  }

我们可以使用如下的查询返回结果:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | WHERE message RLIKE "[cC]onnected.*"
6.    """
7.  }

计算值

使用 EVAL 命令将包含计算值的列追加到表中。 例如,以下查询附加一个 duration_ms 列。 该列中的值是通过将 event.duration 除以 1,000,000 计算得出的。 换句话说: event.duration 从纳秒转换为毫秒。

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | EVAL duration_ms = event.duration / 1000000.0
6.    """
7.  }

EVAL 支持多种 functions。 例如,要将数字四舍五入为最接近指定位数的数字,请使用 ROUND 函数:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.    FROM sample_data
5.    | EVAL duration_ms = ROUND(event.duration / 1000000.0, 1)
6.    """
7.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      ROW a = "2023-01-23T12:15:00.000Z - some text - 127.0.0.1"
5.      | DISSECT a "%{date} - %{msg} - %{ip}"
6.      | KEEP date, msg, ip
7.      | EVAL date = TO_DATETIME(date)
8.    """
9.  }

我们还可以比较时间:

ini 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      ROW a = "2023-01-23T12:15:00.000Z 127.0.0.1 some.email@foo.com 42"
5.      | GROK a "%{TIMESTAMP_ISO8601:date} %{IP:ip} %{EMAILADDRESS:email} %{NUMBER:num:int}"
6.      | EVAL date = TO_DATETIME(date)
7.      | KEEP date, ip, email, num
8.      | EVAL old = CASE(date > DATE_PARSE("yyyy-MM-dd","2023-01-22") , true,false)
9.    """
10.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      ROW first_name = "san", last_name = "zhang", height = 1.75
5.      | EVAL height_feet = height * 3.281, height_cm = height * 100
6.    """
7.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      ROW height = 1.75, weight = 70
5.      | EVAL BMI = 70/POW(height, 2)
6.      | EVAL healthy = CASE( BMI < 18.5, false, BMI > 23.9, false, true)
7.    """
8.  }

使用 DISSECT

你的数据可能包含非结构化字符串,你希望将其结构化以便更轻松地分析数据。 例如,示例数据包含如下日志消息:

"Connected to 10.1.0.3"

通过从这些消息中提取 IP 地址,你可以确定哪个 IP 接受了最多的客户端连接。

要在查询时构建非结构化字符串,你可以使用 ES|QL DISSECT 和 GROK 命令。 DISSECT 的工作原理是使用基于分隔符的模式分解字符串。 GROK 的工作原理类似,但使用正则表达式。 这使得 GROK 更强大,但通常也更慢。

在这种情况下,不需要正则表达式,因为 message 很简单:"Connected to ",后跟服务器 IP。 要匹配此字符串,你可以使用以下 DISSECT 命令:

python 复制代码
1.  POST _query/?format=csv
2.  {
3.    "query": """
4.    FROM sample_data
5.      | DISSECT message "Connected to %{server.ip}"
6.    """
7.  }

这会将 server.ip 列添加到具有与此模式匹配的消息的那些行。 对于其他行,server.ip 的值为空。

你可以在后续命令中使用 DISSECT 命令添加的新 server.ip 列。 例如,要确定每个服务器已接受多少个连接:

python 复制代码
1.  POST /_query?format=csv
2.  {
3.    "query": """
4.      FROM sample_data
5.      | WHERE STARTS_WITH(message, "Connected to")
6.      | DISSECT message "Connected to %{server.ip}"
7.      | STATS COUNT(*) BY server.ip
8.    """
9.  }

使用 GROK

以下示例解析包含时间戳、IP 地址、电子邮件地址和数字的字符串:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.       ROW a = "2023-01-23T12:15:00.000Z 127.0.0.1 some.email@foo.com 42"
5.      | GROK a "%{TIMESTAMP_ISO8601:date} %{IP:ip} %{EMAILADDRESS:email} %{NUMBER:num}"
6.      | EVAL date = TO_DATETIME(date)
7.      | KEEP date, ip, email, num
8.    """
9.  }

聚合

找出 event.duration 的最大值及最小值:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS MIN(event.duration), MAX(event.duration)
6.    """
7.  }

我们找出有多少个 client.ip,以及 event.duration 的平均值:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS COUNT_DISTINCT(client.ip), AVG(event.duration)
6.    """
7.  }

我们想知道每个 client.ip 的平均 event.duration 值:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS AVG(event.duration) BY client.ip
6.    """
7.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS AVG(event.duration), COUNT(*) BY client.ip
6.      | SORT COUNT(*)
7.    """
8.  }

上面的查询会失败。原因是 COUNT(*) 不是一个变量。我们可以使用如下的方法:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS AVG(event.duration), count=COUNT(*) BY client.ip
6.      | SORT count
7.    """
8.  }

或者:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS AVG(event.duration), COUNT(*) BY client.ip
6.      | SORT `COUNT(*)`
7.    """
8.  }

请注意上面的符号是 ` 而不是 '。

如果我们把上面的 COUNT 换成小写的 count:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS AVG(event.duration), COUNT(*) BY client.ip
6.      | SORT `count(*)`
7.    """
8.  }

原因是上面的两个 counts:一个是大写的,一个是小写的。它们不匹配。必须同时是大写,或者同时是小写。

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | STATS avg = AVG(event.duration) BY client.ip
6.    """
7.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.       | STATS median_duration = MEDIAN(event.duration)
6.    """
7.  }

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.       | STATS median_duration = MEDIAN(event.duration), max_duration = MAX(event.duration)
6.    """
7.  }

创建直方图

为了跟踪一段时间内的统计数据,ES|QL 允许你使用 AUTO_BUCKET 函数创建直方图。 AUTO_BUCKET 创建人性化的存储桶大小,并为每行返回一个与该行所属的结果存储桶相对应的值。

例如,要为 10 月 23 日的数据创建每小时存储桶:

less 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.       | KEEP @timestamp
6.       | EVAL bucket = AUTO_BUCKET (@timestamp, 24, "2023-10-23T00:00:00Z", "2023-10-23T23:59:59Z")
7.    """
8.  }

将 AUTO_BUCKET 与 STATS ... BY 结合起来创建直方图。 例如,要计算每小时的事件数:

less 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | KEEP @timestamp, event.duration
6.    | EVAL bucket = AUTO_BUCKET (@timestamp, 24, "2023-10-23T00:00:00Z", "2023-10-23T23:59:59Z")
7.    | STATS COUNT(*) BY bucket
8.    """
9.  }

每个小时的中位数统计:

less 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | KEEP @timestamp, event.duration
6.    | EVAL bucket = AUTO_BUCKET (@timestamp, 24, "2023-10-23T00:00:00Z", "2023-10-23T23:59:59Z")
7.    | STATS median_duration = MEDIAN(event.duration) BY bucket
8.  """
9.  }

我们更进一步对每个桶做细分,比如更进一步根据每个 client.ip 进行统计:

less 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | EVAL bucket = AUTO_BUCKET (@timestamp, 24, "2023-10-23T00:00:00Z", "2023-10-23T23:59:59Z")
6.    | STATS count = COUNT(*) BY bucket, client.ip
7.    """
8.  }

针对数字字段的桶分析

auto_bucket 还可以对数字字段进行操作,如下所示:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | EVAL bckts = AUTO_BUCKET(event.duration,20, 725448, 8268153)
6.    | SORT event.duration
7.    | STATS count = COUNT(*) by bckts
8.  """
9.  }

丰富数据

ES|QL 使你能够使用 ENRICH 命令使用 Elasticsearch 中索引的数据来丰富表。

在使用 ENRICH 之前,你首先需要 createexecute 你的 enrich policy。 以下请求创建并执行将 IP 地址链接到环境("Development"、"QA" 或 "Production")的策略:

markdown 复制代码
1.  PUT clientips
2.  {
3.    "mappings": {
4.      "properties": {
5.        "client.ip": {
6.          "type": "keyword"
7.        },
8.        "env": {
9.          "type": "keyword"
10.        }
11.      }
12.    }
13.  }
bash 复制代码
1.  PUT clientips/_bulk
2.  { "index" : {}}
3.  { "client.ip": "172.21.0.5", "env": "Development", "location": "loc1" }
4.  { "index" : {}}
5.  { "client.ip": "172.21.2.113", "env": "QA", "location": "loc2" }
6.  { "index" : {}}
7.  { "client.ip": "172.21.2.162", "env": "QA", "location": "loc3" }
8.  { "index" : {}}
9.  { "client.ip": "172.21.3.15", "env": "Production", "location":"loc4" }
10.  { "index" : {}}
11.  { "client.ip": "172.21.3.16", "env": "Production", "location": "loc5" }
bash 复制代码
1.  PUT /_enrich/policy/clientip_policy
2.  {
3.    "match": {
4.      "indices": "clientips",
5.      "match_field": "client.ip",
6.      "enrich_fields": ["env", "location"]
7.    }
8.  }
bash 复制代码
PUT /_enrich/policy/clientip_policy/_execute

创建并执行策略后,你可以将其与 ENRICH 命令一起使用:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | KEEP @timestamp, client.ip, event.duration
6.      | EVAL client.ip = TO_STRING(client.ip)
7.      | ENRICH clientip_policy ON client.ip
8.  """
9.  }
python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data
5.      | KEEP @timestamp, client.ip, event.duration
6.      | EVAL client.ip = TO_STRING(client.ip)
7.      | ENRICH clientip_policy ON client.ip WITH env
8.  """
9.  }

我们还可以添加其它定义在 clientip_policy 里的字段,比如:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | KEEP @timestamp, client.ip, event.duration
6.    | EVAL client.ip = TO_STRING(client.ip)
7.    | ENRICH clientip_policy ON client.ip WITH env, location
8.  """
9.  }

在上面,我们添加了 location:

我们甚至可以针对这些被丰富的字段进行聚会:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | KEEP @timestamp, client.ip, event.duration
6.    | EVAL client.ip = TO_STRING(client.ip)
7.    | ENRICH clientip_policy ON client.ip WITH env, location
8.    | STATS count = COUNT(*) by location
9.  """
10.  }

我还可以进行如下的统计:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | KEEP @timestamp, client.ip, event.duration
6.    | EVAL client.ip = TO_STRING(client.ip)
7.    | ENRICH clientip_policy ON client.ip WITH env, location
8.    | STATS count = COUNT(*) by env, location
9.  """
10.  }

我们还可以进行如下的统计:

less 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.    FROM sample_data
5.    | KEEP @timestamp, client.ip, event.duration
6.    | EVAL client.ip = TO_STRING(client.ip)
7.    | ENRICH clientip_policy ON client.ip WITH env, location
8.    | EVAL bucket = AUTO_BUCKET (@timestamp, 24, "2023-10-23T00:00:00Z", "2023-10-23T23:59:59Z")
9.    | STATS count = COUNT(*) by bucket,env, location
10.  """
11.  }

元数据运用

ES|QL 可以访问元数据字段。 目前支持的有:

  • _index:文档所属的索引名称。 该字段的类型为关键字。
  • _id:源文档的 ID。 该字段的类型为关键字。
  • _version:源文档的版本。 该字段的类型为 long。

要启用对这些字段的访问,需要为 FROM source 命令提供专用指令:

css 复制代码
FROM index [METADATA _index, _id]

仅当数据源是索引时元数据字段才可用。 因此,FROM 是唯一支持 METADATA 指令的源命令。比如,

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data [METADATA _index, _id]
5.      | LIMIT 3
6.    """
7.  }

从上面的返回数据中,我们可以看到 _index 及 _id 返回索引名称 sample_data 及文档的 ID。

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data [METADATA _index, _id,  _version]
5.      | LIMIT 3
6.      | WHERE _version == 1
7.      | EVAL key = CONCAT(_index, "_", _id)
8.      | KEEP _index, _version, _id, key
9.    """
10.  }

我们使用如下的命令来创建一个另外一个索引:

bash 复制代码
1.  PUT sample_data/_bulk
2.  {"index":{}}
3.  {"@timestamp":"2023-10-23T11:15:03.360Z","client.ip":"172.21.2.162","message":"Connected to 10.1.0.5","event.duration":3333333}

此外,与索引字段类似,一旦执行聚合,后续命令将无法再访问元数据字段,除非用作分组字段:

python 复制代码
1.  POST _query?format=txt
2.  {
3.    "query": """
4.      FROM sample_data* [METADATA _index, _id]
5.      | STATS max= MAX(event.duration) BY _index
6.    """
7.  }

ES|QL 多值字段

ES|QL 可以很好地读取多值字段。多值字段也就是在一个字段里有多个值。通常是以数组的形式出现。

css 复制代码
1.  POST /mv/_bulk?refresh
2.  {"index":{}}
3.  {"a":1,"b":[2,1]}
4.  {"index":{}}
5.  {"a":2,"b":3}

多值字段以 txt 数组的形式返回:

bash 复制代码
1.  POST /_query?format=txt
2.  {
3.    "query": "FROM mv | LIMIT 2"
4.  }

多值字段中值的相对顺序未定义。 它们通常会按升序排列,但不要依赖于此。

less 复制代码
1.  POST /_query?format=txt
2.  {
3.    "query": "FROM mv | EVAL b=MV_MIN(b) | EVAL b + 2, a + b | LIMIT 4"
4.  }

在 Discover 中进行查询

我们可以在 Discover 中进行查询:

在上面,我们选择 Try ES|QL

在上面,我们填入:

less 复制代码
 1.    FROM sample_data
2.    | KEEP @timestamp, client.ip, event.duration
3.    | EVAL client.ip = TO_STRING(client.ip)
4.    | ENRICH clientip_policy ON client.ip WITH env, location
5.    | EVAL bucket = AUTO_BUCKET (@timestamp, 24, "2023-10-23T00:00:00Z", "2023-10-23T23:59:59Z")
6.    | STATS count = COUNT(*) by bucket,env, location

我们看到的是一个可视化化图。它是一个饼图,我们可以把它保存到可视化中,并最终被 Dashboard 所示使用:

Clean up

我们可以执行如下的命令来清除之前的数据:

sql 复制代码
1.  DELETE sample_data
2.  DELETE clientips
3.  DELETE /_enrich/policy/clientip_policy
相关推荐
Biehmltym4 小时前
【AI】09AI Agent LLM → Streaming → Session 记录 的完整链路
大数据·人工智能·elasticsearch
小湘西5 小时前
Elasticsearch 的一些默认配置上下限
java·大数据·elasticsearch
Dxy12393102168 小时前
Elasticsearch 8如何做好标题搜索
大数据·elasticsearch
斯普信云原生组9 小时前
Elasticsearch(ES) 内存 CPU 过高问题排查报告
大数据·elasticsearch·搜索引擎
弘毅 失败的 mian10 小时前
Git 分支管理
大数据·经验分享·笔记·git·elasticsearch
阿坤带你走近大数据10 小时前
Elasticsearch(ES)的基本概念、架构及基本使用介绍
大数据·elasticsearch
Elastic 中国社区官方博客10 小时前
使用 Elasticsearch 中的结构化输出创建可靠的 agents
大数据·人工智能·elk·elasticsearch·搜索引擎·ai·全文检索
G皮T11 小时前
【Elasticsearch】查询性能调优(六):track_total_hits 影响返回结果的相关性排序吗
大数据·数据库·elasticsearch·搜索引擎·全文检索·性能·opensearch
LCG米12 小时前
嵌入式Linux系统构建:为STM32MP157移植Buildroot并开发温湿度采集驱动
linux·stm32·elasticsearch
phil zhang13 小时前
Celer:为大型C/C++项目打造的极简包管理器
开发语言·c++·elasticsearch