Elasticsearch中的date與時區問題

1 前言

本文主要講解Elasticsearch中date類型數據的底層存儲原理,以及對帶時區日期字符串不帶時區的日期字符串如何在ES底層存儲進行驗證。對於直接存儲Long類型時間戳,不作過多描述。

1.1 Date類型數據的存儲

UTC(Universal Time Coordinated) 叫做世界統一時間,中國大陸所用的時間是東8區時間,比UTC時間超前8小時。即與 UTC 的時差是 +8 ,也就是 UTC+8

在Elasticsearch內部,不論 date 是什麼展示格式,所有date類型數據(時間字符串 or 時間戳等)在 Elasticsearch 內部存儲時全部都會轉換成 UTC時間戳(並且把時區也會計算進去),最後以milliseconds-since-the-epoch 作爲存儲的格式。

1.2 Date類型數據的查詢

在Elasticsearch內部,date被轉爲UTC,並被存儲爲一個長整型數字,代表從1970年1月1號0點到現在的毫秒數。

date類型字段上的查詢會在內部被轉爲對long型值的範圍查詢,查詢的結果類型是字符串。

  • 假如插入的時候,值是"2018-01-01",則返回"2018-01-01"

  • 假如插入的時候,值是"2018-01-01 12:00:00",則返回"2018-01-01 12:00:00"

  • 假如插入的時候,值是1514736000000,則返回"1514736000000"。(進去是long型,出來是String型)

在查詢日期時,會執行下面的過程:

  • 轉換成 long 整形格式的範圍(range) 查詢
  • 得到聚合的結果
  • 將結果中的 date 類型(long 整型數據)根據 date format 字段轉換回對應的展示格式

1.3 Date類型數據的展示

Elasticsearch 數據是以 json格式存儲的,而 json中是並沒有 date 數據類型,因此 Elasticsearch 中雖然有 date 類型,但在展示時卻要轉化成另外的格式。

date 類型在 Elasticsearch 展示的格式有下面幾種:

  • 將日期時間格式化後的字符串,如 "2015-01-01" 或者 "2015/01/01 12:10:30"
  • long 型的整數,意義是 milliseconds-since-the-epoch,翻譯一下就是自 1970-01-01 00:00:00 UTC 以來經過的毫秒數。
  • int 型的整數,意義是 seconds-since-the-epoch, 是指自 1970-01-01 00:00:00 UTC 以來經過的秒數。

2. 驗證

2.1 準備工作

創建索引,設置date類型字段format

PUT /ddate

PUT /ddate/default/_mapping
{
  "properties": {
    "name":{
      "type": "keyword"
    },
    "ddate":{
      "type": "date",
      "format": "strict_date_optional_time||yyyy-MM-dd HH:mm:ss||epoch_millis"
    }
  }
}

2.1 無時區信息驗證

數據情況:
date類型數據信息如下,通過使用Java程序,分別獲取該時間字符串“2019-09-24 00:00:00”東8區時間戳、GMT時間戳、以及UTC時間戳。如下:

獲取指定時區時間戳信息,詳情可見:Java獲取指定時區的時間戳

注意:
當時間字符串中沒有時區信息時,此時,在ES內部會將其(“2019-09-24 00:00:00“)當成是0時區“2019-09-24 00:00:00”
而在我們的Java程序中,Date.getTime()所獲取的卻是將其當做是東8區的時間“2019-09-24 00:00:00”(即返回的是北京時間1970年01月1日0點0分0秒以來的毫秒數,對應UTC時間1970年01月1日8點0分0秒以來的毫秒數,其數值大小等於0時區“2019-09-23 16:00:00”所對應的時間戳)所對應得時間戳。

# 2019-09-24 00:00:00
# 
# local: 1569254400953(表示東8區的時間:2019-09-24 00:00:00<(即返回的是`北京時間1970年01月1日0點0分0秒`以來的`毫秒數`,對應`UTC`時間`1970年01月1日8點0分0秒`以來的`毫秒數`,其數值大小等於`0時區`的`“2019-09-23 16:00:00”`所對應的時間戳)>,對應的UTC時間戳)
# GMT: 1569283200985(表示東0時區的時間:2019-09-24 00:00:00,對應的GMT時間戳)
# UTC: 1569283200734(表示東0時區的時間:2019-09-24 00:00:00,對應的UTC時間戳)

# ES底層實際存儲的
# es utc: 1569283200000

插入數據,其中date類型插入無時區信息的時間字符串,如下:

POST ddate/default
{
  "name":"lzz",
  "ddate": "2019-09-24 00:00:00"
}

查詢驗證:
使用“2019-09-24 00:00:00”對應的UTC時間戳(精確到毫秒:1569283200734)查詢,查詢結果如下:

GET /ddate/default/_search
{
  "query": {
    "term": {
      "ddate": 1569283200734
    }
  }
}

