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开发规范,会不会在启动脚本中指点时区都不重要,重要的是做为一个运维须要主动去沟通,问问开发他们的程序对时区和编码是否有要求,而后主动把这些参数在启动脚本中内设好,加强本身的运维主观意识,减小线上运行程序对系统环境的依赖,来规避一些问题。

相关文章
相关标签/搜索