ElasticSearch里面关于日期的存储方式,解决差8个小时

本文讲解ElasticSearch中时间字段的处理方式,包括时区差异、时间格式化、日期聚合查询等关键操作,并提供Java代码示例。
在ElasticSearch里面最常用的就是时间字段了,经常会在群里看到一些小伙伴提出有关时间的问题,为什么es查询的时间跟我实际看到的时间差8个小时呢。如果我们了解了ElasticSearch底层的时间存储方式就会比较容易的理解这个问题。 

下面散仙先普及下时区的知识,想必大家也不陌生学过地理的同学都知道全球有24个时区每个时区的跨度是经度15度, 


相较于两地时间表,可以显示世界各时区时间和地名的世界时区表(World Time),就显得精密与复杂多了,通常世界时区表的表盘上会标示着全球24个时区的城市名称,但究竟这24个时区是如何产生的?过去世界各地原本各自订定当地时间,但随着交通和电讯的发达,各地交流日益频繁,不同的地方时间,造成许多困扰,于是在西元1884年的国际会议上制定了全球性的标准时,明定以英国伦敦格林威治这个地方为零度经线的起点(亦称为本初子午线),并以地球由西向东每24小时自转一周360°,订定每隔经度15°,时差1小时。而每15°的经线则称为该时区的中央经线,将全球划分为24个时区,其中包含23个整时区及180°经线左右两侧的2个半时区 
就全球的时间来看,东经的时间比西经要早,也就是如果格林威治时间是中午12时,则中央经线15°E的时区为下午1时,中央经线30°E时区的时间为下午2时;反之,中央经线15°W的时区时间为上午11时,中央经线30°W时区的时间为上午10时。以台湾为例,台湾位于东经121°,换算后与格林威治就有8小时的时差。如果两人同时从格林威治的0°各往东、西方前进,当他们在经线180°时,就会相差24小时,所以经线180°被定为国际换日线,由西向东通过此线时日期要减去一日,反之,若由东向西则要增加一日。 


几个时间名词: 
  1. GMT:格林威治标准时间 
  2. UTC:世界协调时间 
  3. DST:夏日节约时间 
  4. CST:中国标准时间 
其中GMT时间可以近似认为和UTC时间是相等的,但从精度上来说UTC时间更精确。其误差值必须保持在0.9秒以内 

CST= GMT + 8 =UTC + 8 

从上面可以看出来中国的时间是等于UTC时间+8小时,es默认存储时间的格式是UTC时间,如果我们查询es然后获取时间日期默认的数据,会发现跟当前的时间差8个小时,这其实是正常的,因为es默认存储是用的UTC时间,所以我们需要做的就是读取long型时间戳,然后重新格式化成下面的时间戳,即可获得正确的时间 :

yyyy-MM-dd HH:mm:ss 
像差8个时区的事情,最容易见到的就是,我们使用logstash收集的日志,发送到es里面,然后通过head查询就能发现不一致,但是如果我们用kibana查询,就不会发现时区问题,为什么? 因为kibana已经处理时区问题了,所以在kibana的页面显示的时间是正确的。 

此外在使用Java Client聚合查询日期的时候,需要注意时区问题,因为默认的es是按照UTC标准时区算的,所以不设置的聚合统计结果是不正确的。 

在es的DateHistogramBuilder里面有几个比较重要的参数:

field:指定按那个字段聚合  
interval:聚合的时间单位(年,季度,月,周,天,小时,分钟,秒)  
format:日期格式  
time_zone:时区指定  
offset:时间偏移量  

注意,默认不设置时区参数,es是安装UTC的时间进行查询的,所以分组的结果可能与预期不一样,所以我们要指定时区为Asia/Shanghai代表北京的时区,这样才能获取正确的聚合结果 

curl方式如下: 

GET my_index/_search?size=0  
{  
  "aggs": {  
    "by_day": {  
      "date_histogram": {  
        "field":     "ctime",  
        "interval":  "day",  
        "time_zone": "Asia/Shanghai"  
      }  
    }  
  }  
}  

Java代码如下: 

