1. 程式人生 > >ElasticSearch裡面關於日期的儲存方式,解決差8個小時

ElasticSearch裡面關於日期的儲存方式,解決差8個小時

在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