场景:
发现部署在不同时区的服务对相同的数据库数据 返回不一致的结果
//结构体用的go的原生的time.Time
//数据库存的是timestamp类型
type M struct {
Description string `gorm:"type:varchar(255)" json:"description" binding:"required"`
Starttime time.Time `gorm:"type:timestamp" json:"starttime" binding:"required"`
Endtime time.Time `gorm:"type:timestamp" json:"endtime" binding:"required"`
}
结构体用的go的原生的time.Time
数据库存的是timestamp类型
在对时间进行逻辑处理的时候 对用到的时间条件都进行了转UTC处理 心想不应该出现结果不一致的错误。
timeCondition := time.Now().UTC().String() > m.Starttime.UTC().String()
后来打印了一下m对象 发现从数据库读的m对象的时间字段都是CST的
{ Description 2022-12-03 18:30:30 +0800 CST 2022-12-10 14:00:00 +0800 CST }
也就是说我再去将cst转为utc会减去8小时 这还得了,
于是考虑两个原因:一, 数据库时区设置有错,二, go程序读取数据库转为go对象的时候有错
由于我的数据库都是docker起的,在mysql和宿主机里都查了 就是UTC(mysql默认跟随宿主机时区)
(先决条件&知识:docker容器里的都是UTC时间)
那就是go程序读取数据库转为go对象的时候有错,为了验证这个猜想 决定在不同时区起服务进行实验:
实验数据库条件: 都是docker 起的数据库服务,且设置均为跟随系统时间 也就是docker容器的时区时间 都是UTC时间
实验后端服务条件:
墨西哥的服务:是用docker起的 为UTC时区时间
香港的服务:是用docker起的 手动将docker 容器时区修改为 毛里求斯+4 和 夏威夷 -10 进行测试
本地服务: goland里起的 也就是采用本地时区时间 即 北京时间 +8
测试结果:
墨西哥:
夏威夷:
本机:
实验结论:也就是说:读同一份相同的时间数据的时候 go程序会将其本身所运行在的系统的时区作为锚定时区,同样读一份时间数据,会将这份数据认为是其所在时区的此时此刻,也就产生了分裂和歧义。比如说实验中2022-12-02 16:38:14这串字符所代表的时间 本该是唯一的一个时刻,但是却被go程序误认为是其所在时区的2022-12-02 16:38:14这个时间,那么转换为UTC后 肯定就是不同的时间了。
对于跨时区分布式部署的服务器来说 这肯定是个大坑,除非你每个服务 都在docker容器里部署 或者 手动将不同时区的服务器调整成同一个时区时间,才能保证一致性。
另外 ,上边的实验和说法 也是建立在mysql数据库的时区是同一的情况下,假如go服务和数据库都是采用在服务器直接部署(而不是docker),各自为战,那么情况将更加混乱 ,连mysql中时间数据的字面值都会不一致,跟别说加上go程序的添乱。
解决方案:
1: 将全局时区修改为某一指定时区
//在main 函数里开头第一句 将全局时区修改为UTC时区
time.Local = time.UTC
//或者将全局时区修改为指定时区
loc, _ := time.LoadLocation("Asia/Shanghai")
time.Local = loc