目录
2.2 使用date histogram做聚合查询涉及时区问题
1.Date数据类型
1.1 Date类型在elasticsearch中的存储
在Elasticsearch内部,date
默认被转为UTC
,并被存储为一个长整型数字(即long
类型),代表从1970年1月1号0点到现在的毫秒数。
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
类型在 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 以来经过的秒数。
当时间字符串中没有时区信息时,此时,在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”所对应的时间戳)所对应得时间戳。
1.3 Date类型在elasticsearch中的查询
在es内部,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 字段转换回对应的展示格式
2.Date类型及时区上可能出现的问题及解决方案
由于es内部date类型默认时区为UTC且不可修改,在实际使用时可能会出现一些时区转换相关的问题。
2.1 查看时发现es写入后的时间值比实际时间相差8小时
如果在写入数据时不加时区信息,ElasticSearch默认按UTC时区写入,默认是0时区,但是当我们查看的时候,kibana会读取我们当地的时间,即东八区,进行转换,所以我们看到的时间会相差8小时。
解决方案
就是在写入前 +8 或者 指定时区
-
我们在向es提交日期数据的时候,直接提交带有时区信息的日期字符串,如:“2016-07-15T12:58:17.136+0800”
-
或者timezone="GMT+8"
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
@Field(type = FieldType.Date)
private Date expectTime;
2.2 使用date histogram做聚合查询涉及时区问题
在使用Java Client聚合查询涉及日期时,也要注意时区问题,因为es默认按UTC标准时区计算,不设置time_zone时区参数将导致聚合统计结果错误。
在es的DateHistogramBuilder里面有几个比较重要的参数:
-
field:指定按那个字段聚合
-
interval:聚合的时间单位(年,季度,月,周,天,小时,分钟,秒)
-
format:日期格式
-
time_zone:时区指定
-
offset:时间偏移量
由于默认不设置时区参数,es是安装UTC的时间进行查询的,所以分组的结果可能与预期不一样
解决方案
为指定时区为Asia/Shanghai或+08:00,这样才能获取正确的聚合结果
2.3 使用now函数进行时间范围过滤出现错误
尽管在2.2中提到查询时指定时区以避免产生问题,但es官方文档中提到The time_zone parameter does not affect the now value.
故当我们想使用now函数,查询近一日或近三日等的数据时,可能会出现最近几个小时的数据并不被包含在查询结果内的情况。
不过,使用now/d等不会出现时区设置不生效的问题
The time_zone parameter does not affect the date math value of . is always the current system time in UTC.
However, the time_zone parameter does convert dates calculated using and date math rounding. For example, the parameter will convert a value of .
较稳妥的解决方案
是在放弃使用now函数,Java程序中计算好需要获取的时间范围的具体时间戳再直接传入进行查询,如计算好"2021-08-30T00:00:00"到"2021-08-30T23:50:00"传入es的"gte"与"lte"参数中。
3.参考文档
Date field type | Elasticsearch Guide Date field type | Elasticsearch Guide [8.14] | Elastic
Range query | Elasticsearch Guide Range query | Elasticsearch Guide [8.14] | Elastic
Range query | Elasticsearch Guide Range query | Elasticsearch Guide [8.14] | Elastic