前几天线上新上线一个Kafka Java Consumer程序,出现一个异常的问题,那就经过查看日志,数据写入到了Elasticsearch索引里面,可是前端查询不到数据。html
最终经过和开发一块儿定位,是由于咱们业务上的缘由,默认数据时间戳问题,默认须要使用UTC TimeZone
;但当运维用date
命令看的时候,默认是UTC时区啊,为啥仍是写错了呢?前端
由于咱们线上维护的是/etc/localtime
文件来保证时区问题,并且也是UTC
时区,可是仍是写入数据时间对不上,以后上线操做的同事说把/etc/timezone
文件删除,而后重启消费者程序好了。java
好了,这是为啥,虽然知道删除/etc/timezone
文件后,业务数据写入正常了,可是这是为何呢,下面咱们就来一探究竟。bash
一般我遇到这种以前没有遇到的问题,都会借助Google搜索一把,搜索完成后,获得JVM加载时区文件顺序以下:运维
/etc/sysconfig/clock
文件中能够找到"ZONE"的值,注意ZONE的值要带双引号,如ZONE="Asia/Shanghai"中文参考连接: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开发规范,会不会在启动脚本中指点时区都不重要,重要的是做为一个运维须要主动去沟通,问问开发他们的程序对时区和编码是否有要求,而后主动把这些参数在启动脚本中内设好,加强本身的运维主观意识,减小线上运行程序对系统环境的依赖,来规避一些问题。