查詢結果:
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 0,
    "max_score": null,
    "hits": []
  }
}

使用“2019-09-24 00:00:00”對應的UTC時間戳(精確到秒:1569283200000)查詢,查詢結果如下:

GET /ddate/default/_search
{
  "query": {
    "term": {
      "ddate": 1569283200000
    }
  }
}

查詢結果:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "ddate",
        "_type": "default",
        "_id": "AXLgmQOJp7tSUSJKLQSm",
        "_score": 1,
        "_source": {
          "name": "lzz",
          "ddate": "2019-09-24 00:00:00"
        }
      }
    ]
  }
}

小結
對於無時區的時間字符串“2019-09-24 00:00:00”,使用精確到毫秒:1569283200734UTC時間戳進行查詢時,查詢無法命中。使用精確到秒:1569283200000UTC時間戳,可以查詢可以命中。

2.2 有時區信息驗證

數據情況:
date類型數據信息如下,通過使用Java程序,分別獲取該時間字符串“ 2019-09-24T00:00:00+0800”東8區時間戳、GMT時間戳、以及UTC時間戳。如下:

注意:
當字符串中有時區信息時,此時,在ES內部“2019-09-24T00:00:00+0800”所對應的東8區時間戳、GMT時間戳、UTC時間戳爲一致的。都是將其當做東8區的“2019-09-24 00:00:00”(即返回的是北京時間1970年01月1日0點0分0秒以來的毫秒數,對應UTC時間1970年01月1日8點0分0秒以來的毫秒數,其數值大小等於0時區“2019-09-23 16:00:00”所對應的時間戳)

# 2019-09-24T00:00:00+0800 (表示東8區的時間:2019-09-24 00:00:00)
# 
# local: 1569254400402(表示東8區的時間:2019-09-24 00:00:00<大小等同0時區的 2019-09-23 16:00:00>,對應的東8區時間戳)
# GMT: 1569254400461(表示東8區的時間:2019-09-24 00:00:00<大小等同0時區的 2019-09-23 16:00:00>,對應的GMT時間戳)
# UTC: 1569254400092(表示東8區的時間:2019-09-24 00:00:00<大小等同0時區的 2019-09-23 16:00:00>,對應的UTC時間戳)

# ES底層實際存儲的
# es utc: 1569254400000

插入數據,其中date類型插入無時區信息的時間字符串,如下:

POST ddate/default
{
  "name":"lcc",
  "ddate": "2019-09-24T00:00:00+0800"
}

查詢驗證:
使用"2019-09-24T00:00:00+0800"對應的UTC時間戳(精確到秒:1569254400000)查詢,查詢結果如下:

GET /ddate/default/_search
{
  "query": {
    "term": {
      "ddate": 1569254400000
    }
  }
}

查詢結果:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "ddate",
        "_type": "default",
        "_id": "AXLg21Sjp7tSUSJKLQ-r",
        "_score": 1,
        "_source": {
          "name": "lcc",
          "ddate": "2019-09-24T00:00:00+0800"
        }
      }
    ]
  }
}

小結
對於有時區的時間字符串“2019-09-24T00:00:00+0800”,使用精確到毫秒:1569254400092UTC時間戳進行查詢時,查詢無法命中。使用精確到秒:1569254400000UTC時間戳,可以查詢可以命中。

3. 總結

  1. 內部存儲形式:ES內部,時間所有date類型的數據,最終都是轉換爲UTC時間戳存儲的,並且精度爲妙級
  2. 無時區信息的時間字符串:對於無時區信息的時間字符串(“2019-09-24 00:00:00”),在ES內部會將其(“2019-09-24 00:00:00”)當成是0時區“2019-09-24 00:00:00”來對待,轉換爲0時區“2019-09-24 00:00:00”對應的UTC時間戳存儲。

    需要注意的是:
    當存入無時區信息的時間字符串時,使用ES的Java api(或其他api)進行查詢時,要特別注意時區的問題。例如,在Java中將“2019-09-24 00:00:00”轉換爲Date類型,然後調用Date.getTime(),獲取的其實是東8區的“2019-09-24 00:00:00”(即返回的是北京時間1970年01月1日0點0分0秒以來的毫秒數,對應UTC時間1970年01月1日8點0分0秒以來的毫秒數,其數值大小等於0時區“2019-09-23 16:00:00”所對應的時間戳)對應的時間戳,而ES中存儲的是0時區的時間“2019-09-24 00:00:00”所對應得時間戳,就會出現查詢不到結果的問題。

  3. 有時區信息的時間字符串:對於有時區信息的時間字符串(“2019-09-24T00:00:00+0800”),在ES內部會將其(“2019-09-24T00:00:00+0800”)當成是0時區“2019-09-23 16:00:00”來對待,轉換爲0時區2019-09-23 16:00:00”對應的UTC時間戳存儲。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章