elasticsearch 聚合之 date_histogram 聚合

2022-11-30 18:00:47

1、背景

此處來簡單學習一下 elasticsearchdate_histogram直方圖聚合。它和普通的直方圖histogram聚合差不多,但是date_histogram只可於 日期或日期範圍 型別的值一起使用。

2、bucket_key如何計算

  1. 假設我們存在如下時間 2022-11-29 23:59:59
  2. es中時間為 2022-11-29 23:59:59 +0000,因為上方的時間沒有時區,所以會自動加上0時區,對應的時間戳為 1669766399000
  3. 此處假設以 1d 為單位來聚合
  4. 聚合統計中 time_zone的值為+0800
  5. bucket_key計算公式為 bucket_key = localToUtc(Math.floor(utcToLocal(value) / interval) * interval))

計算步驟如下:(此處是我自己的理解,如果不對歡迎指出)

  • utcToLocal(value) = 1669766399000(utc的值) + 8*60*60*1000(time_zone +8的值) = 1669795199000
  • Math.floor(utcToLocal(value) / interval) * interval) = Math.floor(1669795199000 / (24*60*60*1000)) * (24*60*60*1000) = 1669766400000
  • localToUtc(...)=1669766400000-86060*1000=1669737600000
  • key_as_string=utc時間1669737600000轉換成東八區時間展示為=2022/11/30 00:00:00

3、前置知識

  1. 日期(date)型別的欄位在 es中是以 long型別的值儲存的。
  2. es中預設 預設的時區是 0時區
  3. 如果我們有一個東八區的時間,那麼在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);
}

6.3 聚合結果

7、完整程式碼

https://gitee.com/huan1993/spring-cloud-parent/blob/master/es/es8-api/src/main/java/com/huan/es8/aggregations/bucket/DateHistogramAggs.java

8、參考檔案

  1. https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html
  2. https://www.pipiho.com/es/7.7/cn/search-aggregations-bucket-datehistogram-aggregation.html