服务中的时区设置
遇到的问题:
A服务请求B服务的接口,接口参数包含一个日期参数time,time为时间戳,在java中为long类型。至于这个time参数,A服务是读库拿到一个datetime类型的字段,然后转成时间戳再请求B接口。
B服务把这个时间戳转为Date对象再格式化为yyyy-MM-dd HH:mm:ss后,却发现这个时间和数据库中对应时间差了8个小时。
背景知识:
要解决这个问题,我们需要对时区的概念有一些了解,同时知道时间戳和格式化的字符串的区别。
时区:
因为地球是自西向东转,不同地区的人看到日出的时刻是不一样的,为了沟通方便,如大家说吃早饭的时间,那都指代的是9点左右这个时候(虽然从宇宙的角度来说,这些9点不是同一时刻),地球被分为24个时区,相邻两个时区之间差一个小时。以英国的格林威治天文台为原点,划分了东12区和西12区,我国横跨了5个时区,但这5个时区都是用北京时间,而北京处于东8区。
关于时区的更多信息,可以参考李永乐老师的科普视频,或者某位仁兄的以下两篇文章:
时间戳:
-
时间戳是一个数值,在java中可以通过Date对象的getTime方法来获得,类型为long, 它表示的是自格林威治时间( GMT)1970年1月1日0点到现在所经过的毫秒数。
-
对一个特定时刻来说,全球的任何地方的时间戳都是一样的。
时间字符串:
-
不带时区信息的字符串,比如2021-07-25 10:00:00,这个时间其实不是个绝对时间,它不能指定一个特定时刻。想象自己站在高于地球的宇宙上空,在一个时刻看向地球,这时候北京的某位同学看到自己的表是2021-07-25 10:00:00,但其实日本东京的同学看到自己的表已经是2021-07-25 11:00:00了。
-
带有时区信息的字符串,如Fri Jul 23 00:26:54 GMT+08:00 2021, 其中的GMT+8:00就表明了这个东八区的2021-07-23 00:26:54,这个时间字符串就可以定位到一个确定的时刻,虽然这个时刻在其他时区的表示方式可能就不是2021-07-23 00:26:54了。
服务链路中的时间处理:
在分析中,关于数据库中的datetime和java中的Date,我们可以简单化理解为:
- 数据库datetime字段,它存储的是一个格式为yyyy-MM-dd HH:mm:ss的字符串。
- Date对象内部是用fastTime字段存了一个long类型的时间戳
所以,归根结底,我们需要分析的是在不同的节点,时间戳和时间字符串是如何转换的。
当出现时间差值的问题时,比如我们开头提到的例子时,我们该从何排查起呢?
首先我们需要了解,在我们的服务链路中,都有哪些地方可以设置时区:
- 数据库可设置时区
- 服务本身可设置时区,如不设置,取服务器时区(这个时间会影响服务中时间戳如何转为时间字符串)
- 服务与数据库连接可设置时区,如不设置,取数据库时区(这个时间会影响数据库中的datetime时间字符串如何转为时间戳)
下图是服务链路时间设置的梳理,这是一个非常典型的场景:
数据库时区设置为东8区,C服务负责写入数据,C本身时区设置为东8区,并且C的数据库连接也为东8区,C写入一个时间到数据库datetime字段,如2021-07-25 10:00:00,这个10点表示的是东8区的10点(可以理解为和时区相关的相对时间),它对应的时间戳(可以理解为绝对时间)为1627178400000。
A从库里取数据时,和java服务不同,它从数据库中拿出的datetime字段是一个字符串2021-07-25 10:00:00(没有mybatis这样的ORM框架去转化),然后把这个字符串再转为时间戳,但因为它的时区为GMT, 所以它把2021-07-25 10:00:00当做GMT时间去转化的,最终的时间戳就是1627178400000+8个小时的毫秒数。
然后A再用这个错误的时间戳去请求B服务,就出现了8个小时的差值。
补充实验:
-
数据库连接设置serverTimezone和服务本身JVM参数-Duser.timezone对服务从数据库读的时间的影响
实验设计,分别改变这两个值,观察从数据库中datetime字段解析出的Date对象,结果如下:
从上图中可以看出,在数据库连接serverTimezone时区设置不变的情况下,解析出的时间戳都是一样的,所以serverTimezone反映的是从datetime怎样转化为毫秒数的规则。
在同一个时间戳,-Duser.timezone设置不同的情况下,Date格式后的时间字符串会不同,表中的字符串带了时区信息,但如果我们用yyyy-MM-dd HH:mm:ss来解析的话,同一时间戳格式化的字符串会不同,相当于图中的时间字符串去掉时区信息。-Duser.timezone反应的是服务中时间戳如何转换为时间字符串的规则。
- 数据库timezone设置的影响
在数据库中,我们最常使用的时间格式就是datetime和timestamp了,除了占用空间和表示范围的不同,他们还有一点不同是datetime的显示和时区无关,timestamp的显示时区有关,这是什么意思呢?
在数据库中datetime就是一个字符串2021-07-25 10:00:00,是没有时区信息的,就说了是这天的10点,没说哪个时区,所以到哪都一样,但timestamp在存储的时候,mysql会把它转化为UTC时间(可以理解为绝对时间,同时间戳的概念),在展示的时候在根据数据库的时区设置,来展示具体的时区相关的年月日时分秒。
假如一个数据库,最开始的时区设置是time_zone=+8:00,我们建一个测试表结构如下:
CREATE TABLE `test_time`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`createTime` datetime NOT NULL COMMENT '创建时间',
`updateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='时间测试';
复制代码
creatTime为datetime, updateTime为timestamp,插入一条记录后,我们看到的是:
接下来,我们把数据的时区设置为东9区
set time_zone = '+9:00';
复制代码
我们看到这条记录的展示如下,其中creatTIime没有变,但updateTime展示为21:39:04,这是按照东9区来展示的
结论和建议:
1.正确设置自己服务的时区,以及数据库连接的时区
2.在和其他服务交互时,尽量不要使用yyyy-MM-dd HH:mm:ss这种字符串,因为不确定其他服务是不是和自己服务设置的时区一样,最好使用绝对时间:long类型的时间戳
链接:https://juejin.cn/post/6988833797395939359
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)