服务器时间对齐
1. 服务器时间不准,到底会出什么事?
很多人觉得"差几秒而已,能怎样"。下面这些都是真实事故:
1.1 证书校验失败,全站 HTTPS 不可用
TLS 证书有 notBefore 和 notAfter。如果服务器时钟比真实时间快了 10 分钟,而证书刚好在这 10 分钟内过期——所有 HTTPS 请求开始报 cert has expired,全站挂掉。
真实案例:2017 年 GitLab 一次故障,根因之一就是某台机器时钟偏差导致证书校验异常。
1.2 数据库主从复制断裂
MySQL 主从复制依赖 binlog 中的时间戳判断事件顺序。如果从库时钟比主库慢,可能出现"从库认为这条日志来自未来"的情况,轻则复制延迟飙升,重则复制中断。
PostgreSQL 的逻辑复制更严格——如果备库时钟回跳,可能导致 WAL 顺序混乱。
1.3 分布式锁失效
Redis 分布式锁靠 TTL 过期。如果持锁机器的时钟突然快进 30 秒,锁提前过期,另一台机器拿到锁——两台机器同时操作同一个资源,数据损坏。
1.4 日志排序错乱,排障困难
8 台机器的日志汇总到 ELK,按 @timestamp 排序。如果其中 2 台时钟差了 5 秒,事件顺序就是乱的——你看到"先返回响应,后收到请求",排查陷入混乱。
1.5 定时任务重复执行或漏执行
cron 任务分散在多台机器上。时钟不准则可能导致:
- 重复执行:两台机器的时钟不同步,都认为"现在到了整点",各跑了一次。
- 漏执行:一台机器时钟慢了,跳过了某个时刻。
1.6 一句话总结影响
| 偏差量 | 可能的后果 |
|---|---|
| < 1ms | 几乎无感知,高频交易除外 |
| 1ms ~ 50ms | 分布式锁、数据库复制可能出现边界问题 |
| 50ms ~ 1s | 日志乱序、监控指标时间偏移、告警误判 |
| 1s ~ 30s | 证书校验失败、定时任务紊乱、缓存过期异常 |
| > 30s | Kerberos 认证失败、TLS 握手失败、主从复制断裂 |
2. 服务器时钟为什么会不准?
理解原因,才能对症下药。
2.1 硬件层面:晶振不准
每台服务器主板上有一个实时时钟(RTC),靠石英晶振驱动。石英晶振的频率受以下因素影响:
- 制造公差:不同晶振的标称频率有 ±10~50 ppm 的偏差(1 ppm = 百万分之一)。50 ppm 意味着每天差 4.3 秒。
- 温度漂移:温度变化 10°C,频率可能偏移 10~50 ppm。机房空调波动、CPU 负载导致的局部温升都会影响。
- 老化:晶振使用越久,频率越漂,每年可能额外偏移 1~5 ppm。
一台典型的服务器:
晶振精度: ±20 ppm
→ 每天偏差: 20 × 86400 / 1e6 = 1.7 秒
→ 每月偏差: ~53 秒
→ 不校时的话,一个月后和标准时间差将近一分钟
2.2 软件层面:系统时钟与硬件时钟
Linux 有两个时钟:
| 时钟 | 来源 | 特性 | 用途 |
|---|---|---|---|
| CLOCK_REALTIME(墙上时钟) | 内核维护,可被 NTP 调整 | 可能跳变 | 记录时间戳、日志 |
| CLOCK_MONOTONIC(单调时钟) | 内核维护,从不回退 | 只增不减 | 测量时间间隔 |
NTP 校时会修改 CLOCK_REALTIME。如果 NTP 一次性把时钟拨快/拨慢 10 秒,所有用 CLOCK_REALTIME 算时间差的代码都会出问题。这就是为什么测量间隔必须用 CLOCK_MONOTONIC。
2.3 虚拟化层面:时钟漂移更严重
虚拟机的时钟由宿主机的虚拟时钟驱动,额外引入:
- VM exit/entry 延迟:每次 CPU 在 VM 和 hypervisor 之间切换,时钟都可能丢几个 tick。
- vCPU 迁移:vCPU 在不同物理 CPU 核之间迁移,各核的 TSC(Time Stamp Counter)可能不完全同步。
- 宿主机负载高:宿主机 CPU 满载时,VM 感觉到的"时间流速"变慢。
经验值:物理服务器不校时,一天漂 1~2 秒;虚拟机不校时,一天可以漂 10~30 秒。
3. NTP:最常用的服务器时间同步方案
3.1 NTP 的工作原理
NTP 是客户端-服务器协议,走 UDP 123 端口。一次同步交互包含 4 个时间戳:
客户端发送请求: T1 (客户端发送时刻)
服务器收到请求: T2 (服务器收到时刻)
服务器发送回复: T3 (服务器发送时刻)
客户端收到回复: T4 (客户端收到时刻)
网络单向延迟 (offset 估算):
往返延迟 RTT = (T4 - T1) - (T3 - T2)
时钟偏移 offset = ((T2 - T1) + (T3 - T4)) / 2
客户端调整: 本地时钟 += offset
关键假设:去程和回程的网络延迟相等。如果去程 10ms、回程 50ms,估算的 offset 会有 20ms 误差。这就是为什么 NTP 精度受网络对称性限制。
3.2 NTP 的层级结构(Stratum)
Stratum 0: 原子钟 / GPS 授时接收器(硬件参考源)
│
Stratum 1: 直接连接 Stratum 0 的服务器(一级时间服务器)
│
Stratum 2: 从 Stratum 1 同步的服务器
│
Stratum 3: 从 Stratum 2 同步的服务器(你的业务服务器通常在这层)
每多一层,精度大约降一个数量级。Stratum 3 的典型精度是 1~50ms。
3.3 chrony vs ntpd:该用哪个?
ntpd 是传统 NTP 守护进程,chrony(chronyd)是现代替代品。生产环境推荐用 chrony。
| 对比项 | ntpd | chrony |
|---|---|---|
| 收敛速度 | 慢,启动后需要几分钟才稳定 | 快,几秒内就能同步 |
| 大偏差处理 | 偏差 > 128ms 时拒绝调整 | 能处理任意大小的偏差 |
| 网络断开后 | 漂移较快 | 内部跟踪漂移率,断网后也能保持较长时间准确 |
| 最小同步间隔 | 固定 | 自适应,根据漂移情况动态调整 |
| 内存占用 | 较大 | 很小 |
| 是否推荐 | 传统环境 | 现代 Linux 发行版默认(RHEL 7+、Ubuntu 18.04+) |
3.4 chrony 配置示例
# /etc/chrony/chrony.conf
# 上游时间服务器(建议配 3~4 个,互为校验)
server ntp.aliyun.com iburst minpoll 4 maxpoll 10
server cn.pool.ntp.org iburst minpoll 4 maxpoll 10
server time.windows.com iburst minpoll 4 maxpoll 10
# 允许 chrony 在大偏差时直接跳变(而不是缓慢调整)
# 生产环境慎用——跳变会导致基于 REALTIME 的计算出错
makestep 1.0 3
# 允许 chrony 跟踪硬件 RTC,重启后用 RTC 做初始估计
rtcsync
# 允许 drift(漂移率)记录到文件,重启后恢复
driftfile /var/lib/chrony/drift
# 日志
logdir /var/log/chrony
参数解释:
iburst:启动时快速发 4 个请求,加快初始同步。minpoll 4:最小轮询间隔 2^4 = 16 秒。maxpoll 10:最大轮询间隔 2^10 = 1024 秒(约 17 分钟)。chrony 会在这个范围内自适应。makestep 1.0 3:如果偏差超过 1 秒,前 3 次同步允许直接跳变。之后只允许缓慢调整(slew)。
3.5 验证同步状态
# 查看同步状态
chronyc tracking
# 输出示例:
# Reference ID : A1A2A3A4 (ntp.aliyun.com)
# Stratum : 3
# System time : 0.000123456 seconds fast of NTP time
# Last offset : +0.000045678 seconds
# RMS offset : 0.000098765 seconds
# Frequency : 5.678 ppm fast
# Update interval : 64.7 seconds
# Leap status : Normal
# 关键指标:
# System time — 当前时钟与 NTP 标准的偏差
# RMS offset — 偏差的均方根,反映同步稳定性
# Frequency — 本地晶振的漂移率,chrony 在持续补偿这个值
# 查看各上游源的状态
chronyc sources -v
# 输出示例:
# MS Name/IP address Stratum Poll Reach LastRx Last sample
# ===================================================================
# ^* ntp.aliyun.com 2 10 377 45m +23ms[+25ms] +/- 12ms
# ^+ cn.pool.ntp.org 2 10 377 45m -18ms[-16ms] +/- 18ms
# ^- time.windows.com 2 10 377 45m +45ms[+47ms] +/- 25ms
# ^* = 当前选中的同步源
# ^+ = 备选源
# ^- = 被排除的源(偏差太大)
# Reach = 377 (八进制) = 0b11111111,表示最近 8 次请求全部成功
4. PTP:需要更高精度时
4.1 什么时候需要 PTP?
| 场景 | 需要的精度 | 方案 |
|---|---|---|
| 普通业务服务器 | 10~50ms | NTP / chrony |
| 数据库集群 | 1~10ms | NTP / chrony(配本地 NTP 源) |
| 金融交易撮合 | < 1ms | PTP |
| 5G 基站同步 | < 1μs | PTP + GPS |
| 高频交易(HFT) | < 100ns | PTP + GPS + 硬件时间戳 |
4.2 PTP vs NTP 的核心区别
NTP: 软件时间戳,精度受协议栈延迟抖动限制
精度: 毫秒级
PTP (IEEE 1588): 硬件时间戳,在网卡 PHY 层打时间戳
消除了操作系统协议栈的不确定延迟
精度: 亚微秒级
PTP 通过 Pdelay 机制测量链路延迟的不对称性,并补偿这个不对称——这是 NTP 做不到的(NTP 假设链路对称)。
4.3 PTP 的部署要求
- 网卡支持硬件时间戳:需要支持 IEEE 1588 的网卡(如 Intel i210、Mellanox ConnectX 系列)。
- 交换机支持 PTP:边界时钟(Boundary Clock)或透明时钟(Transparent Clock),否则交换机引入的排队延迟会破坏精度。
- 全网链路 PTP 使能:任何一跳不支持,精度就在那一跳退化到软件时间戳水平。
普通业务服务器不需要 PTP。NTP/chrony 配置得当,精度足够覆盖 99% 的场景。
5. 生产环境最佳实践
5.1 架构:不要让所有服务器都去公网同步
┌─────────────┐
│ 公网 NTP │
│ (Stratum 1) │
└──────┬──────┘
│
┌──────▼──────┐
│ 内网 NTP 主 │ ← 2~3 台,专门做时间服务器
│ (Stratum 2) │ 可以绑 GPS 授时卡
└──┬───┬───┬──┘
│ │ │
┌────────┘ │ └────────┐
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ 业务机 A │ │ 业务机 B │ │ 业务机 C │ ← 全部从内网 NTP 同步
└─────────┘ └─────────┘ └─────────┘
好处:
- 业务机不依赖公网可达性,公网断了内网时间仍然准(内网 NTP 有本地 RTC 和漂移记录兜底)。
- 减少公网 NTP 流量(人家也是免费服务,别滥用)。
- 内网延迟对称性好,同步精度更高。
5.2 内网 NTP 服务器的配置
# /etc/chrony/chrony.conf (内网 NTP 服务器)
# 上游用公网 NTP
server ntp.aliyun.com iburst
server cn.pool.ntp.org iburst
# 允许内网客户端来同步
allow 10.0.0.0/8
# 作为 NTP 服务器,即使自己还没同步好,也对外提供服务
# (否则所有客户端都拒绝同步,形成"全集群不同步"的死锁)
local stratum 5
# 如果有 GPS 授时卡(如 /dev/gps0),可以绑上去
# refclock PHC /dev/ptp0 poll 2 dpoll 2 offset 0 trust prefer
5.3 业务服务器的配置
# /etc/chrony/chrony.conf (业务服务器)
# 只从内网 NTP 同步,配 2~3 台互为备份
server 10.0.0.10 iburst minpoll 4 maxpoll 8
server 10.0.0.11 iburst minpoll 4 maxpoll 8
server 10.0.0.12 iburst minpoll 4 maxpoll 8
# 不对外提供 NTP 服务
deny all
# 漂移记录
driftfile /var/lib/chrony/drift
rtcsync
5.4 监控:你必须监控时钟偏差
不监控就等于没同步——你不知道哪台机器偷偷漂走了。
# 用 chronyc 获取偏差,推送到监控系统
OFFSET=$(chronyc tracking | grep "System time" | awk '{print $4}')
# 推送到 Prometheus / Zabbix / CloudWatch
告警阈值建议:
| 级别 | 偏差阈值 | 动作 |
|---|---|---|
| 正常 | < 50ms | 无 |
| 警告 | 50ms ~ 500ms | 告警通知,不自动处理 |
| 严重 | > 500ms | 告警 + 自动重启 chrony |
| 紧急 | > 5s | 告警 + 摘除节点(停止调度流量到此机器) |
5.5 虚拟机的额外注意事项
# KVM 虚拟机: 确保宿主机开启 kvm-clock
# 宿主机 qemu-kvm 参数加: -rtc base=utc,clock=host
# 这让 VM 直接用宿主机的时钟,减少一层漂移
# VMware 虚拟机: 安装 VMware Tools,开启 Tools 同步
# vmware-toolbox-cmd timesync enable
# AWS EC2: 默认提供 Amazon Time Sync Service
# server 169.254.169.123 prefer iburst minpoll 4 maxpoll 10
# 这是一个 Stratum 1 源,免费且每个可用区都有
# 华为云 ECS: 可使用华为云内网 NTP
# server ntp.myhuaweicloud.com iburst
6. 常见问题排查
6.1 chrony 启动后一直不同步
# 检查 1: UDP 123 端口是否被防火墙挡了
chronyc sources
# 如果 Reach 是 0,说明请求发不出去
# 检查 2: 上游服务器是否可达
nc -uv ntp.aliyun.com 123
# 检查 3: 系统是否装了多个时间同步服务(ntpd 和 chronyd 冲突)
systemctl status ntpd
systemctl status chronyd
# 只能运行一个!
# 检查 4: SELinux 是否阻止了 chrony
getsebool -a | grep chrony
6.2 时钟反复跳变,不稳定
# 检查 makestep 配置——如果阈值太低,chrony 会频繁跳变
# 修正: 提高 makestep 阈值,或去掉 makestep 让 chrony 只做 slew(缓慢调整)
# makestep 10 0 # 只在偏差 > 10s 时跳变,且只跳一次
# 检查上游源是否互相矛盾
chronyc sources
# 如果多个源的 Last sample 偏差方向不一致,说明某个源本身不准
# 去掉不靠谱的源
6.3 容器内时间不准
# Docker 容器默认共享宿主机的时钟,不需要单独同步
# 但如果容器内 chrony 和宿主机 chrony 冲突:
# 容器内不要装 chrony,直接用宿主机的时间
#
# 如果宿主机时间不准,容器里怎么调都没用——先修宿主机
# Kubernetes: 确保所有 Node 的时间同步
# 在 DaemonSet 里跑一个检查脚本,发现偏差就告警
6.4 跨时区问题
# 服务器统一用 UTC,不要用本地时区
timedatectl set-timezone UTC
# 应用代码里:
# - 存储、计算、日志: 用 UTC
# - 展示给用户: 在前端/接口层转成用户时区
# - cron 表达式: 注意 cron 用的是系统时区,如果系统是 UTC,
# "每天凌晨 2 点执行" 要写成 "0 18 * * *"(北京时间凌晨 2 点 = UTC 18 点)
7. 一张图总结
服务器时间对齐
├── 为什么重要
│ ├── TLS 证书校验
│ ├── 数据库复制
│ ├── 分布式锁
│ ├── 日志排序
│ └── 定时任务
│
├── 为什么不准
│ ├── 晶振公差 + 温度漂移 + 老化
│ ├── REALTIME vs MONOTONIC
│ └── 虚拟化额外漂移
│
├── 同步方案
│ ├── NTP / chrony(毫秒级,覆盖 99% 场景)
│ └── PTP(亚微秒级,需硬件支持)
│
├── 生产实践
│ ├── 内网 NTP 分层架构
│ ├── 配 3+ 个上游源互验
│ ├── 监控偏差 + 分级告警
│ └── 虚拟机/容器特殊处理
│
└── 排查清单
├── 端口/防火墙
├── 服务冲突
├── makestep 频繁跳变
└── 时区统一用 UTC
- 点赞
- 收藏
- 关注作者
评论(0)