在今天的文章中,我将展示如何使用 transform 从数据中获得有用的见解。 所有示例都使用 Kibana 示例数据集之一。 有关更详细的分步示例,请参阅教程 “Elasticsearch:Transforms 介绍”。在今天的展示中,我将使用最新的 Elastic Stack 8.4.3 来进行展示。
准备数据
我们将使用 Kibana 中自带的数据 eCommerce,Flight 及 web logs 数据来进行展示:
这样,我们就加载了一个叫做 kibana_sample_data_ecommerce 的索引到 Elasticsearch 中了。
按照同样的方法,我们加载上面所示的 Sample flight data:
它生成一个叫做 kibana_sample_data_flights 的索引。
我也加载 Sample web logs 样本数据:
它将生成 kibana_sample_data_logs 索引数据。
寻找你最好的客户
此示例使用电子商务订单样本数据集来查找在假设的网上商店中花费最多的客户。 让我们使用 pivot 类型的转换,以便目标索引包含订单数量、订单总价、唯一产品数量和每个订单的平均价格,以及每个客户的订购产品总量。
我们进入到 transform 的界面:
按照上面的方法,我们把各个项逐个添加,最终我们可以看到如下的表格:
除了使用上面的 transform 界面之外,我们也可以使用 API 来完成。我们可以使用 preview transform 及 create transform API 来完成:
`
1. POST _transform/_preview
2. {
3. "source": {
4. "index": "kibana_sample_data_ecommerce"
5. },
6. "dest" : {
7. "index" : "sample_ecommerce_orders_by_customer"
8. },
9. "pivot": {
10. "group_by": {
11. "user": { "terms": { "field": "user" }},
12. "customer_id": { "terms": { "field": "customer_id" }}
13. },
14. "aggregations": {
15. "order_count": { "value_count": { "field": "order_id" }},
16. "total_order_amt": { "sum": { "field": "taxful_total_price" }},
17. "avg_amt_per_order": { "avg": { "field": "taxful_total_price" }},
18. "avg_unique_products_per_order": { "avg": { "field": "total_unique_products" }},
19. "total_unique_products": { "cardinality": { "field": "products.product_id" }}
20. }
21. }
22. }
`
由于我们使用了 _preview,目标索引被忽略了。在上面,我们使用了两个 group_by 字段。这意味着转换包含每个 user 和 customer_id 组合的唯一行。 在这个数据集中,这两个字段都是唯一的。 通过将两者都包含在转换中,它为最终结果提供了更多上下文。
preview 转换 API 使你能够提前查看转换的布局,其中填充了一些示例值。 例如:
`
1. {
2. "preview": [3. {4. "total_order_amt": 3946.9765625,5. "order_count": 59,6. "total_unique_products": 116,7. "avg_unique_products_per_order": 2,8. "customer_id": "10",9. "user": "recip",10. "avg_amt_per_order": 66.8979078389830411. },12. {13. "total_order_amt": 5196.6796875,14. "order_count": 75,15. "total_unique_products": 148,16. "avg_unique_products_per_order": 2,17. "customer_id": "11",18. "user": "fuzzy",19. "avg_amt_per_order": 69.289062520. },21. {22. "total_order_amt": 9659.125,23. "order_count": 135,24. "total_unique_products": 266,25. "avg_unique_products_per_order": 2,26. "customer_id": "12",27. "user": "brigitte",28. "avg_amt_per_order": 71.5490740740740729. },30. ...31. ]
32. }
`
这种 transform 可以更轻松地回答以下问题:
- 哪些客户花费最多?
- 哪些客户在每个订单中花费最多?
- 哪些客户最常订购?
- 哪些客户订购的不同产品数量最少?
可以单独使用聚合来回答这些问题,但是 transform 允许我们将这些数据作为以客户为中心的索引进行持久化。 这使我们能够大规模分析数据,并为从以客户为中心的角度探索和导航数据提供更大的灵活性。 在某些情况下,它甚至可以使创建可视化变得更加简单。
寻找延误最多的航空公司
此示例使用航班示例数据集来找出延误最多的航空公司。 首先,使用查询过滤器过滤源数据,使其排除所有已取消的航班。 然后将数据转换为包含不同的航班数量、延误分钟数的总和以及航空承运人的飞行分钟数的总和。 最后,使用 bucket_script 确定实际延误的飞行时间百分比。
`
1. POST _transform/_preview
2. {
3. "source": {
4. "index": "kibana_sample_data_flights",
5. "query": {
6. "bool": {
7. "filter": [
8. { "term": { "Cancelled": false } }
9. ]
10. }
11. }
12. },
13. "dest" : {
14. "index" : "sample_flight_delays_by_carrier"
15. },
16. "pivot": {
17. "group_by": {
18. "carrier": { "terms": { "field": "Carrier" }}
19. },
20. "aggregations": {
21. "flights_count": { "value_count": { "field": "FlightNum" }},
22. "delay_mins_total": { "sum": { "field": "FlightDelayMin" }},
23. "flight_mins_total": { "sum": { "field": "FlightTimeMin" }},
24. "delay_time_percentage": {
25. "bucket_script": {
26. "buckets_path": {
27. "delay_time": "delay_mins_total.value",
28. "flight_time": "flight_mins_total.value"
29. },
30. "script": "(params.delay_time / params.flight_time) * 100"
31. }
32. }
33. }
34. }
35. }
`
首先,过滤源数据以仅选择未取消的航班。目标索引被忽略,这是因为我们选择了 preview。数据按包含航空公司名称的 carrier 字段分组。此 bucket_script 对聚合返回的结果执行计算。 在这个特定示例中,它计算了延误占用的行程时间百分比。
预览显示,新索引将包含每个 carrier 的如下数据:
`
1. {
2. "preview": [3. {4. "carrier": "ES-Air",5. "flights_count": 2802,6. "flight_mins_total": 1436927.5130677223,7. "delay_time_percentage": 9.335543983955839,8. "delay_mins_total": 1341459. },10. {11. "carrier": "JetBeats",12. "flights_count": 2833,13. "flight_mins_total": 1451143.6898144484,14. "delay_time_percentage": 8.937088787987832,15. "delay_mins_total": 12969016. },17. {18. "carrier": "Kibana Airlines",19. "flights_count": 2832,20. "flight_mins_total": 1419081.404241085,21. "delay_time_percentage": 9.088273556017194,22. "delay_mins_total": 12897023. },24. ...25. ]
26. }
`
这种转换可以更轻松地回答以下问题:
- 哪个航空公司的延误占飞行时间的百分比最多?
查找可疑的客户端 IP
此示例使用 Web 日志示例数据集来识别可疑客户端 IP。 它转换数据,使新索引包含字节总和以及不同 URL、代理、按位置的传入请求以及每个客户端 IP 的地理目的地的数量。 它还使用过滤器聚合来计算每个客户端 IP 接收到的特定类型的 HTTP 响应。 最终,以下示例将 Web 日志数据转换为以实体为中心的索引,其中实体为 clientip。
`
1. PUT _transform/suspicious_client_ips
2. {
3. "source": {
4. "index": "kibana_sample_data_logs"
5. },
6. "dest" : {
7. "index" : "sample_weblogs_by_clientip"
8. },
9. "sync" : {
10. "time": {
11. "field": "timestamp",
12. "delay": "60s"
13. }
14. },
15. "pivot": {
16. "group_by": {
17. "clientip": { "terms": { "field": "clientip" } }
18. },
19. "aggregations": {
20. "url_dc": { "cardinality": { "field": "url.keyword" }},
21. "bytes_sum": { "sum": { "field": "bytes" }},
22. "geo.src_dc": { "cardinality": { "field": "geo.src" }},
23. "agent_dc": { "cardinality": { "field": "agent.keyword" }},
24. "geo.dest_dc": { "cardinality": { "field": "geo.dest" }},
25. "responses.total": { "value_count": { "field": "timestamp" }},
26. "success" : {
27. "filter": {
28. "term": { "response" : "200"}}
29. },
30. "error404" : {
31. "filter": {
32. "term": { "response" : "404"}}
33. },
34. "error5xx" : {
35. "filter": {
36. "range": { "response" : { "gte": 500, "lt": 600}}}
37. },
38. "timestamp.min": { "min": { "field": "timestamp" }},
39. "timestamp.max": { "max": { "field": "timestamp" }},
40. "timestamp.duration_ms": {
41. "bucket_script": {
42. "buckets_path": {
43. "min_time": "timestamp.min.value",
44. "max_time": "timestamp.max.value"
45. },
46. "script": "(params.max_time - params.min_time)"
47. }
48. }
49. }
50. }
51. }
`
在这次练习中,我们没有使用 preview,而是直接生成相应的索引 sample_weblogs_by_clientip。在上面,我们使用了 sync 配置。它将转换配置为连续运行。 它使用时间戳字段来同步源索引和目标索引。 最坏情况下的摄取延迟为 60 秒。数据按 clientip 字段分组。filter 聚合,计算响应字段中成功 (200) 响应的出现次数。 以下两个聚合(error 404 和 error 5xx)按错误代码计算错误响应,匹配精确值或响应代码范围。这个 bucket_script 根据聚合的结果计算 clientip 访问的持续时间。
创建转换后,你必须启动它:
POST _transform/suspicious_client_ips/_start
不久之后,第一个结果应该在目标索引中可用:
GET sample_weblogs_by_clientip/_search
搜索结果显示每个客户端 IP 的数据如下:
`
1. {
2. "took": 0,
3. "timed_out": false,
4. "_shards": {
5. "total": 1,
6. "successful": 1,
7. "skipped": 0,
8. "failed": 0
9. },
10. "hits": {
11. "total": {
12. "value": 1000,
13. "relation": "eq"
14. },
15. "max_score": 1,
16. "hits": [
17. {
18. "_index": "sample_weblogs_by_clientip",
19. "_id": "MOeHH_cUL5urmartKj-b5UQAAAAAAAAA",
20. "_score": 1,
21. "_source": {
22. "geo": {
23. "src_dc": 1,
24. "dest_dc": 2
25. },
26. "success": 2,
27. "error404": 0,
28. "clientip": "0.72.176.46",
29. "agent_dc": 2,
30. "bytes_sum": 4422,
31. "responses": {
32. "total": 2
33. },
34. "error5xx": 0,
35. "url_dc": 2,
36. "timestamp": {
37. "duration_ms": 521916980,
38. "min": "2022-10-17T07:51:57.333Z",
39. "max": "2022-10-23T08:50:34.313Z"
40. }
41. }
42. },
43. {
44. "_index": "sample_weblogs_by_clientip",
45. "_id": "MGNzzFDYQ28eyzVadV_UHVUAAAAAAAAA",
46. "_score": 1,
47. "_source": {
48. "geo": {
49. "src_dc": 1,
50. "dest_dc": 3
51. },
52. "success": 3,
53. "error404": 0,
54. "clientip": "0.207.229.147",
55. "agent_dc": 3,
56. "bytes_sum": 14174,
57. "responses": {
58. "total": 3
59. },
60. "error5xx": 0,
61. "url_dc": 3,
62. "timestamp": {
63. "duration_ms": 694814737,
64. "min": "2022-10-19T11:02:32.392Z",
65. "max": "2022-10-27T12:02:47.129Z"
66. }
67. }
68. },
69. ...
`
注意:与其他 Kibana 示例数据集一样,Web 日志示例数据集包含相对于你安装它的时间戳,包括未来的时间戳。 连续变换将拾取过去的数据点。 如果你之前安装了 web 日志示例数据集,你可以卸载并重新安装它,时间戳会发生变化。
这种转换可以更轻松地回答以下问题:
- 哪些客户端 IP 传输的数据量最多?
- 哪些客户端 IP 与大量不同的 URL 交互?
- 哪些客户端 IP 的错误率高?
- 哪些客户端 IP 正在与大量目的地国家/地区进行交互?
查找每个 IP 地址的最后一个日志事件
此示例使用 Web 日志示例数据集从 IP 地址查找最后一个日志。 让我们在连续模式下使用 latest 类型的变换。 它将每个唯一键的最新文档从源索引复制到目标索引,并在新数据进入源索引时更新目标索引。
选择 clientip 字段作为唯一键; 数据按此字段分组。 选择时间戳作为按时间顺序对数据进行排序的日期字段。 对于连续模式,指定用于标识新文档的日期字段,以及检查源索引更改的时间间隔。
此转换创建包含每个客户端 IP 的最新登录日期的目标索引。 当转换以连续模式运行时,目标索引将更新为进入源索引的新数据。
我们也可以使用 API 来完成:
`
1. PUT _transform/last-log-from-clientip
2. {
3. "source": {
4. "index": [
5. "kibana_sample_data_logs"
6. ]
7. },
8. "latest": {
9. "unique_key": [
10. "clientip"
11. ],
12. "sort": "timestamp"
13. },
14. "frequency": "1m",
15. "dest": {
16. "index": "last-log-from-clientip"
17. },
18. "sync": {
19. "time": {
20. "field": "timestamp",
21. "delay": "60s"
22. }
23. },
24. "retention_policy": {
25. "time": {
26. "field": "timestamp",
27. "max_age": "30d"
28. }
29. },
30. "settings": {
31. "max_page_search_size": 500
32. }
33. }
`
在上面,我们定义 clientip 字段来进行分组。我们定义 timestamp 字段来进行排序。我们使用 frequency 来定义检查 source 有没有变化的间隔。我们使用 sync 来定义 延迟的时间以保证 source 和 destination 进行同步。在上面,我们也定义了 retention_policy。早于配置值的文档将从目标索引中删除。
创建转换后,启动它:
POST _transform/last-log-from-clientip/_start
转换处理数据后,搜索目标索引:
GET last_log_event_for_each_ip/_search
搜索结果显示每个客户端 IP 的数据如下:
`
1. {
2. "took": 1,
3. "timed_out": false,
4. "_shards": {
5. "total": 1,
6. "successful": 1,
7. "skipped": 0,
8. "failed": 0
9. },
10. "hits": {
11. "total": {
12. "value": 1001,
13. "relation": "eq"
14. },
15. "max_score": 1,
16. "hits": [
17. {
18. "_index": "last_log_event_for_each_ip",
19. "_id": "MOeHH_cUL5urmartKj-b5UQAAAAAAAAA",
20. "_score": 1,
21. "_source": {
22. "referer": "http://twitter.com/error/don-lind",
23. "request": "/elasticsearch",
24. "agent": "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)",
25. "extension": "",
26. "memory": null,
27. "ip": "0.72.176.46",
28. "index": "kibana_sample_data_logs",
29. "message": "0.72.176.46 - - [2018-09-18T06:31:00.572Z] \"GET /elasticsearch HTTP/1.1\" 200 7065 \"-\" \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\"",
30. "url": "https://www.elastic.co/downloads/elasticsearch",
31. "tags": [
32. "success",
33. "info"
34. ],
35. "geo": {
36. "srcdest": "US:PH",
37. "src": "US",
38. "coordinates": {
39. "lon": -124.1127917,
40. "lat": 40.80338889
41. },
42. "dest": "PH"
43. },
44. "utc_time": "2022-12-13T06:31:00.572Z",
45. "bytes": 7065,
46. "machine": {
47. "os": "ios",
48. "ram": 12884901888
49. },
50. "response": 200,
51. "clientip": "0.72.176.46",
52. "host": "www.elastic.co",
53. "event": {
54. "dataset": "sample_web_logs"
55. },
56. "phpmemory": null,
57. "timestamp": "2022-12-13T06:31:00.572Z"
58. }
59. },
60. ...
`
这种转换可以更轻松地回答以下问题:
- 与特定 IP 地址关联的最新日志事件是什么?
查找向服务器发送最多字节的客户端 IP
此示例使用 Web 日志示例数据集来查找每小时向服务器发送的字节数最多的客户端 IP。 该示例使用带有 top_metrics 聚合的数据 pivot 变换。
按时间字段上的日期直方图(date histogram)对数据进行分组,间隔为一小时。 在字节字段上使用 max 聚合来获取发送到服务器的最大数据量。 如果没有 max 聚合,API 调用仍会返回发送最多字节的客户端 IP,但不会返回它发送的字节数。 在 top_metrics 属性中,指定 clientip 和 geo.src,然后按 bytes 字段降序对它们进行排序。 转换返回发送数据量最大的 client IP 和相应位置的 2 个字母的 ISO 代码。
`
1. POST _transform/_preview
2. {
3. "source": {
4. "index": "kibana_sample_data_logs"
5. },
6. "pivot": {
7. "group_by": {
8. "timestamp": {
9. "date_histogram": {
10. "field": "timestamp",
11. "fixed_interval": "1h"
12. }
13. }
14. },
15. "aggregations": {
16. "bytes.max": {
17. "max": {
18. "field": "bytes"
19. }
20. },
21. "top": {
22. "top_metrics": {
23. "metrics": [
24. {
25. "field": "clientip"
26. },
27. {
28. "field": "geo.src"
29. }
30. ],
31. "sort": {
32. "bytes": "desc"
33. }
34. }
35. }
36. }
37. }
38. }
`
- 数据按时间字段的日期直方图分组,间隔为一小时。
- 计算 bytes 字段的最大值。
- 指定要返回的顶部文档的字段(clientip 和 geo.src)和排序方法(具有最高 bytes 值的文档)。
上面的 API 调用返回一个类似这样的响应:
`
1. {
2. "preview" : [
3. {
4. "top" : {
5. "clientip" : "223.87.60.27",
6. "geo.src" : "IN"
7. },
8. "bytes" : {
9. "max" : 6219
10. },
11. "timestamp" : "2021-04-25T00:00:00.000Z"
12. },
13. {
14. "top" : {
15. "clientip" : "99.74.118.237",
16. "geo.src" : "LK"
17. },
18. "bytes" : {
19. "max" : 14113
20. },
21. "timestamp" : "2021-04-25T03:00:00.000Z"
22. },
23. {
24. "top" : {
25. "clientip" : "218.148.135.12",
26. "geo.src" : "BR"
27. },
28. "bytes" : {
29. "max" : 4531
30. },
31. "timestamp" : "2021-04-25T04:00:00.000Z"
32. },
33. ...
34. ]
35. }
`
通过客户 ID 获取客户姓名和电子邮件地址
本示例使用电子商务样本数据集创建基于客户 ID 的以实体为中心的索引,并使用 top_metrics 聚合获取客户姓名和电子邮件地址。
按 customer_id 对数据进行分组,然后添加一个 top_metrics 聚合,其中 metrics 是 email、customer_first_name.keyword 和 customer_last_name.keyword 字段。 按 order_date 以降序对 top_metrics 进行排序。 API 调用如下所示:
`
1. POST _transform/_preview
2. {
3. "source": {
4. "index": "kibana_sample_data_ecommerce"
5. },
6. "pivot": {
7. "group_by": {
8. "customer_id": {
9. "terms": {
10. "field": "customer_id"
11. }
12. }
13. },
14. "aggregations": {
15. "last": {
16. "top_metrics": {
17. "metrics": [
18. {
19. "field": "email"
20. },
21. {
22. "field": "customer_first_name.keyword"
23. },
24. {
25. "field": "customer_last_name.keyword"
26. }
27. ],
28. "sort": {
29. "order_date": "desc"
30. }
31. }
32. }
33. }
34. }
35. }
`
说明:
- 数据按 customer_id 字段上的术语聚合进行分组。
- 指定按订单日期降序返回的字段(电子邮件和姓名字段)。
API 返回类似于以下内容的响应:
`
1. {
2. "preview" : [3. {4. "last" : {5. "customer_last_name.keyword" : "Long",6. "customer_first_name.keyword" : "Recip",7. "email" : "recip@long-family.zzz"8. },9. "customer_id" : "10"10. },11. {12. "last" : {13. "customer_last_name.keyword" : "Jackson",14. "customer_first_name.keyword" : "Fitzgerald",15. "email" : "fitzgerald@jackson-family.zzz"16. },17. "customer_id" : "11"18. },19. {20. "last" : {21. "customer_last_name.keyword" : "Cross",22. "customer_first_name.keyword" : "Brigitte",23. "email" : "brigitte@cross-family.zzz"24. },25. "customer_id" : "12"26. },27. ...28. ]
29. }
`