简述 {#简述}
最近我致力于实现 PostgreSQL 到 Elasticsearch(ES)的实时同步功能。
在源端,我利用 PostgreSQL(PG)的 Write-Ahead Logging(WAL)日志来实现实时同步,将 WAL 转换为 ES 的相关写入操作。
源端 PG 数据库和 ES 均采用 UTC 时区,因此 Timestamp 类型的同步应该无需进行任何处理。
然而,在将数据写入 ES 后,我们却发现 Timestamp 类型的数值少了 8 个小时。
原因分析 {#原因分析}
首先,让我们梳理一下整个同步链路:
PG 的数据经过 SYNC 程序处理,然后同步到 ES。
经过初步分析,我们得出两个可能的原因:
-
ES 中的时间存储正确,而 Kibana 使用的是浏览器的时区(Asia/Shanghai)。
-
SYNC 程序在处理时间时出现了问题。
在 Kibana 设置中查看后,发现其设置为 UTC,即不会默认进行任何时区转换,因此我们推断问题出现在 SYNC 程序的时间处理中。
代码分析 {#代码分析}
在处理时间类型的代码中,存在以下逻辑,首先会使用parseDate
方法将时间类型的字符串转换为DateTime
:
public static DateTime parseDate(String datetimeStr) {
// ... 省略部分代码 ...
try {
return new DateTime(datetimeStr);
} catch (IllegalFieldValueException e) {
String errMsg = "Parse date fail, Root cause is " + ExceptionUtils.getRootCauseMessage(e);
log.error(errMsg);
throw e;
}
}
通过调试,我们发现在parseDate
后,时间减少了 8 小时。
源码分析 {#源码分析}
TimeZone.getDefault()
是 JDK 自带的方法:
获取 Java 虚拟机的默认时区。
如果缓存的默认时区可用,则返回其克隆。
否则,该方法采取以下步骤来确定默认时区。
使用默认时区的风险 {#使用默认时区的风险}
当 JVM 中的 user.timezone
变量未设置值时,根据上述源码分析,将读取系统的默认时区。
风险就出在这里,如果系统安装时时区未正确设置,将导致程序获取的默认时区与预期不符,从而引发异常。
在 Java 程序中设置时区 {#在-java-程序中设置时区}
-
在 Java 程序启动时,在 JVM 参数中添加
-Duser.timezone=UTC
。 -
在程序首次启动时,使用
TimeZone.setDefault()
来设置时区。