JVM加载TimeZone读取文件优先级实战分析
问题现象
前几天线上新上线一个Kafka Java Consumer程序,出现一个异常的问题,那就通过查看日志,数据写入到了Elasticsearch索引里面,但是前端查询不到数据。
最终通过和开发一起定位,是因为我们业务上的原因,默认数据时间戳问题,默认需要使用UTC TimeZone
;但当运维用date
命令看的时候,默认是UTC时区啊,为啥还是写错了呢?
因为我们线上维护的是/etc/localtime
文件来保证时区问题,而且也是UTC
时区,但是还是写入数据时间对不上,之后上线操作的同事说把/etc/timezone
文件删除,然后重启消费者程序好了。
好了,这是为啥,虽然知道删除/etc/timezone
文件后,业务数据写入正常了,但是这是为什么呢,下面我们就来一探究竟。
寻找真相
通常我遇到这种之前没有遇到的问题,都会借助Google搜索一把,搜索完成后,得到JVM加载时区文件顺序如下:
- 如果系统环境变量有TZ设置,则优先取变量TZ的值;
- 如果在文件
/etc/sysconfig/clock
文件中可以找到"ZONE"的值,注意ZONE的值要带双引号,如ZONE="Asia/Shanghai" - 如果没有找到找到ZONE的值,就会读取/etc/localtime的内容和/usr/hsare/zoneinfo下的时区文件进行匹配,如果找到匹配的,就返回对应的路径
那按照搜索到的结果,跟我的情况不对啊,我们线上删除/etc/timezone
文件就好了,所以肯定跟文件/etc/timezone
有关啊,所以我感觉肯定跟操作系统和JAVA版本有关,SO我觉得实践一把,一定要把谜底揭开。
揭开谜底
环境 |
操作系统 |
JAVA版本 |
---|---|---|
aliyun |
Centos6.5 |
1.8.0_25 |
如上表格是我线上环境情况,实践过程如下。
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
一起来实践,验证过程如下:
[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开发规范,会不会在启动脚本中指点时区都不重要,重要的是作为一个运维需要主动去沟通,问问开发他们的程序对时区和编码是否有要求,然后主动把这些参数在启动脚本中内设好,增强自己的运维主观意识,减少线上运行程序对系统环境的依赖,来规避一些问题。
- 函数式编程在Redux/React中的应用
- 关于刘海打理这种事儿,美团点评的iOS工程师早就有经验了,不信你看!
- 这个Spring高危漏洞,你修补了吗?
- 详析JSONP跨域
- Android Binder漏洞挖掘技术与案例分享
- Stanford机器学习笔记-5.神经网络Neural Networks (part two)
- 监控平台前端SDK开发实践
- 一步步实现静态页面布局
- Stanford机器学习笔记-3.Bayesian statistics and Regularization
- 在R中使用支持向量机(SVM)进行数据挖掘
- 【你问我答】你与Java大牛的距离,只差这24个问题
- Android漏洞扫描工具Code Arbiter
- Huawei HG532 系列路由器远程命令执行漏洞分析
- postMessage与postMessage跨域
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- matlab中类的重载简析
- Js输入验证
- 并发工具的使用
- js创建对象
- 前端笔试题(附答案)
- .Net Core微服务入门全纪录(一)——项目搭建
- JS中的call()方法和apply()方法和slice()用法总结
- 完整的url以及同源跨域处理
- .Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)
- .Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)
- MTO变量缩放与统一决策空间
- css相关的几个点
- .Net Core微服务入门全纪录(四)——Ocelot-API网关(上)
- Apache下如何禁止指定目录运行PHP脚本
- .Net Core微服务入门全纪录(五)——Ocelot-API网关(下)