Files
ykxmz/relay/watchdog.sh
T
xmz7891 2b204cae07 修看门狗重启死循环(我上一版改过头了)
用户报 502 Bad Gateway,Cloudflare 的图显示
「Host: relay.xxmxxs.top - Error」,也就是源站/隧道这一跳挂了。

查 watchdog 日志,发现它在**每 50 秒重启一次隧道**:
    [10:31:57] 只剩 1 条连接 -> 重启隧道
    [10:32:22] 重启完
    [10:32:24] 又只剩 1 条 -> 距上次才 27 秒,先不重启
    [10:32:47] 距上次 50 秒 -> 又重启
    ... 循环不停

根因(我上一版的错):
  隧道在**质量差的 4G 上重建 4 条连接要 30~60 秒**。
  而我上一版把 MIN_RESTART_GAP 设成 45 秒,并且
  "公网不通 + 连接数<=1"就立刻重启 ——
  结果每次隧道还没把连接建齐,就被下一次重启打断,
  **永远停在"只剩 1 条"的状态**。
  等于我自己制造了不稳定。

修法:
  1. 重启后给 GRACE=150 秒宽限期,这期间绝不重启,
     让 cloudflared 专心把连接建齐
  2. MIN_RESTART_GAP 从 45 提到 150(和宽限期一致)
  3. "立刻重启"的判定加一条:距上次重启必须超过宽限期

实测(停掉 watchdog -> 手动重启隧道 -> 观察):
    10 秒内建齐 4 条连接,公网立刻 200
    宽限期生效后公网 20/20 = 100%,延迟 ~690ms

顺带一个教训:之前测出 80%、20% 的 000,
是我自己后台还跑着一个观察任务在并发打接口造成的干扰。
**测稳定性的时候,测量工具本身不能成为干扰源。**
2026-10-07 10:37:26 +08:00