SearchRequestBuilder search = client.prepareSearch("search2017-02*").setTypes("log");  
DateHistogramBuilder dateagg = AggregationBuilders.dateHistogram("dateagg");  
dateagg.field("ctime");//聚合时间字段  
dateagg.interval(DateHistogramInterval.DAY);//按天聚合第一天的0点到第二天的0点  
dateagg.timeZone("Asia/Shanghai");//指定时区  
dateagg.offset("+8h");//默认都是从0点开始计算一天的,通过这个offset,我们可以把第一天的6点到第二天的6点当做一天来聚合  
search.addAggregation(dateagg);  

Histogram hs= search.get().getAggregations().get("dateagg");  
List<Histogram.Bucket> buckets =   (List<Histogram.Bucket>) hs.getBuckets();//获取结果  
for(Histogram.Bucket bk:buckets){  
//下面的转化,也是因为默认是UTC的时间,所以我们要获取时间戳,自己转化  
    System.out.println(new DateTime(Long.parseLong(bk.getKeyAsString()+"")).toString("yyyy-MM-dd HH:mm:ss") +"  "+bk.getDocCount());  
}  
client.close();  
上面的这个例子,基本涵盖了日期聚合核心功能,其中时区和偏移量时两个非常有用的而且需要特别注意的参数,不设置时区直接统计结果肯定是不准确的,offset偏移量这个参数,在某些时刻也是有用的,它可以自己定义一天的开始,比如设置从第一天的3点到第二天的3点为一天,默认都是从0点开始0点结束算做一天的,最后一点需要注意的是在输出打印时间的时候也要考虑转化因为默认也是UTC的时间,所以我们直接取出时间戳,自己格式化时间即可。 

官网文档: 

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html  
### 解决 Elasticsearch 时间与时区异的方法 Elasticsearch 和 Kibana 的时间显示问题通常源于不同组件之间的时区设置不一致。以下是针对该问题的具体解决方案: #### 方法一:调整 Logstash 配置 如果使用 Logstash 将数据写入 Elasticsearch,则可以在 Logstash 的配置文件中指定 `timezone` 参数,确保时间字段被正确解析并转换为 UTC 时区。 ```ruby filter { date { match => ["timestamp", "yyyy-MM-dd HH:mm:ss"] target => "@timestamp" timezone => "+08:00" # 设置为中国标准时间 (CST),即东八区 } } ``` 通过上述方式可以确保 Logstash 处理的数据时间字段统一为 UTC 或者目标时区[^4]。 --- #### 方法二:修改 Kibana 的日期格式化设置 由于 Kibana 默认会根据用户的浏览器时区来展示时间,因此可以通过更改 Kibana 的高级设置 (`Advanced Settings`) 来强制其使用特定的时区(如 UTC)。具体操作如下: 1. 登录 Kibana 并导航至 **Management -> Advanced Settings**。 2. 找到参数 `dateFormat:tz` 并将其值更改为 `"UTC"` 或其他所需的时区字符串(例如 `"Asia/Shanghai"` 表示中国标准时间)。 3. 保存设置后刷新页面即可生效。 此方法适用于希望在前端层面解决问题而不改变底层存储逻辑的需求场景[^3]。 --- #### 方法三:直接在 Elasticsearch 中定义索引模板 为了使新创建的文档自动携带正确的时区信息,在建立索引之前可预先定义好映射规则,并明确指出相关字段应采用何种格式及时区表示法。例如: ```json PUT _template/my_template { "index_patterns": ["my_index_*"], "mappings": { "properties": { "custom_time_field": { "type": "date", "format": "strict_date_optional_time||epoch_millis", "ignore_malformed": false, "doc_values": true } } }, "settings": { "number_of_shards": 1, "number_of_replicas": 1 } } ``` 注意此处并未显式涉及任何具体的时区偏移量;这是因为推荐的做法是在应用程序层面上完成必要的转换后再提交给 ES 存储[^1]。 --- #### 方法四:利用 Ruby 插件修正时间戳 对于某些特殊情况下无法单纯依赖配置项达成目的的情形下,还可以考虑借助自定义脚本来动态调整每条记录中的时间属性值大小。比如下面这段代码片段展示了如何增加固定秒数从而补偿跨区域带来的偏效果: ```ruby filter { ruby { code => " event.set('adjusted_time', event.get('@timestamp').time.localtime + Rational(8,24)) " } } ``` 不过需要注意的是这种方法可能会引入额外计算开销以及潜在一致性风险等问题,因而仅作为备选方案存在[^4]。 --- 以上四种途径均可有效应对由时区异引发的各种异常状况,请依据实际情况选取最合适的策略实施优化改进工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值