数据库时间相差 8 小时问题排查与解决方案

121 阅读3分钟

在开发过程中,发现服务器数据库中保存的时间比期望写入的时间晚 8 小时,于是开始排查问题原因。
项目环境:K8s 集群中运行的 Java 微服务项目,使用 MySQL 数据库。

问题排查

1. 检查数据库时区配置

执行语句

show variables like '%time_zone';

image.png
time_zone 的值 SYSTEM 表示其值跟随 system_time_zone,而在 MySQL 里 CST 既表示中国标准时间 China Standard Time UTC+8:00,也表示美国中部时间 Central Standard Time (USA) UTC-6:00,以及澳大利亚中部时间 Central Standard Time (Australia) UTC+9:30 和古巴标准时间 Cuba Standard Time UTC-4:00 这四个时区的缩写。
为了防止歧义,数据库最保险的配置是将 time_zone 设置为 +08:00,但是基于实际业务,此操作会影响整个数据库。而且这个时区是可以被 JDBC url 中的时区覆盖的,所以不对数据库配置进行修改。

另外,数据库的时区配置只对 timestamp 类型产生影响,在读取 timestamp 值时会根据时区转换;而 datetime 类型不受影响,存入的值和读出的值始终保持一致。

2. 检查数据库当前时间

执行语句

select now();

image.png
数据库时间无误。

3. 检查 Java 配置文件

前面提到数据库时区可以被 JDBC url 中参数覆盖,于是检查 Java 配置文件。
image.png

spring: 
  datasource: 
    type: com.alibaba.druid.pool.DruidDataSource
    druid: 
      url: jdbc:mysql://${db.host:127.0.0.1}:${db.port:3306}/${db.instance}?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
      driver-class-name: com.mysql.cj.jdbc.Driver

GMT%2B8GMT+8(GMT:Greenwich Mean Time 格林尼治标准时间),配置无误。

4. 检查虚拟机时间

image.png
虚拟机时间无误。
这时候想起来日志打印的时间也不正确,提前了 8 小时。
image.png
这时使用微服务提供的 API 查询数据库中的数据,后端返回的查询结果又自动减去了 8 小时,变成了正确时间。于是将问题锁定在 Docker 容器和 JVM 中。

5. 检查 Docker 容器

登入服务器,执行命令。

kubectl exec -it POD_NAME -- /bin/bash

image.png
前者为容器的时间,后者为宿主机时间。容器默认采用的是 UTC 时区即协调世界时,但是宿主机默认采用的是 CST 时区,容器的时间比宿主机的时间早 8 小时。
问题定位完成。

解决方案

通过 Dockerfile 对容器进行修改,来同步容器和宿主机的时区。
在构建 Docker 镜像的时候, Dockerfile 加上以下命令:

RUN rm -f /etc/localtime && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone

重新构建后,容器和宿主机时间一致。 image.png
另外,也可以通过将宿主机的时区文件/环境变量挂载到 Docker 容器中,或是在启动 Docker 镜像时通过Java参数 -Duser.timezone=GMT+8 指定 JVM 时区等方法解决该问题。

参考链接:【原创】K8s-Pod时区与宿主时区时区同步 - lightinglei - 博客园