1、背景
此處來簡單學習一下 elasticsearch
的 date_histogram
直方圖聚合。它和普通的直方圖histogram
聚合差不多,但是date_histogram
只可於 日期或日期範圍
型別的值一起使用。
2、bucket_key如何計算
- 假設我們存在如下時間
2022-11-29 23:59:59
。 - 在
es
中時間為2022-11-29 23:59:59 +0000
,因為上方的時間沒有時區,所以會自動加上0時區
,對應的時間戳為1669766399000
- 此處假設以
1d
為單位來聚合 - 聚合統計中
time_zone
的值為+0800
- bucket_key計算公式為
bucket_key = localToUtc(Math.floor(utcToLocal(value) / interval) * interval))
計算步驟如下:(此處是我自己的理解,如果不對歡迎指出)
utcToLocal(value)
= 1669766399000(utc的值
)+
8*60*60*1000(time_zone +8的值
) = 1669795199000Math.floor(utcToLocal(value) / interval) * interval)
= Math.floor(1669795199000 / (24*60*60*1000)) * (24*60*60*1000) = 1669766400000localToUtc(...)
=1669766400000-
86060*1000=1669737600000key_as_string
=utc時間1669737600000轉換成東八區時間展示為=2022/11/30 00:00:00
3、前置知識
- 日期(
date
)型別的欄位在es
中是以long
型別的值儲存的。 es
中預設 預設的時區是0時區
。- 如果我們有一個東八區的時間,那麼在es中是如何儲存的呢?
- 假設存在如下mapping
"invoked_time": {
"type": "date",
"format": ["yyyy-MM-dd HH:mm:ss"]
}
- 如果我們此時存在 如下
東八區
時間2022-11-29 12:12:12
,那麼在 es 會儲存為2022-11-29 12:12:12 +0000
對應的時間戳,為什麼會加上+0000
,因為我們自己的時間字串中沒有時區,就會加上預設的0時區。
4、日曆和固定時間間隔
既然我們是根據時間來進行聚合,那麼必然就會涉及到這麼一個問題。假設以天為單位來聚合,那麼1天
到底是固定
的24小時
呢,還是可變
的呢? 因為存在時區
的關係,在有的國家,在某些時區下,一天就不一定是24個小時。因此在es
中提供了calendar-aware time intervals
, 和 fixed time intervals.
兩種型別。
4.1 Calendar intervals 日曆間隔
日曆感知間隔使用calendar_interval引數配置。
它可以自動感應到日曆中的時區變化。它的單位只能是單數,不可是複數,比如2d
就是錯誤的。
日曆間隔 可用的單位為:分鐘 (1m
)、小時 (1h
)、天 (1d
)、星期 (1w
)、月 (1M
)、季度 (1q
)、年 (1y
)
舉個例子:1m
是從何時開始的,何時結束的?.
所有的分鐘都從00
秒開始。一分鐘是指定時區中第一分鐘的00秒和下一分鐘的00秒之間的時間間隔,用於補償任何介於其間的閏秒,因此整點後的分鐘數和秒數在開始和結束時是相同的。
4.2 Fixed intervals 固定間隔
固定間隔使用fixed_interval引數進行配置。
與日曆感知間隔相比,固定間隔是固定數量的SI
單位,無論它們落在日曆的哪個位置,都不會偏離。一秒總是由1000ms組成
。這允許以支援的單位的任意倍數指定固定間隔。但是,這意味著固定間隔不能表示其他單位,例如月,因為一個月的持續時間不是固定的數量。嘗試指定月或季度等日曆間隔將引發異常。
固定間隔 可用的單位為:
毫秒 (ms
)
秒 (s
)
定義為每個1000毫秒
分鐘 (m
)
所有分鐘都從00秒開始。 定義為每個60秒(60,000毫秒)
小時 (h
)
所有小時都從00分00秒開始。 定義為每60分鐘(3,600,000毫秒)
天 (d
)
所有天都在儘可能早的時間開始,通常是00:00:00(午夜)。 定義為24小時(86,400,000毫秒)
5、資料準備
5.1 準備mapping
PUT /index_api_invoked_time
{
"settings": {
"number_of_shards": 1
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"api": {
"type": "keyword"
},
"invoked_time": {
"type": "date",
"format": ["yyyy-MM-dd HH:mm:ss"]
}
}
}
}
5.2 準備資料
PUT /index_api_invoked_time/_bulk
{"index":{"_id":1}}
{"api":"/user/infos","invoked_time": "2022-11-26 00:00:00"}
{"index":{"_id":2}}
{"api":"/user/add"}
{"index":{"_id":3}}
{"api":"/user/update","invoked_time": "2022-11-26 23:59:59"}
{"index":{"_id":4}}
{"api":"/user/list","invoked_time": "2022-11-27 00:00:00"}
{"index":{"_id":5}}
{"api":"/user/export","invoked_time": "2022-11-29 23:59:59"}
{"index":{"_id":6}}
{"api":"/user/detail","invoked_time": "2022-12-01 01:00:00"}
6、聚合案例
6.1 dsl
POST /index_api_invoked_time/_search
{
"size": 0,
"aggregations": {
"agg_01": {
"date_histogram": {
"field": "invoked_time",
"calendar_interval": "1d",
"min_doc_count": 0,
"missing": "2022-11-27 23:59:59",
"time_zone": "+08:00",
"offset":"+10h",
"extended_bounds": {
"min": "2022-11-26 10:00:00",
"max": "2022-12-03 10:00:00"
}
}
}
}
}
6.2 java程式碼
@Test
@DisplayName("日期直方圖聚合")
public void test01() throws IOException {
SearchRequest request = SearchRequest.of(searchRequest ->
searchRequest.index("index_api_invoked_time")
.size(0)
.aggregations("agg_01", agg ->
agg.dateHistogram(dateAgg ->
// 聚合的欄位
dateAgg.field("invoked_time")
// 聚合的單位,日曆感知 單位為天,此時的一天不一定為24小時,因為夏令時時,有些國家一天可能只有23個小時
.calendarInterval(CalendarInterval.Day)
// 固定間隔, 此處可以指定 1天就是24小時
// .fixedInterval()
// 如果聚合的桶中,沒有文件也返回
.minDocCount(0)
// 對於文件中,聚合欄位缺失,此處給一個預設值,預設情況是此文件不參與聚合
.missing(DateTime.of("2022-11-27 23:59:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
// 時區
.timeZone("+08:00")
// 偏移,偏移是在時間在對應的時區調整之後,再去偏移
.offset(time -> time.time("+10h"))
// 如果返回的桶資料不在這個邊界中,則給預設值,不會對資料進行過濾。
.extendedBounds(bounds ->
bounds.min(FieldDateMath.of(f -> f.expr("2022-11-26 10:00:00")))
.max(FieldDateMath.of(f -> f.expr("2022-12-03 10:00:00")))
)
)
)
);
System.out.println("request: " + request);
SearchResponse<String> response = client.search(request, String.class);
System.out.println("response: " + response);
}