WebSocket 连接没断,行情却停了:如何给实时数据流加双层 watchdog?

摘要

WebSocket 心跳正常、连接状态显示"已连接",但行情数据已经沉默了三分钟------这种"假活"状态比断线更危险,因为它绕过了所有常规监控。本文提出双层 watchdog 模式:连接 watchdog 盯通道交互,数据 watchdog 盯最后一条有效行情的时间戳。两者协作的状态机能在数据静默时触发告警,在连接断开后安全恢复------先校准当前状态,再恢复业务处理。文中以 TickDB 的 REST 快照、WebSocket 推送和 AI 工具入口为例,展示这套架构中每条通道的角色分工,并给出可直接验证的最小 watchdog 结构。


1. 心跳还在,数据呢?

这是一个在行情接入中反复出现的故障模式。

你们的实时行情服务跑了大半年,一直很稳定。某天下午,WebSocket 连接监控面板一片绿色------所有连接都在,心跳正常,没有断线日志。但策略模块的输出开始异常:信号频率骤降,部分品种的报价停留在三个小时前。

排查后发现,数据源在某个时间点之后不再推送 ticker 消息。不是连接断了------TCP 通道完好,协议层 ping/pong 照常往返。只是 data 帧不再到达。你的监控系统盯着连接状态,连接是绿的,于是它什么都没说。

这就是"假活":连接活着,数据死了。 它比断线更隐蔽,因为常规的"连接 watchdog"无法发现它。你需要第二层 watchdog------盯着数据本身的时间戳。


2. 为什么一层 watchdog 不够

传统的 WebSocket 监控只做一件事:检查连接是否存活。实现方式通常是心跳探测------定期发送 ping,等待 pong,超时判定断开。

这个模型假设"连接存活 = 数据正常流动"。在大多数即时通讯场景中,这个假设成立。但在行情推送场景中,有两个因素打破了它:

第一,收盘、休市或服务端静默期。 某些数据源在非交易时段会保持连接但不推送数据。此时连接 watchdog 看到的是一段正常的长连接------只是没有消息而已。

第二,服务端推送逻辑的静默故障。 更危险的情况:交易时段内,服务端因为内部错误、订阅状态丢失或队列阻塞,停止向特定连接推送数据。TCP 通道和协议层心跳完全不受影响。

这两种情况下,连接 watchdog 都会给出"一切正常"的假阳性判断。你的业务逻辑需要另一层 watchdog,直接检查数据的时效性。


3. 双层 watchdog 的状态机

把监控拆成两层,每层有明确的职责和独立的状态。

3.1 连接 watchdog(传输层)

职责:判断 WebSocket 通道本身是否可用。

输入信号

  • 协议层心跳(ping/pong 往返)
  • TCP 连接状态变更(on_open / on_close / on_error)
  • 连接空闲时间(超过阈值未收到任何帧)

状态CONNECTED / DISCONNECTED / RECONNECTING

盲区 :当状态为 CONNECTED 时,无法判断推送的 data 帧是否正在到达。它只监控通道,不监控内容。

3.2 数据 watchdog(业务层)

职责:判断行情数据是否在预期时间窗口内更新。

输入信号

  • 每条 ticker 消息到达时,记录 symbol 和本地到达时间
  • 周期性扫描:当前时间 - 每个 symbol 的最后一条数据时间

状态FRESH(在窗口内)/ STALE(超过窗口)

关键设计决策:数据 watchdog 不依赖连接 watchdog 的状态。 即使连接状态为 CONNECTED,数据也可能 STALE。反之,连接 DISCONNECTED 时,数据必然 STALE------但这个结论应该由数据 watchdog 自己得出,而不是从连接状态推导。

3.3 组合状态与恢复策略

两个 watchdog 的状态组合产生四种场景:

连接状态 数据状态 含义 动作
CONNECTED FRESH 正常 持续监控
CONNECTED STALE 假活:连接正常但数据超时未更新 触发业务告警,标记数据不可用
DISCONNECTED STALE 连接断开,数据必然过期 触发重连,进入恢复流程
RECONNECTING --- 正在恢复 重连成功后先校准,再恢复业务

恢复流程的核心原则:先校准当前状态,再恢复业务处理。 重连成功后,不等 WebSocket 推送第一条数据,立即通过 REST 快照拉取当前状态,作为断线窗口后的第一份可信数据。收到 WebSocket 推送后,对比快照确认数据流恢复,再将状态切回 FRESH。恢复期间,业务层标记为降级状态,不参与需要连续性的计算。

REST 快照在这里的角色是"当前状态的基准",不是"补齐断线窗口内的历史数据"。 断线期间的逐笔变化已经不可恢复。重连后的第一件事是确认"现在是什么状态",而不是假装没有断过。


