统一系统时间

系统时间问题

开发一个分布式的需要支持国际化的服务时,可能会遇到时间不对(有时差)的问题,比如一个数据使用服务当前的时间添加到数据库,另一个服务查询该数据时得到的时间差了8个小时。这种问题原因可能不只一种,现在来分析一下。
系统可以大致划分为数据库(在此仅指MySQL)、后台服务、前端三大层,时间在各层内部流转时,只要不进行变换,是不会有问题的,问题就出在跨层的处理。
先来看看后台服务和前端之间的问题,比如后台服务使用北京时间(+8),如果使用不包含时区的字符串进行传递,前端如果按照世界标准时间(+0)解析,就有问题了,这个很好理解。一般来说,要么使用时间戳(ms值)进行传递,要么使用带时区的字符串传递。
在看看MySQL和后台服务之间的问题。这涉及到数据表是如何设计的,如果按照常规使用datetime或者timestamp存储,来看看MySQL是如何处理的:

MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.) By default, the current time zone for each connection is the server’s time. The time zone can be set on a per-connection basis. As long as the time zone setting remains constant, you get back the same value you store. If you store a TIMESTAMP value, and then change the time zone and retrieve the value, the retrieved value is different from the value you stored.

MySQL存储的是UTC(世界标准时间),当然,这个不同配置时区是不同的,而后台服务获取到的时间是转换成数据库连接的当前时区到的时间,数据库连接的时区又从哪里来的呢,文中也说明了,就是服务的时区。那么如果所有连接都是使用的统一时区,时间就不会出问题。
了解了以上信息,现在来解决数据库和后台服务的时间问题,方案还是比较多的,相对简单的一般有两种:

  • 所有时间全部用毫秒值存储。无论是数据库还是后台服务,全部不涉及时区问题,仅在前端解析毫秒值进行显示时,才会转换为相应时区的时间。
  • 后台服务统一时区,保证所有数据库连接时区一致。

方案1要在设计初期就确定,否则改动会比较大。常规来说还是存储的日期,也比较直观,因此方案2使用还是很多的,下面将分析方案2的实施以及遇到的一个奇怪的坑。

服务统一时区

服务统一时区的实施也有两种方法:第一就是统一服务器时区,这个属于部署范畴;第二就是在代码中设置服务时区,这个不影响服务器上的其他程序。下面将分析的是第二种方法。
在代码中设置服务时区,将不依赖于部署环境,完全可控,先看看设置的代码:

public class MainLauncher {

    static{
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    }

    public static void main(String[] args){
        // normal code
    }
}

非常简单,但是有一点要注意:static块并不是很受欢迎的代码,但是在这里,放在static块中,而且是放在main方法所在类(也就是最先加载的类)中。
刚开始,设置默认时区的代码写在@PostConstruct方法中(服务使用的spring boot框架),测试时就发现服务将数据添加到数据库中时间没有变化,这个是正确的,可是服务再查询出来时间就变了,提前了8个小时。
后来发现,设置默认时区前取到的时区将不会改变,仅影响设置后的代码执行,spring初始化context,创建连接池的连接时,还没有执行设置代码,而服务运行后,添加数据取得的当前时间是UTC时间,因此出现的问题。果然,将设置时区代码位置修正后,就没有问题了。因此设置默认时区要尽量越早越好,要在使用时区的代码执行前,放在static块中并不是必须的,但这样做可以保证尽量早设置。

奇怪的问题

后来仔细思考了一下,之前遇到默认时区的代码写在@PostConstruct方法中的问题,如果连接都是+8时区,存储到数据库时应该把UTC时间转换成北京时间,查询出来应该将北京时间再转换成UTC时间,就不应该有问题,跟踪代码排查,理清楚其中的原因:
时间的转换发生在MySQL JDBC Connector库中,在PreparedStatement类中可以找到setTimestampInternal方法,就是这里将时间转换成字符串,然后拼接到sql中,跟踪发现这里在默认配置下,并不会进行时区转换,而且转换成字符串也是直接用SimpleDateFormat格式化成”yyyy-MM-dd HH:mm:ss”,在此时区已经丢失。然而在查询的时候,可以跟踪到ResultSetImpl类的getTimestampInternal方法,获取到的bytes解析成年月日时分秒后最终使用的是canlender的set方法,而这里的canlender是使用的连接的时区,也就是说这时的canlender的时间是经过时区转换的,然后再从canlender中获取毫秒数创建Timestemp对象返回。insert的时候直接丢掉了时区,而select的时候却经过转换变成了+8时区,因此时间减少了8小时,之前认为的原因其实并不准确。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值