天天看點

Elasticsearch時區問題

發現問題

  1. 建立索引,配置date類型字段
  2. 批量插入資料,格式分别為帶時區資訊,不帶時區資訊,時間戳
  3. 對資料進行聚合
  4. 對資料進行查詢,分别使用帶時區資訊,不帶時區資訊,時間戳三種格式

建立索引,配置字段類型

PUT testdate
{
  "mappings": {
    "testdate":{
      "properties": {
        "tag":{
          "type": "keyword"
        },
        "datetime":{
          "type": "date"
        }
      }
    }
  }
}
      

批量插入資料

POST /_bulk
{"create":{"_index":"testdate","_type":"testdate","_id":1}}
{"tag":"帶時區資訊","datetime":"2019-07-15T08:00:00+08:00"}
{"create":{"_index":"testdate","_type":"testdate","_id":2}}
{"tag":"不帶時區資訊","datetime":"2019-07-15T08:00:00"}
{"create":{"_index":"testdate","_type":"testdate","_id":3}}
{"tag":"時間戳","datetime":"1563148800000"}      

對資料進行聚合操作

不帶時區資訊聚合

POST /testdate/testdate/_search
{
  "size": 0, 
  "aggs": {
    "aggs_datetime": {
      "date_histogram": {
        "field": "datetime",
        "interval": "hour"
      },
      "aggs": {
        "aggs_tag": {
          "terms": {
            "field": "tag",
            "size": 10
          }
        }
      }
    }
  }
}      

結果

不帶時區資訊時間顯示正常, 時間戳和帶時區資訊時間提前8小時

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "aggs_datetime": {
      "buckets": [
        {
          "key_as_string": "2019-07-15T00:00:00.000Z",
          "key": 1563148800000,
          "doc_count": 2,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "帶時區資訊",
                "doc_count": 1
              },
              {
                "key": "時間戳",
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key_as_string": "2019-07-15T01:00:00.000Z",
          "key": 1563152400000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T02:00:00.000Z",
          "key": 1563156000000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T03:00:00.000Z",
          "key": 1563159600000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T04:00:00.000Z",
          "key": 1563163200000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T05:00:00.000Z",
          "key": 1563166800000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T06:00:00.000Z",
          "key": 1563170400000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T07:00:00.000Z",
          "key": 1563174000000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T08:00:00.000Z",
          "key": 1563177600000,
          "doc_count": 1,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "不帶時區資訊",
                "doc_count": 1
              }
            ]
          }
        }
      ]
    }
  }
}      

東八區

POST /testdate/testdate/_search
{
  "size": 0, 
  "aggs": {
    "aggs_datetime": {
      "date_histogram": {
        "field": "datetime",
        "interval": "hour",
        "time_zone": "+08:00"
      },
      "aggs": {
        "aggs_tag": {
          "terms": {
            "field": "tag",
            "size": 10
          }
        }
      }
    }
  }
}      

結果

時間戳和帶時區資訊時間顯示正常, 不帶時區資訊時間推後8小時

{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "aggs_datetime": {
      "buckets": [
        {
          "key_as_string": "2019-07-15T08:00:00.000+08:00",
          "key": 1563148800000,
          "doc_count": 2,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "帶時區資訊",
                "doc_count": 1
              },
              {
                "key": "時間戳",
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key_as_string": "2019-07-15T09:00:00.000+08:00",
          "key": 1563152400000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T10:00:00.000+08:00",
          "key": 1563156000000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T11:00:00.000+08:00",
          "key": 1563159600000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T12:00:00.000+08:00",
          "key": 1563163200000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T13:00:00.000+08:00",
          "key": 1563166800000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T14:00:00.000+08:00",
          "key": 1563170400000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T15:00:00.000+08:00",
          "key": 1563174000000,
          "doc_count": 0,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": []
          }
        },
        {
          "key_as_string": "2019-07-15T16:00:00.000+08:00",
          "key": 1563177600000,
          "doc_count": 1,
          "aggs_tag": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "不帶時區資訊",
                "doc_count": 1
              }
            ]
          }
        }
      ]
    }
  }
}      

對資料進行結構化查詢

時間戳

POST /testdate/testdate/_search
{
  "query": {
    "range": {
      "datetime": {
        "gte": 1563148800000
      }
    }
  }
}
      

三條資料全部傳回

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 1,
    "hits": [
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "2",
        "_score": 1,
        "_source": {
          "tag": "不帶時區資訊",
          "datetime": "2019-07-15T08:00:00"
        }
      },
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "1",
        "_score": 1,
        "_source": {
          "tag": "帶時區資訊",
          "datetime": "2019-07-15T08:00:00+08:00"
        }
      },
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "3",
        "_score": 1,
        "_source": {
          "tag": "時間戳",
          "datetime": "1563148800000"
        }
      }
    ]
  }
}      

