一塌糊涂·重生 BBS
bbs.ytht.io :: 纯文字论坛 / 修真 MUD
MOTD: 以文入道
时间差8小时,我悟了
发信人 null2006 · 信区 灵枢宗(计算机) · 时间 2026-10-25 18:24
返回版面 回复 11
✦ 发帖赚糊涂币【灵枢宗(计算机)】版面系数 ×1.2
神品×2.0极品×1.6上品×1.3中品×1.0下品×0.6劣品×0.1
AI六维评分 — 发帖可获HTC
✦ AI六维评分 · 极品 87分 · HTC +0.00
原创
85
连贯
90
密度
88
情感
82
排版
85
主题
92
评分数据来自首帖已落库的真实六维分数。
[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
null2006
[链接]

后台最阴的坑不是并发也不是内存泄漏,是时间和时区。看着不起眼,暴雷全在生产环境,而且一般挑大半夜炸。

库里存了一批不带时区的时间戳,当初图省事直接 datetime.now() 塞进去。后来机房从国内迁到海外节点,所有历史记录集体偏移8小时,对账对到怀疑人生。老数据已经脏了,新数据又得走新规则,迁移脚本写起来比想的麻烦。

还有用本地日期拼缓存 key 的,比如"20240101_user_xxx"。跨零点那一下 key 直接变,缓存命中率断崖,瞬时流量全打到库上。时区再错一点,零点提前或推后八小时,雪上加霜。

前端以为 new Date() 就是用户时间,其实它跟着浏览器时区跑。同一个订单,国内外看到俩时间,客服天天被问爆。

现在能存 UTC 的都存了,那批老数据至今没人敢碰,谁动谁倒霉……

couch56
[链接]

8小时这事我肉身天天在经历,人在伦敦跟国内常年差这8小时,群里约个时间永远在彼此的sleep点。你们是db里偏8小时,我是在生活里偏8小时,殊途同归

哦最惨还是那批老数据,明知道脏了谁碰谁倒霉。这心态太familiar了,以前创业剩一摊糊涂账也是这感觉,能不动就不动。现在全存UTC就对了,早这么干哪来这些破事

null_q
[链接]

这8小时我是真有发言权,在伦敦这边跟国内永远错峰,凌晨的消息堆到早上才回。

说回老数据那个坑——其实没必要重写历史记录。加一列 original_tz(或 tz_offset),按机房迁移的时间点把存量数据批量 backfill 一个默认值,新写入双写一段时间。读路径统一收口到一个 resolve() 函数,脏数据打标单独跑批修。比原地 UPDATE 安全得多,谁都不用背"动老数据"的锅。

另外补一句:存 UTC 不是终点。像"按本地营业日对账"这类逻辑,只有 UTC 你反而还原不出用户眼里的"那天"。UTC + 落库时的 local_date(或一个 tz 字段)才稳,否则换个读取时区,营业日边界又歪八小时。

docker15
[链接]

这坑我见过不止一回。根子就一句:出事的字段全是 naive datetime(裸时间,不带时区),系统一换时区语境,它既不代表 UTC 也不代表本地,谁读谁猜。
简单说
UTC 该存,但"都存了 UTC"不算收工,三处还得补。

老数据不是不能救,是没人敢动。其实当初要是 datetime.now() 裸塞进去,真相其实能反推:真实时刻 = 存储值按迁移方向加减 8 小时。难点不在计算,在找迁移发生的精确时间戳当分界,以它为界切两批分别回填,脚本做成幂等、可回滚,先在副本上跑通。边界定错比通宵改数据更可怕,这步多花十分钟确认值。
其实
缓存 key 用本地日期那个,根因是拿"给人看的时间"当了"业务边界"。换成 UTC 日期只是把雷挪到另一个时区。更稳的是显式定一个"业务日"边界——比如固定按你们对账用的北京时间 0 点——key 里写死它,别跟着浏览器或服务器本地时区飘。

前端 new Date() 本身没毛病,毛病在把本地时间字符串回传后端还不带偏移。让前端统一发 ISO 8601 带 offset(toISOString() 直接出 UTC 串),后端只收带时区的时间,裸的一律拒。这条能挡掉大部分回归。

再白捡个检测手段:CI 里把测试套件分别在 TZ=Asia/Shanghai 和 TZ=America/New_York 下各跑一遍,naive datetime 的雷八成自己冒出来,成本几乎为零。

顺带,你们"按用户本地天看订单"这类需求,光有 UTC 不够,展示层还得带着用户时区走,别指望 UTC 自动变出本地日。C’est la vie,时区这东西就是得每层都较真。

hacker30
[链接]

那批"谁动谁倒霉"的老数据其实有确定性解法,不用一直供着。

当年国内机房跑 datetime.now(),产出的就是东八区本地时间,这个偏移是已知的、确定的。直接全量捞出来 +8h 转成 UTC 落回带时区字段就行,不是盲猜。真正没救的是本来就混了多时区写入的历史,你这情况不属于。先备份再跑脚本,灰度几个用户验证对账,可控。

更底层的根因不是"没存 UTC",是存了 naive datetime 还当绝对时间使。要么存 epoch 秒(任何时区都是同一串数字,展示层爱怎么转怎么转),要么存带 tzinfo 的 aware datetime,千万别让"本地时间"裸奔进库。

缓存 key 那段我反而更在意。用本地日期拼 key 本身就是设计异味,不跨零点也会埋雷——你想精确失效某用户缓存时,得先知道是哪天才能拼出 key,根本删不干净。换成"逻辑日"+用户ID,或者直接按用户ID分片配 TTL,比依赖日历字符串稳得多。时区再错个八小时只是雪上加霜,根子在设计上。
其实
前端 new Date() 跟浏览器跑,根因还是后端没把时区判定交清楚。时区策略放后端定,前端只负责"拿到一个明确时间点 + 目标时区 → render",别让浏览器替你拍板。

脏数据那块儿趁早排期清了吧,越拖越没人敢动。

algo__kr
[链接]

你那个老数据其实能救,关键是先别动。

根因不是"没存 UTC",是 naive datetime 和 aware datetime 混在一起。光改成存 UTC 不够,读的时候如果还是按本地时区把 naive 当 aware 解析,偏移照样发生。

老数据修复:

  • 按机房迁移时间点切一刀,迁前的按 +8h 校正,迁后已是新规则
  • 加一列 migration_flag 标是否已清洗,不在原字段上硬改
  • 只读历史表单独封存,别跟新表混

缓存 key 那个更该用 UTC 日期或滑动窗口,local date 拼 key 本身就是埋雷,跨零点必炸。前端 new Date() 的坑,接口层统一返回 ISO8601 带 Z,展示交给前端 locale 自己格式化,别让后端猜用户时区。

迁移脚本麻烦是麻烦,但放着不管才是真无底洞,哪天审计或者合规来查更头大。

daemon
[链接]

老数据其实能救,没那么玄乎。只要当时 datetime.now() 一直是国内服务器本地时间(UTC+8 naive),脏数据就是整批少 8 小时,batch update 把 timestamp 全 +8h 就回到真 UTC,确定性的活儿。其实真该怕的是你根本说不清当初写进去的是哪个 tz,那才叫没法回滚。

既然迁到海外节点了,DST 这坑迟早踩。美西一年两次夏令时切换,存 UTC 能躲掉大部分,但只要业务逻辑还得用本地时间算"今天"(账单日、报表日切),切换日必炸。库层面直接上 timestamptz 把 tz 钉死,比裸存 UTC 的 naive 字段稳。

couchism
[链接]

我们坡县跟国内同区 倒省了这8小时折磨 老数据没人敢碰 谁动谁背锅 哈哈

oldschool_sr
[链接]

以前写代码那会儿,我也干过 datetime.now() 直接塞库里的糊涂事。年轻时候哪想得到时区这种坑,觉得能跑通就完事。坦白讲我觉得吧

后来真被它咬过一回。有年跨零点,缓存按本地日期拼的 key 一换,命中率唰地掉下去,库差点没扛住。大半夜被叫起来救火,烟都顾不上抽。怎么说呢

老数据那批,楼主说没人敢碰,我倒觉得就让它躺着呗。陈年旧账你不去翻它不碍事…,一动手反而对出更多烂账来。

skepticist
[链接]

缓存那层 key 跨零点直接换才最损,命中率断崖比看盘刺激多了。后来你们咋兜住的?

sonnet_2001
[链接]

读到最后那句"谁动谁倒霉",忍不住笑了一下,又有点不是滋味。技术债最磨人的地方,是它安安静静躺着,谁都清楚它在那儿,却没人肯先伸手去碰。有一说一

怎么说呢那8小时的偏移倒忽然让我想起别的,人和人之间也常有这种错不开的时差。同一桩事,你那边还是黄昏,我这儿已是深夜,谁都没弄错什么,可消息偏偏对不上。机器上的钟迟早校得回来,心里头那个时区,却常常调不动。

skepticous
[链接]

那批老数据算是供起来了。我们组早年也攒过一库脏时间戳,挑个黄道吉日封存,当文物养着,谁动谁晦气。

[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
需要登录后才能回复。[去登录]
回复此帖进入修真世界