4. 网关以太网与 4G 双上行链路切换配置

双上行链路拓扑与切换判据

农业物联网网关通常部署在田间机井房或大棚控制柜内,以太网接入本地光纤/宽带,4G 作为备份。要让“主用断开、备用接管”真正可靠,核心不是把两条线都插上,而是明确三件事:链路健康如何检测、切换判据与回切条件、切换期间数据如何补传。下图给出本例的链路结构与检测点分布。

田间设备 RS485 传感器 采集周期 60 s 本地缓存队列 农业物联网网关 链路探测模块(ICMP/TCP) 路由表 / 默认网关切换 断链缓存与补传队列 MQTT 客户端(上行) 以太网(主用) eth0 · 192.168.1.20 metric 100 4G(备用) wwan0 · 10.64.3.77 metric 600 云平台 MQTT Broker 设备影子 / 时序库 补传去重校验 RS485 主路径 备用路径 探测目标 223.5.5.5 / 平台域名 4G 拨号状态 + 公网可达性 切换判据 连续 3 次探测失败 间隔 5 s → 判定断链 主链路恢复 60 s 后回切 切换期间数据入队缓存

链路参数与切换策略配置

以下配置以常见的 Linux 网关(OpenWrt 或 Debian 系)为例,用 ip route 的 metric 值表达主备优先级:metric 越小越优先。以太网 metric 100、4G metric 600,正常情况下流量走以太网;当以太网探测失败时,脚本删除以太网默认路由,4G 路由自动成为最优。

# 1) 查看当前默认路由与接口状态
ip route show default
ip -brief addr show eth0
ip -brief addr show wwan0

# 2) 设定主备默认路由(metric 决定优先级)
sudo ip route replace default via 192.168.1.1 dev eth0 metric 100
sudo ip route replace default via 10.64.3.1 dev wwan0 metric 600

# 3) 链路探测参数(写入 /etc/gw-link-monitor.conf)
PROBE_TARGETS="223.5.5.5 8.8.8.8"
PROBE_INTERVAL=5          # 探测间隔 5 s
PROBE_FAIL_THRESHOLD=3    # 连续 3 次失败判定断链
PROBE_RECOVER_WAIT=60     # 主链路恢复后等待 60 s 再回切
PRIMARY_IF=eth0
PRIMARY_GW=192.168.1.1
BACKUP_IF=wwan0
BACKUP_GW=10.64.3.1
CACHE_DIR=/var/spool/gw-cache

探测脚本的核心逻辑是“失败计数 + 状态机”,避免单次丢包就误切换。下面给出可直接运行的 Bash 实现,探测使用 ping -I 绑定源接口,确保测的是该链路本身而不是系统默认路由。

#!/bin/bash
# /usr/local/bin/gw-link-monitor.sh
source /etc/gw-link-monitor.conf

fail=0
state="primary"   # primary | backup

probe_if() {
  local iface=$1
  for t in $PROBE_TARGETS; do
    if ping -I "$iface" -c 1 -W 2 "$t" >/dev/null 2>&1; then
      return 0
    fi
  done
  return 1
}

switch_to_backup() {
  ip route del default via "$PRIMARY_GW" dev "$PRIMARY_IF" 2>/dev/null
  ip route replace default via "$BACKUP_GW" dev "$BACKUP_IF" metric 600
  logger -t gw-link "PRIMARY down -> switch to 4G"
  state="backup"
}

switch_to_primary() {
  ip route replace default via "$PRIMARY_GW" dev "$PRIMARY_IF" metric 100
  ip route replace default via "$BACKUP_GW" dev "$BACKUP_IF" metric 600
  logger -t gw-link "PRIMARY recovered -> back to eth0"
  state="primary"
}

while true; do
  if [ "$state" = "primary" ]; then
    if probe_if "$PRIMARY_IF"; then
      fail=0
    else
      fail=$((fail+1))
      if [ "$fail" -ge "$PROBE_FAIL_THRESHOLD" ]; then
        switch_to_backup
        fail=0
      fi
    fi
  else
    # 备用态:先确认 4G 可用,再判断主链路是否恢复
    if ! probe_if "$BACKUP_IF"; then
      logger -t gw-link "WARN: 4G also unreachable"
    fi
    if probe_if "$PRIMARY_IF"; then
      sleep "$PROBE_RECOVER_WAIT"
      probe_if "$PRIMARY_IF" && switch_to_primary
    fi
  fi
  sleep "$PROBE_INTERVAL"