169 lines
6.2 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 看门狗第三版:两个信号都查。
#
# 为什么还要改:
# 第二版只看 cloudflared 的 ha_connections 指标。
# 但"僵死连接"会把指标维持在 1 —— 计数还在,实际早断了。
# 这种时候看门狗以为一切正常,一直不管。
# (今早就遇到一次:浏览器 1033,指标却说有 1 条连接)
#
# 这一版:
# 信号 A:ha_connections == 0 -> 记一次"零连接"
# 信号 B:公网 /v1/health 打不通 -> 记一次"公网不通"
# 任一信号连续 2 次 -> 重启隧道
# 另外:隧道日志超过 10 分钟没新行 + 公网不通 -> 判定进程卡死,重启
set -u
RELAY_DIR=/root/lanremote-relay
URL_FILE="$RELAY_DIR/public_url.txt"
STATE_FILE="$RELAY_DIR/watchdog.state"
TUNNEL_LOG="$RELAY_DIR/tunnel.log"
METRICS=http://127.0.0.1:20241/metrics
LOG_TAG=lanremote-watchdog
# 连续几次公网不通才重启。
# 设 3(约 60 秒)而不是 2:短抖动让 cloudflared 自己重连就好,
# 频繁重启反而让它一直停在"正在重连"的状态。
THRESHOLD=3
STALE_MINUTES=10 # 日志多久没动静算卡死
# 两次重启之间至少隔这么久(秒)。
# 实测隧道重建只要 30~40 秒,原来设 120 秒太保守 ——
# 真断的时候会干等(日志里出现过"距上次重启才 118 秒,先不重启",
# 结果多断了 2 分钟)。
# 设 45 秒:既够 cloudflared 把连接建起来,又不会干等太久。
# 两次重启至少隔这么久。
# 150 秒不是拍脑袋:隧道在差的 4G 上重建 4 条连接要 30~60 秒,
# 原来设 45 秒会导致**隧道还没建齐就被下一次重启打断**,
# 永远停在只剩 1 条连接的状态(实测死循环,每 50 秒重启一次)。
# 给足宽限期,让它有机会稳定下来。
MIN_RESTART_GAP=150
GRACE=150
log() {
logger -t "$LOG_TAG" "$1"
echo "[$(date '+%m-%d %H:%M:%S')] $1"
}
read_state() {
local key="$1" default="$2" value=""
if [ -f "$STATE_FILE" ]; then
value=$(grep "^$key=" "$STATE_FILE" 2>/dev/null | cut -d= -f2)
fi
[ -n "$value" ] && echo "$value" || echo "$default"
}
write_state() { printf 'zero=%s\nfail=%s\n' "$1" "$2" > "$STATE_FILE"; }
LAST_RESTART_FILE="$RELAY_DIR/watchdog.last_restart"
restart_tunnel() {
local why="$1"
local now last gap
now=$(date +%s)
last=0
[ -f "$LAST_RESTART_FILE" ] && last=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)
gap=$((now - last))
# 连接数归零 = 真的断了,不是抖动 —— 这种不受间隔限制
local conns_now
conns_now=$(timeout 5 curl -s --max-time 4 "$METRICS" 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
if [ "${conns_now:-1}" != "0" ] && [ "$gap" -lt "$MIN_RESTART_GAP" ]; then
# 刚重启过还没到间隔:这次先不重启,
# 让 cloudflared 自己把连接建起来(它本来就在重试)。
log "$why —— 但距上次重启才 ${gap} 秒(下限 ${MIN_RESTART_GAP} 秒),先不重启"
write_state 0 0
exit 0
fi
log "$why —— 重启隧道(距上次 ${gap} 秒)"
echo "$now" > "$LAST_RESTART_FILE"
systemctl restart lanremote-tunnel
write_state 0 0
sleep 25
log "重启后地址: $(cat "$URL_FILE" 2>/dev/null || echo '(还没拿到)')"
exit 0
}
ZERO=$(read_state zero 0)
FAIL=$(read_state fail 0)
# ---------- 信号 A:cloudflared 自报的连接数 ----------
CONNS=$(timeout 5 curl -s --max-time 4 "$METRICS" 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
[ -z "$CONNS" ] && CONNS=""
# ---------- 信号 B:公网真的通吗 ----------
URL=$(cat "$URL_FILE" 2>/dev/null | tr -d '\r\n')
CODE="000"
if [ -n "$URL" ]; then
CODE=$(timeout 20 curl -4 -s -o /dev/null -w '%{http_code}' \
-H 'User-Agent: LanRemote/1.4.0' "$URL/v1/health" 2>/dev/null)
CODE=${CODE:-000}
fi
PUBLIC_OK=0
[ "$CODE" = "200" ] && PUBLIC_OK=1
# ---------- 信号 C:日志僵死了吗 ----------
LOG_STALE=0
if [ -f "$TUNNEL_LOG" ]; then
LAST=$(stat -c %Y "$TUNNEL_LOG" 2>/dev/null || echo 0)
NOW=$(date +%s)
if [ "$LAST" -gt 0 ] && [ $((NOW - LAST)) -gt $((STALE_MINUTES * 60)) ]; then
LOG_STALE=1
fi
fi
# ---------- 判断 ----------
if [ "$PUBLIC_OK" = "1" ]; then
# 公网通就是真的通,计数器清零
if [ "$ZERO" != "0" ] || [ "$FAIL" != "0" ]; then
log "公网恢复(HTTP 200),计数清零(之前 zero=$ZERO fail=$FAIL)"
fi
write_state 0 0
exit 0
fi
# 公网不通 —— 看是哪种原因
DETAIL="HTTP $CODE"
[ -n "$CONNS" ] && DETAIL="$DETAIL,ha_connections=$CONNS"
if [ "$LOG_STALE" = "1" ] && [ "$FAIL" -ge 1 ]; then
restart_tunnel "日志 ${STALE_MINUTES} 分钟没动静加上公网不通(进程卡死)"
fi
FAIL=$((FAIL + 1))
# 连接数 <= 1 也算"降级"。
#
# 原来是 == 0 才算,但实测**只剩 1 条连接时跨机房回源就会失败**
# (用户撞上的 530 就是这种:请求落 SEA,隧道只剩 LAX 一条)。
# 所以 1 和 0 一样危险,都计入降级计数。
DEGRADED=0
if [ -n "$CONNS" ] && [ "$CONNS" -le 1 ] 2>/dev/null; then
ZERO=$((ZERO + 1))
DEGRADED=1
fi
log "公网不通($DETAIL),连续第 $FAIL 次(降级 $ZERO 次)"
# 公网不通 + 连接数已经降到危险区 = **确认的故障**,不是短抖动。
# 这种情况立刻重启,不等攒够 3 次 —— 每次检查间隔 20 秒,
# 等 3 次就是白断 40 秒。
if [ "$DEGRADED" = "1" ]; then
# 宽限期内不重启 —— 让上一次重启有机会把连接建齐。
# (不加这条会死循环:隧道还没建齐就被再次打断。)
LAST_R=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)
SINCE=$(( $(date +%s) - LAST_R ))
if [ "$SINCE" -lt "$GRACE" ]; then
log "公网不通且只剩 ${CONNS} 条连接,但距上次重启才 ${SINCE} 秒(宽限 ${GRACE} 秒)—— 先让它建完"
else
restart_tunnel "公网不通且只剩 ${CONNS} 条隧道连接(确认故障,立即重启)"
fi
fi
if [ "$FAIL" -ge "$THRESHOLD" ] || [ "$ZERO" -ge "$THRESHOLD" ]; then
restart_tunnel "连续 $FAIL 次公网不通(含 $ZERO 次降级)"
fi
write_state "$ZERO" "$FAIL"