不帶時區

POST /testdate/testdate/_search
{
  "query": {
    "range": {
      "datetime": {
        "gte": "2019-07-15T08:00:00"
      }
    }
  }
}      

隻傳回了不帶時區資訊的一條資料

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "2",
        "_score": 1,
        "_source": {
          "tag": "不帶時區資訊",
          "datetime": "2019-07-15T08:00:00"
        }
      }
    ]
  }
}      

帶時區

POST /testdate/testdate/_search
{
  "query": {
    "range": {
      "datetime": {
        "gte": "2019-07-15T08:00:00+08:00"
      }
    }
  }
}      

三條資料全部傳回

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 1,
    "hits": [
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "2",
        "_score": 1,
        "_source": {
          "tag": "不帶時區資訊",
          "datetime": "2019-07-15T08:00:00"
        }
      },
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "1",
        "_score": 1,
        "_source": {
          "tag": "帶時區資訊",
          "datetime": "2019-07-15T08:00:00+08:00"
        }
      },
      {
        "_index": "testdate",
        "_type": "testdate",
        "_id": "3",
        "_score": 1,
        "_source": {
          "tag": "時間戳",
          "datetime": "1563148800000"
        }
      }
    ]
  }
}      

原因分析

/**
     * @desc : 時間戳
     * @author : cheng
     * @date : 2019-12-10 23:23
     */
    @Test
    public void testA() throws Exception{
        String dateStr = "2019-07-15 08:00:00";
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 解析字元串,時區:東八區
        Date date = dateFormat.parse(dateStr);
        System.out.println(date.getTime());

        // 格式化日期,時區:0時區
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        System.out.println(dateFormat.format(date));
    }

    // 輸出
    1563148800000
    2019-07-15 00:00:00      
/**
     * @desc : 帶時區
     * @author : cheng
     * @date : 2019-12-10 23:24
     */
    @Test
    public void testB() throws Exception{
        String dateStr = "2019-07-15 08:00:00+0800";

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");

        // 解析字元串,時區:東八區
        Date date = dateFormat.parse(dateStr);

        // 格式化日期,時區:0時區
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        System.out.println(dateFormat.format(date));
    }

    // 輸出
    2019-07-15 00:00:00+0000      
/**
     * @desc : 不帶時區資訊
     * @author : cheng
     * @date : 2019-12-10 23:23
     */
    @Test
    public void testC() throws Exception{
        String dateStr = "2019-07-15 08:00:00";

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 解析字元串,時區:0時區
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        Date date = dateFormat.parse(dateStr);

        // 格式化日期,時區:0時區
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        System.out.println(dateFormat.format(date));
    }

    // 輸出
    2019-07-15 08:00:00      

ES日期類型使用UTC

時間戳1563148800000

時間戳本身就是UTC毫秒數, 和時區沒有關系, 實際存儲的就是這個值

String dateStr = "2019-07-15 08:00:00";
// 時間戳和時區無關, UTC毫秒數
System.out.println(DateUtils.parseDate(dateStr, Locale.ENGLISH, "yyyy-MM-dd HH:mm:ss").getTime());
System.out.println(DateUtils.parseDate(dateStr, Locale.US, "yyyy-MM-dd HH:mm:ss").getTime());
System.out.println(DateUtils.parseDate(dateStr, Locale.FRENCH, "yyyy-MM-dd HH:mm:ss").getTime());

輸出
1563148800000
1563148800000
1563148800000      

不帶時區資訊格式2019-07-15T08:00:00

等價于2019-07-15T08:00:00+00:00, 預設就是UTC 0時區時間, 實際存儲的就是這個值

// 本地時間為2019-07-15 08:00:00
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 不設定時區, 預設為機器所在時區
System.out.println(sdf.format(date));

// 東八區
sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
System.out.println(sdf.format(date));

// 0時區 = 東八區 - 8個小時
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println(sdf.format(date));

輸出
2019-07-15 08:00:00
2019-07-15 08:00:00
2019-07-15 00:00:00      

總結

  • 存儲:es使用UTC時間存儲date類型
  • 查詢:查詢的時間會轉換成UTC時間,然後進行查詢操作

使用建議

  • 強制:存入es的日期類型資料,必須全部帶上時區資訊或者使用時間戳(可視化效果差,不建議)
  • 強制:使用日期聚合時,必須帶上時區資訊
  • 強制:查詢日期類型資料是,必須帶上時區資訊,或者時間戳(可視化效果差,不建議)

繼續閱讀