跳到主要内容

心跳与时钟同步

NRCP 心跳用于维护会话健康状态,时钟同步用于在 Client 与 Server 系统时间不一致的情况下,估算两端单调时钟之间的偏移与链路时延

二者共用同一类 Heartbeat Ping/Pong 帧。心跳提供周期性活性检测,Ping/Pong 中携带的时间信息提供时钟同步采样,从而支持 Payload 过期判断、Flow 活性诊断、网络延迟统计和日志关联

时钟同步不要求两端系统墙钟一致,也不要求修改操作系统时间。协议只维护一组运行时估计值,用于把发送方的单调时间戳转换到接收方单调时间轴

心跳模型

心跳由双方独立周期性发起。每个 Endpoint 都维护独立的 Heartbeat Sequence 空间,收到对端 Ping 后必须返回 Pong。Ping/Pong 同时用于会话活性检测和时钟同步采样

典型交互如下:

Client -> Server: Heartbeat Ping
Server -> Client: Heartbeat Pong

Server -> Client: Heartbeat Ping
Client -> Server: Heartbeat Pong

每个 Endpoint 都应周期性发送 Heartbeat Ping,并独立检查最近一次收到对端有效心跳的时间。收到对端 Heartbeat Ping 后,应尽快返回 Heartbeat Pong,使发起端能够完成四时间点采样

连接进入 CONNECTED 后,Heartbeat 与时钟同步必须持续维护。若 Heartbeat 超时,本地状态应进入 DEGRADED,并停止建立或运行 Flow。Flow 只有在本地连接状态为 CONNECTED 时才允许进入 Active

帧要求

心跳帧要求:

  • Message Type 使用心跳类消息;
  • Payload Codec 使用 JSON;
  • Payload 中必须携带时钟同步载荷;
  • Payload Length 按 JSON 文本序列化后的 UTF-8 字节数填写;
  • source_mono_timestamp 填写发送该心跳帧时的本地单调时间戳;

用于会话保活与时钟同步基线的心跳帧必须通过 QUIC Stream 承载。仅用于额外高频延迟采样的心跳帧允许通过 QUIC Datagram 承载,并且必须接受丢包与乱序

心跳超时

心跳超时表示 NRCP 会话或当前网络路径存在异常,不等价于 QUIC 连接一定已经断开。实现应区分 QUIC 连接状态、NRCP 会话状态和具体业务 Flow 的活性状态

接收端根据最近一次收到对端有效心跳的本地单调时间判断心跳状态:

Δh=tntl\Delta_h = t_n - t_l Δh>ThHeartbeat Timeout\Delta_h > T_h \Rightarrow \text{Heartbeat Timeout} Δh>Th+TrNRCP Session Expired\Delta_h > T_h + T_r \Rightarrow \text{NRCP Session Expired}

其中:

  • Δh\Delta_h 表示距离上次收到有效心跳的时间;
  • tnt_n 表示当前本地单调时间;
  • tlt_l 表示最近一次收到对端有效心跳的本地单调时间;
  • ThT_h 表示心跳超时时间;
  • TrT_r 表示 NRCP 层恢复窗口,前提是底层 QUIC 连接仍未断开;

推荐状态处理:

状态触发条件行为
ACTIVEΔhTh\Delta_h \le T_h会话心跳正常
TIMEOUTΔh>Th\Delta_h > T_h标记 Session 异常,停止接受该 Session 的高危控制类消息
EXPIREDΔh>Th+Tr\Delta_h > T_h + T_r在当前 QUIC 连接上放弃恢复旧 NRCP Session,并清理其资源

双方都必须执行本地心跳超时处理:

  • Client 检测到心跳超时后,应停止发送控制类消息,并在同一 QUIC 连接上尝试恢复 NRCP 层会话状态;
  • Server 检测到心跳超时后,应停止处理该 Session 的高危控制类消息,并使机器人进入安全状态;
  • NRCP 层恢复窗口只用于保留可恢复状态,不用于维持控制能力;

机器人安全状态由业务状态机定义,例如 SAFE_STOPPASSIVE_HOLDCONTROL_LOST。协议只要求 Server 在控制 Session 超时后进入安全降级,不规定具体运动控制策略

当前版本不定义控制权租约。后续版本若引入控制权,心跳超时与控制权撤销的关系应由控制权章节单独定义

恢复职责

NRCP 层恢复只由 Client 主动发起,Server 不主动恢复 Client

Client 恢复流程必须覆盖以下步骤:

  • 在当前 QUIC 连接上重新建立 NRCP Session 状态;
  • 重新订阅必要 Flow;

Server 在恢复窗口 TrT_r 内保留必要的 Session 和订阅恢复信息,以便 Client 在当前 QUIC 连接上恢复 NRCP 层状态。超过 TrT_r 后,Server 应清理旧 Session、Flow 上下文、QoS Grant 和订阅状态等资源

若底层 QUIC 连接已经断开,旧 NRCP Session 立即失效,当前版本不提供跨 QUIC 连接的 Session 恢复机制。Client 必须新建 QUIC 连接,新连接视为全新的 NRCP Session

当前版本不定义控制权恢复语义。后续版本若引入控制权,控制权恢复规则应由控制权章节单独定义

Payload

心跳帧的 Payload 编码格式为 JSON,Payload 内容为 UTF-8 编码的 JSON Object。字段名使用小写下划线风格,时间单位统一为微秒

Client 发起心跳请求时,Payload 示例:

