JVM加載TimeZone讀取文件優先級實戰分析

問題現象

前幾天線上新上線一個Kafka Java Consumer程序,出現一個異常的問題,那就經過查看日誌,數據寫入到了Elasticsearch索引裏面,可是前端查詢不到數據。html

最終經過和開發一塊兒定位,是由於咱們業務上的緣由,默認數據時間戳問題,默認須要使用UTC TimeZone;但當運維用date命令看的時候,默認是UTC時區啊,爲啥仍是寫錯了呢?前端

由於咱們線上維護的是/etc/localtime文件來保證時區問題,並且也是UTC時區,可是仍是寫入數據時間對不上,以後上線操做的同事說把/etc/timezone 文件刪除,而後重啓消費者程序好了。java

好了,這是爲啥,雖然知道刪除/etc/timezone文件後,業務數據寫入正常了,可是這是爲何呢,下面咱們就來一探究竟。bash

尋找真相

一般我遇到這種以前沒有遇到的問題,都會藉助Google搜索一把,搜索完成後,獲得JVM加載時區文件順序以下:運維

  1. 若是系統環境變量有TZ設置,則優先取變量TZ的值;
  2. 若是在文件/etc/sysconfig/clock 文件中能夠找到"ZONE"的值,注意ZONE的值要帶雙引號,如ZONE="Asia/Shanghai"
  3. 若是沒有找到找到ZONE的值,就會讀取/etc/localtime的內容和/usr/hsare/zoneinfo下的時區文件進行匹配,若是找到匹配的,就返回對應的路徑

中文參考連接:https://blog.csdn.net/zj380475045/article/details/72765936 http://www.360doc.com/content/12/1011/17/110467_240881174.shtml 英文參考連接:https://bugs.java.com/view_bug.do?bug_id=6456628測試

那按照搜索到的結果,跟個人狀況不對啊,咱們線上刪除/etc/timezone文件就行了,因此確定跟文件/etc/timezone有關啊,因此我感受確定跟操做系統和JAVA版本有關,SO我以爲實踐一把,必定要把謎底揭開。編碼

揭開謎底

環境 操做系統 JAVA版本
aliyun Centos6.5 1.8.0_25

如上表格是我線上環境狀況,實踐過程以下。spa

Java測試代碼以下:操作系統

[root@Labhost2 src]# cat TimeTest.java 
import java.util.Date;
import java.util.TimeZone;

public class TimeTest {

    public static void main(String args[]) {
	long time = System.currentTimeMillis();
	String millis = Long.toString(time);
	Date date = new Date(time);
	System.out.println("Current time in milliseconds = " + millis + " => " + date.toString());
	System.out.println("Current time zone: " + TimeZone.getDefault().getID());
    }
}

[root@Labhost2 src]# javac TimeTest.java # 生成測試類
[root@Labhost2 src]# ls
TimeTest.class  TimeTest.java
複製代碼

從搜索咱們知道JVM讀取時區跟系統變量TZ和文件/etc/sysconfig/clock/etc/localtime 有關,我這裏在加上咱們刪除的文件/etc/timezone 一塊兒來實踐,驗證過程以下:.net

[root@Labhost2 src]# export TZ="Pacific/Honolulu"
[root@Labhost2 src]# cat /etc/sysconfig/clock
ZONE="America/Los_Angeles"
UTC=false 
ARC=false
[root@Labhost2 src]# ll /etc/localtime 
lrwxrwxrwx 1 root root 23 4月  18 09:23 /etc/localtime -> /usr/share/zoneinfo/UTC
[root@Labhost2 src]# cat /etc/timezone
Asia/Shanghai
複製代碼

從上信息咱們總結一下狀態:

測試項 時區值
TZ Pacific/Honolulu
/etc/sysconfig/clock America/Los_Angeles
/etc/localtime UTC
/etc/timezone Asia/Shanghai

上面狀態設置好了以後,測試輸出驗證以下:

[root@Labhost2 src]# java TimeTest
Current time in milliseconds = 1524275592096 => Fri Apr 20 15:53:12 HST 2018
Current time zone: Pacific/Honolulu
[root@Labhost2 src]# unset TZ
[root@Labhost2 src]# java TimeTest
Current time in milliseconds = 1524275606924 => Sat Apr 21 09:53:26 CST 2018
Current time zone: Asia/Shanghai
[root@Labhost2 src]# rm -rf /etc/timezone 
[root@Labhost2 src]# java TimeTest
Current time in milliseconds = 1524275627626 => Sat Apr 21 01:53:47 UTC 2018
Current time zone: UTC
[root@Labhost2 src]# rm -rf /etc/localtime 
[root@Labhost2 src]# java TimeTest
Current time in milliseconds = 1524275640872 => Sat Apr 21 01:54:00 GMT 2018
Current time zone: GMT
複製代碼

從上面測試結果可知,在我這種環境下,JVM讀取時區文件順序依次爲:$TZ > /etc/timezone > /etc/localtime > 默認GMT , 因此跟搜索到的狀況不同,跟文件/etc/sysconfig/clock 無關。

好了,到這裏獲得了正確的答案了,終於明白了,能夠解釋咱們線上的狀況了,咱們線上刪除文件/etc/timezone 後,就去讀取文件 /etc/localtime了,咱們線上文件/etc/localtime默認維護設置的就是UTC時區,正好符合咱們業務需求,這就解釋了。

默認GMT說明:java.util.TimeZone類中getDefault方法的源代碼顯示,它最終是會調用sun.util.calendar.ZoneInfo類的getTimeZone 方法。這個方法爲須要的時間區域返回一個做爲ID的String參數。這個默認的時間區域ID是從 user.timezone (system)屬性那裏獲得。若是user.timezone沒有定義,它就會嘗試從user.country和java.home (System)屬性來獲得ID。 若是它沒有成功找到一個時間區域ID,它就會使用一個"fallback" 的GMT值。換句話說, 若是它沒有計算出你的時間區域ID,它將使用GMT做爲你默認的時間區域。

總結

要避免這種問題最好的方式以下:

[推薦]Java程序在發佈後的啓動腳本中,可經過JVM參數指定應用的時區、編碼, 好比 java -Duser.timezone=Asia/Shanghai -Dfile.encoding=utf8 DateTest

無論大家公司的研發人員有沒有相應的Java開發規範,會不會在啓動腳本中指點時區都不重要,重要的是做爲一個運維須要主動去溝通,問問開發他們的程序對時區和編碼是否有要求,而後主動把這些參數在啓動腳本中內設好,加強本身的運維主觀意識,減小線上運行程序對系統環境的依賴,來規避一些問題。

相關文章
相關標籤/搜索