Skip to content

分布式时间同步技术分析

概述

在水声网络数字孪生系统中,时间同步是保证多节点仿真事件因果一致性的基础。本章分析系统当前的时间基准实现、步进时序控制机制、同步误差度量方法,并对照分布式时钟理论讨论其局限与改进方向。

当前实现分析

本地高精度计时基准

系统采用 time.perf_counter_ns() 作为运行内时钟源,在 gateway 模式的步循环启动前建立时间零点:

python
# src/unet_dt/orchestrator/runner.py:537-540
t0_ns = time.perf_counter_ns()

def now_ms() -> int:
    return int((time.perf_counter_ns() - t0_ns) // 1_000_000)

设计要点

  • perf_counter_ns 返回单调递增的纳秒级计数器,不受系统时钟回调(NTP adjustment)影响
  • 将纳秒值除以 10^6 转换为毫秒精度,满足水声信道典型传播延迟(数十到数百毫秒)的度量需求
  • t0_ns 作为运行级别的时间零点,所有 TX/RX 事件的时间戳均相对于此计算

实际精度评估

在 Windows 平台上,perf_counter_ns 底层调用 QueryPerformanceCounter,典型分辨率为 100ns 量级;Linux 上对应 clock_gettime(CLOCK_MONOTONIC),分辨率可达纳秒级。对于水声仿真场景(步长通常为秒级),此精度远超实际需求。

步进时序控制

系统采用固定步长 deltaT 驱动仿真推进。每步结束时,通过 wall-clock 对齐确保步间隔的实时性:

python
# src/unet_dt/orchestrator/runner.py:788-791
target = start_perf + (step_id + 1) * float(run_cfg.dt_seconds)
now = time.perf_counter()
if now < target:
    time.sleep(target - now)

步进控制伪代码

算法: 固定步长时序控制
输入: steps (总步数), dt (步长/秒)
输出: 各步的调度决策与指标

t_start = perf_counter()
FOR step_id = 0 TO steps-1:
    1. 构造 StepContext(step_id, t_start_ms, t_end_ms)
    2. decision = strategy.decide(context)
    3. 执行 TX 计划 (遍历 flows, 受 quota 限制)
    4. SLEEP(rx_settle_s)  -- 等待声学传播
    5. 快照 traces, 计算 window_metrics
    6. target = t_start + (step_id+1) * dt
    7. IF perf_counter() < target THEN
         SLEEP(target - perf_counter())

此方案的特点:

  • 确定性推进:每步逻辑时间固定增长 step_ms,不受实际处理耗时影响
  • wall-clock 对齐:步骤 6-7 确保仿真时间与实际时间同步,避免"时间漂移"
  • 容忍超时:若实际处理耗时超过 dt,不会补偿或跳步,仅继续执行下一步

PayloadHeader 端到端延迟计算

系统在每个水声帧的 payload 前嵌入 24 字节固定头部(protocol/payload_header.py:8-9),其中 tx_time_ms 字段记录发送时刻:

python
# src/unet_dt/orchestrator/runner.py:708-718
tx_time_ms = now_ms()
payload = (
    pack_header(
        run_id=run_u32,
        step_id=step_id,
        seq=seq,
        tx_time_ms=tx_time_ms,
        src=src,
        dst=dst,
    )
    + user_data
)

接收端解析头部后,用当前时间减去 tx_time_ms 得到端到端延迟:

python
# src/unet_dt/orchestrator/runner.py:581-583
rx_ms = now_ms()
delay = rx_ms - int(hdr.tx_time_ms) if hdr.tx_time_ms else -1

头部结构(小端序,24 字节):

偏移字段类型说明
0run_iduint32运行标识,用于过滤跨运行的残留帧
4step_iduint32步序号
8sequint32流内序列号
12tx_time_msuint64发送时刻(相对 t0_ns)
20srcuint16源节点 ID
22dstuint16目的节点 ID

BarrierState 设计

BarrierState 定义于 orchestrator/barrier.py:7-12

python
@dataclass
class BarrierState:
    step_id: int
    dt_seconds: float

    def next_step(self) -> "BarrierState":
        return BarrierState(step_id=self.step_id + 1, dt_seconds=self.dt_seconds)

设计意图:BarrierState 旨在实现步末收敛确认机制——在每个仿真步结束时,所有参与节点需要确认当前步已完成(包括所有预期的 TX/RX 事件),才能推进到下一步。这是分布式仿真中常见的 barrier synchronization 模式。

当前状态:BarrierState 仅定义了数据结构,未被 runner.py 的主循环实际调用。步推进完全依赖固定 wall-clock sleep,不做收敛确认。

clock.py 辅助模块

orchestrator/clock.py 提供了一个简单的墙钟毫秒函数:

python
# src/unet_dt/orchestrator/clock.py:6-7
def now_ms() -> int:
    return int(time.time() * 1000)

该函数基于 time.time()(系统墙钟),与 runner.py 中基于 perf_counter_nsnow_ms() 是独立的。clock.py 同样未被主循环集成。

sync_error_ms 指标

在指标计算模块中,sync_error_ms 被定义为每步输出的标准字段,但当前硬编码为 0:

python
# src/unet_dt/metrics/compute.py:64
"sync_error_ms": 0.0,

该字段的设计目的是量化各节点时钟与全局基准之间的偏差。在单机集中式架构下,所有时间戳源自同一个 perf_counter_ns,不存在时钟偏移,因此恒为 0。

理论背景与对照分析

Lamport Clock

Lamport 逻辑时钟是分布式系统中建立事件因果序的经典方案。其核心规则:

算法: Lamport 逻辑时钟
规则:
  1. 本地事件: L(e) = L(prev) + 1
  2. 发送消息 m: L(send_m) = L(prev) + 1, 附带 L(send_m)
  3. 接收消息 m: L(recv_m) = max(L(local), L(m)) + 1

与本系统的关系:当前系统由单一 Orchestrator 集中调度,所有 TX/RX 事件的时间戳由同一进程的 now_ms() 产生,天然满足因果一致性,无需 Lamport Clock。但若未来扩展为多 Orchestrator 分布式架构,则需要引入逻辑时钟或向量时钟来维护事件偏序。

Vector Clock

Vector Clock 在 Lamport Clock 基础上扩展为向量形式,可精确判断事件间的并发关系:

算法: Vector Clock (N 个节点)
每个节点 i 维护向量 VC_i[0..N-1]
规则:
  1. 本地事件: VC_i[i] += 1
  2. 发送消息: VC_i[i] += 1, 附带 VC_i
  3. 接收消息 m(附带 VC_m):
     FOR j = 0 TO N-1:
       VC_i[j] = max(VC_i[j], VC_m[j])
     VC_i[i] += 1

在水声网络仿真场景中,Vector Clock 可用于精确追踪多跳转发链路中帧的因果依赖,但其 O(N) 空间开销在大规模网络中可能成为瓶颈。

局限性分析

L-1: 集中式调度,无真正分布式同步

当前所有时间戳由单一 Orchestrator 进程生成,不存在跨进程/跨主机的时钟同步问题。这简化了实现但限制了系统的可扩展性——无法模拟真实水声网络中各节点时钟独立漂移的场景。

L-2: BarrierState / clock.py 未接入主循环

尽管系统预留了 barrier 同步和独立时钟模块,但 runner.py 的步循环未调用它们。步推进完全依赖 time.sleep() 的 wall-clock 等待,不做任何收敛确认。

L-3: sync_error_ms 硬编码为 0

同步误差指标始终输出 0,无法反映真实的时钟偏差。在当前单机架构下这是正确的,但对论文的实验分析价值有限。

改进方向

短期:激活 barrier + clock

  1. runner.py 步循环中引入 BarrierState 状态机,在每步结束时检查 RX 收敛情况
  2. clock.pynow_ms() 作为独立对照基准,计算与 perf_counter_ns 的漂移量填充 sync_error_ms

中期:NTP 级偏移校准

  1. 在多机部署场景中,启动时执行 NTP offset 采样(ntplib 库),记录各节点与参考时钟的初始偏移
  2. 在 PayloadHeader 中增加 clock_offset_us 字段,接收端可据此校准延迟计算

长期:分布式时钟模型

  1. 引入 Lamport/Vector Clock 机制,在每个 DatagramReq 中附带逻辑时间戳
  2. Orchestrator 汇总后可重建全局事件偏序图,支持因果一致性分析
  3. 模拟各节点独立时钟漂移(crystal oscillator drift model),在仿真层面体现水声网络的时间不确定性