done
探测目标建议同时配置一个公共 DNS(如 223.5.5.5)和云平台域名解析结果,避免“能 ping 通外网但平台不可达”的假健康状态。若网关使用 mwan3 等现成多拨组件,可用其 health check 配置替代上述脚本,判据参数含义一致。

断网注入验证与数据补传

验证分两步:先测切换时延,再测补传完整性。切换时延的观测方法是:在网关侧持续向云平台发送带序号的心跳,同时在 PC 上以 1 s 间隔记录时间戳;拔掉网线后,统计从最后一次以太网成功心跳到第一次 4G 成功心跳之间的时间差。

验证项注入方式观测点预期判据
主链路断链切换拔掉 eth0 网线心跳序号 + 时间戳日志≤ 20 s 内恢复上行(3×5 s 探测 + 拨号/路由收敛)
主链路恢复回切重新插回网线默认路由与出口 IP60 s 稳定后回切至 eth0,无频繁抖动
4G 弱信号移至信号弱区或遮挡天线RSSI、重拨次数不误判为“主链路故障”,仅记录告警
数据补传断网 5 min 后恢复平台时序库记录数断网期间数据全部到达,按时间戳排序无缺失

补传依赖本地缓存队列。采集程序在 MQTT 发布失败时把消息写入磁盘队列,恢复后按时间顺序重发,并携带原始采集时间戳,平台侧按 deviceId + timestamp 去重。下面是一个最小可用的补传逻辑示例(Python,使用 paho-mqtt):

import json, os, time, sqlite3
import paho.mqtt.client as mqtt

DB = "/var/spool/gw-cache/queue.db"
conn = sqlite3.connect(DB)
conn.execute("""CREATE TABLE IF NOT EXISTS pending(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    topic TEXT, payload TEXT, ts INTEGER)""")
conn.commit()

def enqueue(topic, payload):
    conn.execute("INSERT INTO pending(topic,payload,ts) VALUES(?,?,?)",
                 (topic, json.dumps(payload), int(time.time())))
    conn.commit()

def flush(client):
    rows = conn.execute(
        "SELECT id,topic,payload FROM pending ORDER BY id LIMIT 200").fetchall()
    for rid, topic, payload in rows:
        info = client.publish(topic, payload, qos=1)
        if info.rc != mqtt.MQTT_ERR_SUCCESS:
            break
        conn.execute("DELETE FROM pending WHERE id=?", (rid,))
        conn.commit()

client = mqtt.Client(client_id="gw-farm-01")
client.connect("mqtt.example-farm.cn", 1883, 30)
client.loop_start()

# 采集循环:先尝试直发,失败则入队
while True:
    data = {"temp": 23.6, "soil_ec": 1.42, "ts": int(time.time())}
    info = client.publish("farm/zone1/sensor", json.dumps(data), qos=1)
    if info.rc != mqtt.MQTT_ERR_SUCCESS:
        enqueue("farm/zone1/sensor", data)
    else:
        flush(client)   # 链路正常时顺带清空积压
    time.sleep(60)
补传队列必须设置容量上限(如 5000 条或 7 天),否则长时间断网会写满网关存储导致采集程序崩溃。队列满时应丢弃最旧数据并记录丢弃计数,便于事后评估数据完整率。

结果判读与常见问题

  • 切换时延超标:多为 4G 拨号耗时或路由未及时删除。检查 ip route show default 是否同时存在两条默认路由且 metric 未生效。
  • 反复切换(抖动):探测阈值过严或回切等待过短。将失败阈值提高到 3 次以上、回切等待设为 60 s,并确认探测目标稳定。
  • 补传重复:QoS 1 本身可能重复,平台侧必须按时间戳去重;不要依赖“只发一次”。
  • 4G 有信号但不可达:检查 APN 与拨号状态,ping -I wwan0 失败而 ifconfig wwan0 有 IP,通常是运营商侧未放行或 DNS 问题。

完成上述配置与注入验证后,网关应具备“以太网优先、4G 兜底、断网缓存、恢复补传”的完整上行能力,切换时延与数据完整性均有可量化判据。