网络层容错:TCP连接池与心跳检测、自动重连机制、多线路冗余

网络层容错,说白了就是保证你的交易指令能发出去,行情数据能收回来。我见过太多系统因为网络抖动直接崩掉,尤其是做多品种交易时,几十个交易所同时连,任何一个掉线都可能引发连锁反应。

这一章我们重点聊三个东西:TCP连接池心跳检测与自动重连多线路冗余。这三者配合好了,你的系统网络层才算真正硬起来。

TCP连接池:别让每次交易都重新握手

很多新手喜欢每次发单前新建一个TCP连接,发完就关。你想想看,一次TCP三次握手至少一个RTT(往返时延),在毫秒级的交易场景里,这简直是灾难。

我个人习惯是用连接池来管理。说白了就是预先创建一批连接,放在池子里,用的时候拿一条,用完归还。这样避免了频繁创建销毁的开销。

核心要点:连接池大小要动态调整。太小了排队,太大了浪费资源。

我在项目中遇到过一个问题:某个交易所的网关只允许每个IP同时建立10条连接。我们一开始设了20条,结果一半连不上。后来改成根据交易所限制动态配置,才稳定下来。

下面是一个简单的连接池实现思路:

class TcpConnectionPool:
    def __init__(self, host, port, min_size=5, max_size=20):
        self.host = host
        self.port = port
        self.min_size = min_size
        self.max_size = max_size
        self._pool = []
        self._init_pool()
    
    def _init_pool(self):
        for _ in range(self.min_size):
            conn = self._create_connection()
            self._pool.append(conn)
    
    def get_connection(self):
        # 从池中取连接,如果池空且未达上限则新建
        if self._pool:
            return self._pool.pop()
        if len(self._pool) < self.max_size:
            return self._create_connection()
        # 等待或超时处理
        return self._wait_for_connection()
    
    def return_connection(self, conn):
        # 检查连接是否健康,健康则归还
        if self._is_healthy(conn):
            self._pool.append(conn)
        else:
            conn.close()
            self._pool.append(self._create_connection())

避坑指南:我曾经犯过一个错——连接池里的连接长时间不用会超时断开。后来我加了个定时心跳,每隔30秒对空闲连接做一次保活检测。

心跳检测:别等断开了才发现

TCP本身有Keep-Alive机制,但默认时间太长(Linux默认7200秒)。在交易系统里,等两个小时才发现连接断了?黄花菜都凉了。

我建议自己做应用层心跳。每1-2秒发一个心跳包,如果连续3次没收到回复,就判定连接断开。

为什么是3次?因为网络偶尔会有瞬断,一次丢包不代表连接真断了。3次是个比较合理的阈值,既不会误判,也不会反应太慢。

class HeartbeatMonitor:
    def __init__(self, interval=1.5, timeout_count=3):
        self.interval = interval  # 心跳间隔(秒)
        self.timeout_count = timeout_count  # 超时次数阈值
        self._missed = 0
        self._running = False
    
    def start(self, connection):
        self._running = True
        while self._running:
            if not self._send_heartbeat(connection):
                self._missed += 1
                if self._missed >= self.timeout_count:
                    # 触发重连
                    self._on_timeout(connection)
                    break
            else:
                self._missed = 0
            time.sleep(self.interval)
    
    def _send_heartbeat(self, conn):
        try:
            conn.send(b'ping')
            return True
        except:
            return False

注意:心跳包不能太大,否则占用带宽。我一般用4字节的固定标识,比如0x00FF。另外,心跳和业务数据要共用同一个连接,别单独开一条心跳连接——那样检测不出业务连接的问题。

自动重连机制:断了要能自己爬起来

自动重连不是简单的「断了就重连」。你想想看,如果交易所网关正在重启,你每秒重连一次,不仅连不上,还会加重对方负载。

我常用的策略是指数退避:第一次重连等1秒,第二次等2秒,第三次等4秒,以此类推,直到一个上限(比如60秒)。这样既不会疯狂重连,也不会等太久。

另外,重连时要重置连接池。我见过一个系统,重连后还拿着旧连接池里的连接去发数据,结果全部超时。

class AutoReconnect:
    def __init__(self, max_retry=10, base_delay=1, max_delay=60):
        self.max_retry = max_retry
        self.base_delay = base_delay
        self.max_delay = max_delay
        self._retry_count = 0
    
    def reconnect(self, pool):
        while self._retry_count < self.max_retry:
            delay = min(self.base_delay * (2 ** self._retry_count), self.max_delay)
            time.sleep(delay)
            try:
                pool.reset()  # 重置连接池
                pool.connect()
                self._retry_count = 0
                return True
            except:
                self._retry_count += 1
        return False

个人经验:重连成功后,不要立即恢复全量交易。先发一个查询类请求,确认连接状态正常,再逐步恢复。我曾经因为重连后直接发单,结果连接虽然恢复了但会话已过期,导致订单状态丢失。

多线路冗余:电信+联通+移动

单线路最大的问题是什么?运营商故障。我记得有一次电信骨干网出问题,整个华东地区的交易都受影响。如果你只有电信线路,那就只能干瞪眼。

我建议至少接入三条线路:电信、联通、移动。每条线路独立接入,互不依赖。

具体怎么做?

  • 主备模式:默认走电信,电信断了切联通,联通断了切移动。切换时间控制在200ms以内。
  • 负载均衡:三条线路同时用,按权重分配流量。比如电信50%,联通30%,移动20%。
  • 智能路由:根据目标交易所的IP归属,自动选择最优线路。比如连上交所的服务器,优先走电信。

下面是一个多线路切换的流程图:

多线路冗余切换流程 交易系统 线路选择器(智能路由) 电信线路 主线路(权重50%) 联通线路 备线路(权重30%) 移动线路 备线路(权重20%) 心跳检测 + 自动切换 切换反馈

实际部署时,每条线路要独立接入不同的物理设备。我见过有人把三条线路都接在同一台交换机上,结果交换机挂了,三条线路全废。嗯,这种低级错误要避免。

核心原则:物理隔离 + 逻辑独立 + 自动切换。三条线路从物理设备到网络出口都要分开,切换逻辑要自动化,不能靠人工。

综合配置示例

下面是一个完整的配置示例,把连接池、心跳、重连、多线路整合在一起:

{
  "network": {
    "connection_pool": {
      "min_size": 5,
      "max_size": 20,
      "idle_timeout": 30,
      "health_check_interval": 10
    },
    "heartbeat": {
      "interval": 1.5,
      "timeout_count": 3,
      "packet_size": 4
    },
    "reconnect": {
      "max_retry": 10,
      "base_delay": 1,
      "max_delay": 60,
      "reset_pool_on_reconnect": true
    },
    "lines": [
      {
        "name": "电信",
        "weight": 50,
        "priority": 1,
        "gateway": "192.168.1.1",
        "interface": "eth0"
      },
      {
        "name": "联通",
        "weight": 30,
        "priority": 2,
        "gateway": "192.168.2.1",
        "interface": "eth1"
      },
      {
        "name": "移动",
        "weight": 20,
        "priority": 3,
        "gateway": "192.168.3.1",
        "interface": "eth2"
      }
    ],
    "switch_policy": "auto_failover",
    "switch_threshold_ms": 200
  }
}

最后说一句:网络层容错不是一劳永逸的。我每个月都会做一次故障演练,手动拔掉一条线路的网线,看看系统能不能自动切换。你想想看,如果真出问题了才发现切换逻辑有bug,那损失就大了。


公众号:蓝海资料掘金营,微信deep3321