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”
,使用精確到毫秒:1569283200734
的UTC
時間戳進行查詢時,查詢無法命中。使用精確到秒:1569283200000
的UTC
時間戳,可以查詢可以命中。
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”
,使用精確到毫秒:1569254400092
的UTC
時間戳進行查詢時,查詢無法命中。使用精確到秒:1569254400000
的UTC
時間戳,可以查詢可以命中。
3. 總結
- 內部存儲形式:ES內部,時間所有
date
類型的數據,最終都是轉換爲UTC時間戳
存儲的,並且精度爲妙級
。 - 無時區信息的時間字符串:對於無時區信息的時間字符串(
“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”
所對應得時間戳,就會出現查詢不到結果的問題。 - 有時區信息的時間字符串:對於有時區信息的時間字符串(
“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
時間戳存儲。