4. 最小工程结构

下面给出双层 watchdog 的核心结构片段。这不是生产级完整实现,而是展示两层如何协作的状态机骨架。恢复流程中涉及的 REST 快照调用,在实际部署中需要一个持续可用的数据入口------本文第 5 节会以 TickDB 为例说明这个入口应该满足什么条件。

python 复制代码
"""
双层 watchdog 核心结构(教学性代码片段)
展示连接监控与数据监控的协作状态机,不包含完整网络实现。
"""

import time
import logging
from enum import Enum
from dataclasses import dataclass, field
from typing import Dict, Optional

logger = logging.getLogger(__name__)

# ------------------------------------------------------------
# 状态定义
# ------------------------------------------------------------
class ConnState(Enum):
    CONNECTED = "connected"
    DISCONNECTED = "disconnected"
    RECONNECTING = "reconnecting"

class DataState(Enum):
    FRESH = "fresh"
    STALE = "stale"

@dataclass
class SymbolWatch:
    """单个 symbol 的数据监控记录"""
    symbol: str
    last_ts: float = 0.0          # 最后一条数据的本地到达时间
    max_age_sec: float = 60.0     # 超过此时间视为 STALE

    @property
    def state(self) -> DataState:
        if self.last_ts == 0.0:
            return DataState.STALE
        return DataState.FRESH if (time.time() - self.last_ts) < self.max_age_sec else DataState.STALE

# ------------------------------------------------------------
# 双层 watchdog
# ------------------------------------------------------------
class DualWatchdog:
    """
    连接 watchdog:外部通过 report_connected / report_disconnected 更新状态。
    数据 watchdog:每次收到 ticker 数据时调用 feed(),定期调用 check() 扫描。
    """

    def __init__(self, max_data_age: float = 60.0):
        self.conn_state: ConnState = ConnState.DISCONNECTED
        self.watches: Dict[str, SymbolWatch] = {}
        self.max_data_age = max_data_age
        self._degraded = True  # 业务降级标记,启动时默认降级

    # --- 连接 watchdog 接口 ---
    def report_connected(self):
        self.conn_state = ConnState.CONNECTED
        # 不自动清除数据 STALE 状态------数据 freshness 由 feed/check 独立判断

    def report_disconnected(self):
        self.conn_state = ConnState.DISCONNECTED
        self._degraded = True

    def report_reconnecting(self):
        self.conn_state = ConnState.RECONNECTING
        self._degraded = True

    # --- 数据 watchdog 接口 ---
    def feed(self, symbol: str):
        """收到一条 ticker 数据时调用"""
        if symbol not in self.watches:
            self.watches[symbol] = SymbolWatch(symbol=symbol, max_age_sec=self.max_data_age)
        self.watches[symbol].last_ts = time.time()

    def check(self) -> Dict[str, DataState]:
        """周期性扫描所有 symbol,返回各自的数据状态"""
        result = {}
        for sym, watch in self.watches.items():
            result[sym] = watch.state
        return result

    # --- 恢复流程 ---
    def recover_with_snapshot(self, symbols: list):
        """
        重连成功后调用。
        通过 REST 快照拉取当前状态,校准数据 watchdog。
        校准的是"当前状态",不是补齐断线窗口。
        """
        logger.info(f"Recovery: snapshot calibration for {len(symbols)} symbols")
        self._degraded = False  # 快照确认后解除降级

    @property
    def is_degraded(self) -> bool:
        """业务层可读取此标记,决定是否参与连续性计算"""
        return self._degraded

    @property
    def healthy(self) -> bool:
        """整体健康:连接正常 + 所有监控 symbol 数据新鲜"""
        if self.conn_state != ConnState.CONNECTED:
            return False
        if not self.watches:
            return False
        return all(w.state == DataState.FRESH for w in self.watches.values())

结构要点:

  • report_connected() 不自动清除数据 STALE 标记------数据 freshness 只由 feed()check() 独立判断;
  • recover_with_snapshot() 在重连后通过外部快照校准当前状态,而不是等 WebSocket 自然到达;
  • healthy 综合两层状态,仅在连接正常且所有监控 symbol 数据新鲜时才返回 True

5. 三层架构的角色分工

双层 watchdog 需要两个数据入口:一个用于持续监控(数据 watchdog 的输入),一个用于断线后的状态校准(恢复流程的基准)。如果再加上 AI Agent 的工具调用入口,就形成了一个三层架构。每一层有明确的职责边界,不能混用。

