三台主机各按各的时钟在记日志
目标
在三台主机用各自不同的时钟打出的日志中,分辨出没有偏移量或含义不同的写法,用基准事件拆分并估计每台主机的本地偏移量和时钟误差,把所有行重写为可信的 UTC,从而理顺被颠倒的因果顺序。
为什么重要
在故障调查中,最先要确定的是“什么先发生”。这个顺序的依据是日志中的时间,而这些时间并不是事实,而是主张。没有偏移量,就无法从外部知道是哪个地区的时钟;-00:00 与 Z 含义不同;即使偏移量准确,只要主机的时钟超前几秒,原因就会比结果更晚被记录。让时间变得可信,不是分析的准备工作,而是分析本身。
步骤
- 创建并运行
/root/clock/gen_clock.py,在/root/clock/raw/下生成 app-seoul.log、db-frankfurt.log、edge-newyork.log。 /root/clock/stamps.json——写明每个文件的时间写法有什么不同,以及现在立刻就能转换成 UTC 的行有多少行。/root/clock/dst_probe.json——用 zoneinfo 实测在有夏令时的地区,本地时间如何消失或出现两次。/root/clock/anchors.json——找出在三个文件中都留下痕迹的探测信号。/root/clock/skew.json——用基准事件拆分并估计每台主机的本地偏移量和时钟误差。/root/clock/fixed.ndjson——把所有行用校正后的 UTC 时间重写,并按时间顺序排列。/root/clock/causality.json——统计校正之前原因比结果更晚被记录的请求,并与校正之后比较。/root/clock/clock_report.md——以报告的形式写明校正了什么、依据是什么。
参考
- 校正后的
ts全部写成2026-03-08T06:05:00.200Z的样子——日期与时间之间是大写的T,毫秒三位数,末尾是大写的Z。 - 读取行中时间的规则统一为一条:带偏移量就按偏移量原样读取,不带就先按 UTC 读取。这个假设是否正确,由基准事件来判定。
- Python 的
strptime用%z既能读-05:00,也能读-00:00。用zoneinfo.ZoneInfo和datetime的fold来处理夏令时区间。 - 本地偏移量是 15 分钟的整数倍。把与基准的差值四舍五入到最近的 15 分钟,就得到本地偏移量,剩下的秒数就是该主机的时钟误差。
- 常见错误:把
-00:00当作与Z含义相同来读取,把不带偏移量的行保持为 UTC 来排序,把剩下的几秒当作噪声丢掉,在夏令时切换日把偏移量固定为一种。 - 本实验的产出物全部集中在
/root/clock/下。会话结束后它们会消失,所以重要的内容请保留在屏幕上。
复现三台主机的日志
创建并运行 /root/clock/gen_clock.py,在 /root/clock/raw/ 下生成 app-seoul.log(65 行)、db-frankfurt.log(41 行)、edge-newyork.log(61 行)。
这三个文件是不同主机用各自的时钟写的,所以时间写法不同。一个完全没有偏移量,一个把偏移量写成 -00:00,一个正确地带着本地偏移量。先创建 /root/clock/raw,再把三个文件写进去。
统计哪些行现在就能转换成 UTC
在 /root/clock/stamps.json 中,为每个主机名称(app-seoul、db-frankfurt、edge-newyork)写入 lines、offset_style、utc_resolvable、distinct_offsets。offset_style 在没有任何偏移量时为 none,所有偏移量都是 -00:00 时为 -00:00,其余情况为 explicit。utc_resolvable 是只看那一行就能转换成 UTC 的行数。
原始文件有 /root/clock/raw/app-seoul.log、db-frankfurt.log、edge-newyork.log 三个。只有带偏移量的行,才能凭那一行本身确定 UTC。distinct_offsets 是把该文件中实际出现的偏移量字符串去重后收集起来的;如果一个文件里出现了两种值,请想一想这个窗口跨越了什么。
实测消失的时间和出现两次的时间
在 /root/clock/dst_probe.json 中,把下面六项按这个顺序原样写成数组。每一项都有 zone、local、count、utc 四个键,count 是该本地时间所对应的 UTC 时刻的个数,utc 是把这些时刻以 2026-03-08T08:30:00.000Z 的样子按升序存放的数组。(1)America/New_York 2026-03-08 02:30:00(2)America/New_York 2026-03-08 04:30:00(3)America/New_York 2026-11-01 01:30:00(4)Asia/Seoul 2026-03-08 02:30:00(5)Europe/Berlin 2026-03-29 02:30:00(6)Europe/Berlin 2026-10-25 02:30:00
用 zoneinfo.ZoneInfo 附上地区,再把 datetime 的 fold 分别设为 0 和 1,得到两个候选。把每个候选转换成 UTC,再转换回该地区,只有与原来的本地时间相同的才是真实的。如果没有能回得来的,这个本地时间就不存在;如果有两个不同的都能回来,就是出现两次的时间。
找出同时出现在三个文件中的基准事件
在 /root/clock/anchors.json 中把 reference_host 写为 db-frankfurt,并在 anchors 中,为三个文件中都出现过的 probe= 值,各放入 corr、app-seoul、db-frankfurt、edge-newyork 四个键,并按 corr 升序排列。三台主机的值,是该文件中所写的时间字符串原样。
原始文件有 /root/clock/raw/app-seoul.log、/root/clock/raw/db-frankfurt.log、/root/clock/raw/edge-newyork.log 三个。运维调度器发出的探测信号,是以 probe=SYNC-xxxx 的形式打出来的。只出现在一台主机上的信号不能当尺子用,所以只保留三个文件的交集。时间字符串要连偏移量在内原样放入,以后才能回溯。基准要从带着偏移量、能告知 UTC 的主机中,选择时钟可信的那一台。
拆分并估计本地偏移量和时钟误差
在 /root/clock/skew.json 中写入 reference_host 和 hosts。hosts 对每台主机都有 zone_offset_minutes、skew_seconds、anchors_used。对每个基准事件,求出那一行所标明的时间(有偏移量就原样使用,没有则按 UTC 读取的值)减去基准主机的时间所得的差值,把这个差值四舍五入到最近的 15 分钟,得到 zone_offset_minutes,剩下的秒数就是 skew_seconds。
材料是第 4 步生成的 /root/clock/anchors.json。一个差值里混着两样东西——表盘相对于 UTC 偏移了多少(本地偏移量),以及这个时钟错了多少(时钟误差)。IANA 的本地偏移量是 15 分钟的整数倍,所以能把这二者分开。基准事件有多个时,数值可能波动,所以要像中位数那样汇总成一个值,并在 anchors_used 中留下用了几个。
把所有行重写成可信的时间
在 /root/clock/fixed.ndjson 中,把三个文件的所有行按 ts、host、raw_ts、corr、msg 每行写一条,并按 ts 升序排列。ts 是从该行所标明的时间中减去 zone_offset_minutes 和 skew_seconds 得到的 UTC,样子为 2026-03-08T06:05:00.200Z。raw_ts 是原始时间字符串,corr 是 req= 或 probe= 的值(没有则为 null),msg 是时间之后剩下的正文。
材料是 /root/clock/raw/ 下的三个文件,以及第 5 步生成的 /root/clock/skew.json。校正就是两次减法——减去表盘被偏移的量,再减去时钟错误的量。不要覆盖原始字符串,而要把它作为 raw_ts 一并保留。以后更换基准时必须从头重新计算,没有原文就做不到。
统计原因比结果更晚被记录的请求
在 /root/clock/causality.json 中写入 pairs、inverted_before、inverted_after、examples。pairs 是 edge-newyork 和 db-frankfurt 两者都出现过的 req=RQ- 值的个数。inverted_before 是校正之前(按那一行所标明的时间原样)前端的时间晚于数据库时间的数量,inverted_after 是用校正后的时间重新统计的数量。examples 是校正前发生颠倒的值中,按升序排在前面的三个。
校正后的时间已经在第 6 步生成的 /root/clock/fixed.ndjson 中,校正之前的时间可以用同一个文件的 raw_ts 恢复。前端收到请求是原因,数据库处理该请求是结果,所以如果前端的时间更晚,仅从记录来看,就成了结果比原因先发生。两台主机都正确地带着偏移量,这一点是这一步的关键。
写明改了什么、依据是什么
在 /root/clock/clock_report.md 中分四节书写:## 시계가 어떻게 어긋나 있었나、## 무엇을 기준으로 삼았나、## 보정한 뒤 무엇이 달라졌나、## 다음에 받을 때의 요구사항(韩文,依次意为“时钟是如何错位的”“以什么为基准”“校正之后有什么变化”“下次接收时的要求”)。必须以数字形式包含校正的全部记录数、app-seoul 的 zone_offset_minutes、校正之前的颠倒数量。
材料是 /root/clock/skew.json、/root/clock/fixed.ndjson、/root/clock/causality.json。校正值不是测量值,而是你根据基准事件建立的假设,所以必须一并写明这个假设依赖于什么,下一个人才能回溯。最后一节要写明,向客户提出什么要求,就能让这种估计整个消失。