{
"kind": "clock_sync_ping",
"sequence": 1024
}

Server 返回心跳响应时,Payload 示例:

{
"kind": "clock_sync_pong",
"sequence": 1024,
"server_recv_mono_us": 223456789120
}

发送方发送时间由帧头 source_mono_timestamp 表达,因此 JSON Payload 中不重复携带发送时间。Client 收到响应后,使用本地记录、Pong Payload 与 Pong 帧头补齐四个时间点:

名称含义
t0发起方发送 Ping 时的单调时间,对应 Ping 帧头 source_mono_timestamp,发起方本地也应按 sequence 记录
t1响应方接收 Ping 时的单调时间,对应 Pong Payload 中的 server_recv_mono_us 或等价字段
t2响应方发送 Pong 时的单调时间,对应 Pong 帧头 source_mono_timestamp
t3发起方接收 Pong 时的单调时间,由发起方本地记录

若由 Client 发起 Ping,响应字段名为 server_recv_mono_us。若由 Server 发起 Ping,响应字段名应表达 Client 接收 Ping 的时间,例如 client_recv_mono_us

偏移估算

发起方可用 NTP 风格公式估算响应方单调时间相对发起方单调时间的偏移:

O=(t1t0)+(t2t3)2O = \frac{(t_1 - t_0) + (t_2 - t_3)}{2} D=(t3t0)(t2t1)D = (t_3 - t_0) - (t_2 - t_1)

其中:

  • OO 表示 offset_us,即把发起方单调时间转换到响应方单调时间轴时需要加上的估计偏移;
  • DD 表示 delay_us,即本次采样估算出的往返链路时延;
  • t0t_0t3t_3 属于发起方单调时间轴;
  • t1t_1t2t_2 属于响应方单调时间轴;

实现应保留多次采样结果,并优先选择延迟较低、抖动较小的样本更新当前偏移估计

明显异常的样本应被丢弃,例如:

  • D<0D < 0DD 超过会话阈值;
  • 同一端时间点不满足单调递增关系,例如 t3<t0t_3 < t_0t2<t1t_2 < t_1
  • sequence 无法匹配本地未完成的 Ping;
  • 样本对应的 Ping 已过期;
  • 时间字段缺失、类型错误或超出合理范围;

Offset 估计值具有时效性。实现应记录估计值更新时间,并在超过最大有效期、采样质量持续恶化或链路路径发生变化时降低其可信度,必要时暂停依赖精确时效性的判断

双向同步

任一 Endpoint 均允许发起时钟同步。每个 Endpoint 应维护 Peer Monotonic Time 到 Local Monotonic Time 的运行时估计值

若某一端需要对来自对端的业务消息执行 TTL、stale 或延迟判断,则该端必须具备对端单调时间到本地单调时间轴的有效偏移估计。仅由 Client 发起的 Ping/Pong 只能使 Client 估算 Server 相对 Client 的时间偏移,不能自动使 Server 获得 Client 相对 Server 的时间偏移

每个端点应维护独立的 Clock Sync Sequence 空间。sequence 在同一 NRCP 会话、同一发送方向内单调递增,Ping/Pong 匹配时应结合发送方向、Message Type 和 sequence 进行关联

时间转换

接收端在执行 TTL、stale 或延迟判断时,必须将发送方的 source_mono_timestamp 转换到本地单调时间轴,再估算消息年龄

当 Client 需要把 Server 单调时间转换到 Client 单调时间轴时:

Cs=SOC_s = S - O

当 Client 需要把 Client 单调时间转换到 Server 单调时间轴时:

Sc=C+OS_c = C + O

消息年龄估算:

A=RXrA = R - X_r

其中:

  • CC 表示 Client 本地单调时间;
  • SS 表示 Server 本地单调时间;
  • CsC_s 表示 Server 时间 SS 映射到 Client 时间轴后的时间;
  • ScS_c 表示 Client 时间 CC 映射到 Server 时间轴后的时间;
  • RR 表示接收端当前本地单调时间,对应 receiver_now_mono_us
  • XrX_r 表示发送方 source_mono_timestamp 映射到接收端时间轴后的时间,对应 source_time_on_receiver_axis_us
  • AA 表示估算出的消息年龄,对应 estimated_age_us

估算结果用于 TTL 判断、状态 stale 判断、控制消息过期丢弃和延迟统计。TTL 仍然来自 Server 授权的 Flow QoS,不能由发送端在单条消息中自报

校验约束

时钟同步 Payload 不能作为连接状态、权限或 Flow 授权来源。接收端必须校验 Message Type、Channel ID、会话状态,以及在适用时校验 Flow ID 或 Flow Epoch,再使用 Heartbeat Payload 更新时钟估计

实现还应防止异常时钟样本污染当前估计值:

  • sequence 必须用于关联 Ping 与 Pong;
  • sequence 或重复 sequence 应被丢弃;
  • 时间字段缺失、类型错误或超出合理范围时应丢弃该样本;
  • 单次样本不应直接大幅覆盖当前偏移,应经过滤波或阈值判断;

与 QoS 的关系

时钟同步是 QoS 时效性判断的基础能力,但不属于狭义 QoS 属性

QoS 仍然由 Server 授权,时钟同步只提供以下辅助信息:

  • 估算消息年龄;
  • 判断 Payload 是否超过 TTL;
  • 统计链路延迟与抖动;
  • 辅助判断 Flow 是否 stale;