这个架构需要数据源提供三种不同的接入方式,各自承担独立角色。 下面以 TickDB 为例,说明一个满足此架构的数据源应该具备的能力:

入口 角色 在 watchdog 中的位置
REST 快照 按需获取当前行情状态 恢复流程的校准基准(recover_with_snapshot 调用)
WebSocket 推送 持续接收实时行情更新 数据 watchdog 的输入源(feed() 调用)
AI 工具入口 Agent 按需查询行情的标准化工具调用 独立于 watchdog,共享同一数据源

为什么恢复流程需要 REST 快照: WebSocket 重连后,第一条推送数据可能延迟到达。如果等推送到达再恢复业务,这段时间窗口内的数据静默可能被误判为持续 STALE。REST 快照的作用是在重连后立即回答"现在是什么状态",让数据 watchdog 有一个明确的基准时间戳,然后 WebSocket 推送接过持续更新的职责。

为什么 AI 工具入口不替代 REST 快照: AI 工具入口(TickDB 提供 MCP 托管的行情查询工具)是请求-响应模式的工具调用,不是持续订阅通道。它适合 Agent 在推理过程中按需查询,但不适合作为 watchdog 的持续监控输入或恢复校准入口------后者需要明确的接口契约和可预期的响应结构。

三条通道各司其职:REST 负责校准,WebSocket 负责持续,AI 工具入口负责按需查询。混用其中任何一个角色,都会在特定故障场景下产生盲区。


6. 适合什么场景

双层 watchdog 不是银弹。它的价值取决于你的业务对数据时效性的敏感程度。

值得投入的场景:

  • 实时行情服务,行情停顿直接影响下游策略或前端展示;
  • 监控告警系统,需要区分"网络故障"和"数据静默"两种告警;
  • 需要处理非交易时段静默与交易时段异常停顿的区分;
  • 重连后需要明确的当前状态,而非依赖断线前缓存的场景。

简单脚本或偶尔查询不需要完整状态机: 如果你的使用模式是偶尔发起 REST 查询获取快照,没有持续 WebSocket 订阅,单层错误处理已经足够。双层 watchdog 的复杂度只在"连接可能活着但数据可能停了"的场景中有回报。


7. 你的行情监控,盯的是连接还是数据?

回到开头的事故模式。

如果你的监控面板只显示 WebSocket 连接状态,那"假活"已经埋在你的系统里了------它正在等待一个交易时段、一个静默故障、一个被服务端丢弃的订阅。当它发生时,连接 watchdog 会告诉你一切正常。

给实时数据流加双层 watchdog,本质上是承认一个简单的事实:连接和数据是两层东西,它们的健康状态需要分别判断。 连接健康不代表数据新鲜,数据断流不一定是连接断了。重连之后,先校准当前状态,再恢复业务处理。

如果你想验证这套架构,TickDB 提供了清晰的三层入口:REST 快照用于状态校准,WebSocket 推送用于持续监控,AI 工具入口用于 Agent 按需查询。从实现一个最小的 DualWatchdog 类开始------它能让你在下一个周五下午,比去年更早发现数据已经停了。


📡 本文以 TickDB 的 REST、WebSocket 和 AI 工具入口作为架构示例。接口文档见 TickDB 官网。本文仅讨论行情数据接入的工程实现,不构成投资建议。

标签: WebSocket, 行情监控, watchdog, 断线重连, Python, TickDB

相关推荐
石头城的小石头1 小时前
【从0到1的鼠标位置显示记录器,基于python环境pycharm下编译实施,最终打包为exe,欢迎交流】
python·目标跟踪·pycharm·计算机外设·鼠标
用户8356290780511 小时前
Python 操作 Word 修订跟踪(Track Changes)
后端·python
电商API_180079052471 小时前
Python 实现闲鱼商品列表批量采集,接口异常重试机制搭建
大数据·开发语言·数据库·爬虫·python
放下华子我只抽RuiKe51 小时前
FastAPI 全栈后端(四):认证与授权
开发语言·前端·javascript·python·深度学习·react.js·fastapi
量化君也2 小时前
从回测到全自动实盘交易,全天候策略需要经历哪些改造?
大数据·人工智能·python·算法·金融
装不满的克莱因瓶2 小时前
自然语言处理发展历史——从规则系统到大语言模型的演进之路
网络·人工智能·python·深度学习·语言模型·自然语言处理
2601_951645783 小时前
Linux 编程语言全解析:C、C++、Python、Go、Rust 谁更强?
linux·python·go·c·编程语言
themingyi3 小时前
Abaqus2024安装python包pandas
开发语言·python·pandas
殇淋狱陌3 小时前
Python列表知识思维导图
开发语言·python·学习