From 2b204cae07337f9a2dc47bd85369ed365693fb13 Mon Sep 17 00:00:00 2001 From: xmz7891 Date: Wed, 7 Oct 2026 10:37:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E7=9C=8B=E9=97=A8=E7=8B=97=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E6=AD=BB=E5=BE=AA=E7=8E=AF=EF=BC=88=E6=88=91=E4=B8=8A?= =?UTF-8?q?=E4=B8=80=E7=89=88=E6=94=B9=E8=BF=87=E5=A4=B4=E4=BA=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户报 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, 是我自己后台还跑着一个观察任务在并发打接口造成的干扰。 **测稳定性的时候,测量工具本身不能成为干扰源。** --- _build/_c12.txt | 9 --- _build/commit_watchdog_grace.py | 102 +++++++++++++++++++++++++++++ _build/diag_502_now.sh | 66 +++++++++++++++++++ _build/fix_watchdog_loop.sh | 110 ++++++++++++++++++++++++++++++++ relay/watchdog.sh | 18 +++++- 5 files changed, 294 insertions(+), 11 deletions(-) delete mode 100644 _build/_c12.txt create mode 100644 _build/commit_watchdog_grace.py create mode 100644 _build/diag_502_now.sh create mode 100644 _build/fix_watchdog_loop.sh diff --git a/_build/_c12.txt b/_build/_c12.txt deleted file mode 100644 index a60310f..0000000 --- a/_build/_c12.txt +++ /dev/null @@ -1,9 +0,0 @@ -看门狗检查间隔 20 秒 -> 10 秒 - -每次 530 窗口从"出现"到"被看门狗发现并重启",最长要等一个检查周期。 -20 秒缩到 10 秒,用户撞上 1033 的概率窗口直接减半。 - -代价:公网探测从每 20 秒一次变成每 10 秒一次。 -这台设备本来就在长轮询,这点流量可以忽略。 - -设备上已生效(OnUnitActiveSec=10s, AccuracySec=3s)。 diff --git a/_build/commit_watchdog_grace.py b/_build/commit_watchdog_grace.py new file mode 100644 index 0000000..377d9ef --- /dev/null +++ b/_build/commit_watchdog_grace.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +r'''把 watchdog 的宽限期修复同步到仓库并提交。''' +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +GIT = r"C:\Program Files\Git\bin\git.exe" +WATCHDOG = ROOT / "relay" / "watchdog.sh" +sys.path.insert(0, str(ROOT / "_build")) + +from devssh import run # noqa: E402 + +MSG = """修看门狗重启死循环(我上一版改过头了) + +用户报 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, +是我自己后台还跑着一个观察任务在并发打接口造成的干扰。 +**测稳定性的时候,测量工具本身不能成为干扰源。** +""" + + +def sync_watchdog() -> None: + """把设备上改好的 watchdog.sh 拉回本地仓库。""" + _c, out, _e = run("cat /root/lanremote-relay/watchdog.sh", timeout=120) + if not out or "MIN_RESTART_GAP" not in out: + print(" [失败] 拉不到设备上的 watchdog") + return + if "GRACE" not in out: + print(" [警告] 设备上的版本没有 GRACE,先在本机补上") + + WATCHDOG.write_text(out, encoding="utf-8", newline="\n") + print(" 已同步 watchdog.sh(%d 行)" % out.count("\n")) + + print(" 关键参数:") + for line in out.splitlines(): + if line.startswith(("MIN_RESTART_GAP", "GRACE", "THRESHOLD", + "STALE_MINUTES")): + print(" " + line.strip()) + + +def commit() -> None: + print() + subprocess.run([GIT, "add", "-A"], cwd=str(ROOT), check=False) + msg_file = ROOT / "_build" / "_c13.txt" + msg_file.write_bytes(MSG.encode("utf-8")) + r = subprocess.run([GIT, "commit", "-q", "-F", str(msg_file)], + cwd=str(ROOT), capture_output=True, text=True, + encoding="utf-8", errors="replace") + msg_file.unlink(missing_ok=True) + print(" commit exit=%s" % r.returncode) + show = subprocess.run([GIT, "log", "--oneline", "-1"], cwd=str(ROOT), + capture_output=True, text=True, encoding="utf-8", + errors="replace") + print(" " + (show.stdout or "").strip()) + env = dict(os.environ) + env["GIT_SSH_COMMAND"] = r'"C:\Program Files\Git\usr\bin\ssh.exe"' + push = subprocess.run([GIT, "push", "origin", "main"], cwd=str(ROOT), + capture_output=True, text=True, encoding="utf-8", + errors="replace", env=env) + for line in (push.stderr or push.stdout or "").strip().splitlines()[-2:]: + print(" " + line) + + +def main() -> int: + sync_watchdog() + commit() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/diag_502_now.sh b/_build/diag_502_now.sh new file mode 100644 index 0000000..50eebd9 --- /dev/null +++ b/_build/diag_502_now.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# 502 Bad Gateway —— Cloudflare 说源站报错。立刻查 relay。 +set -u + +echo "════════ 1. relay 还活着吗 ════════" +echo " 服务: $(systemctl is-active lanremote-relay)" +echo " 进程: $(pgrep -c -f 'relay.py' 2>/dev/null || echo 0) 个" +echo " 端口: $(ss -lntp 2>/dev/null | grep -c ':8080')" +echo " 本机 HTTP: $(curl -s -o /dev/null -w '%{http_code}' --max-time 8 http://127.0.0.1:8080/v1/health)" +echo " 官网本机: $(curl -s -o /dev/null -w '%{http_code}' --max-time 8 http://127.0.0.1:8080/)" + +echo +echo "════════ 2. relay 日志最后的错误 ════════" +tail -60 /root/lanremote-relay/relay.log 2>/dev/null | tail -30 | sed 's/^/ /' + +echo +echo "════════ 3. journal 里的 relay 错误 ════════" +journalctl -u lanremote-relay --since '20 minutes ago' --no-pager 2>/dev/null \ + | grep -iE 'error|traceback|exception|killed|oom|restart' | tail -12 | sed 's/^/ /' +echo " (没输出=没有明显错误)" + +echo +echo "════════ 4. relay 重启过几次 ════════" +journalctl -u lanremote-relay --since '30 minutes ago' --no-pager 2>/dev/null \ + | grep -cE 'Started|Stopped' | sed 's/^/ 事件数: /' +journalctl -u lanremote-relay --since '30 minutes ago' --no-pager 2>/dev/null \ + | grep -E 'Started|Stopped|Deactivated|killed' | tail -8 | sed 's/^/ /' + +echo +echo "════════ 5. 内存(OOM 会把 relay 干掉)════════" +free -m | sed 's/^/ /' +echo " relay 内存: $(ps -o rss= -p $(pgrep -f 'relay.py' | head -1) 2>/dev/null | awk '{printf "%d MB", $1/1024}')" +systemctl show lanremote-relay -p MemoryCurrent -p MemoryMax --value 2>/dev/null | sed 's/^/ /' + +echo +echo "════════ 6. 隧道连接数 + 落点 ════════" +echo " 连接: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')" +grep -E 'Registered tunnel connection' /root/lanremote-relay/tunnel.log 2>/dev/null | tail -4 \ + | grep -oE 'location=[a-z0-9]+ protocol=[a-z0-9]+' | sed 's/^/ /' + +echo +echo "════════ 7. cloudflared 有没有报回源错误 ════════" +tail -200 /root/lanremote-relay/tunnel.log 2>/dev/null \ + | grep -iE 'origin|502|refused|dial tcp 127|connection refused|no route' | tail -10 | sed 's/^/ /' +echo " (没输出=cloudflared 没报回源错)" + +echo +echo "════════ 8. 公网连打 12 次 ════════" +ok=0 +for i in $(seq 1 12); do + c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \ + -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/) + [ "$c" = "200" ] && ok=$((ok+1)) + printf " %2d: %s\n" "$i" "$c" + sleep 1 +done +echo " 可用 $ok/12" + +echo +echo "════════ 9. 当前还在跑的长轮询(会不会占满线程)════════" +ss -tn state established '( sport = :8080 )' 2>/dev/null | wc -l | sed 's/^/ 到 8080 的连接数: /' +ps -o nlwp= -p $(pgrep -f 'relay.py' | head -1) 2>/dev/null | sed 's/^/ relay 线程数: /' + +echo +echo "════════ 10. watchdog 最近 ════════" +tail -10 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /' diff --git a/_build/fix_watchdog_loop.sh b/_build/fix_watchdog_loop.sh new file mode 100644 index 0000000..975267e --- /dev/null +++ b/_build/fix_watchdog_loop.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# 修 watchdog 的重启死循环(这是我上一版改过头造成的) +# +# 现象(watchdog 日志): +# [10:31:57] 只剩 1 条连接 -> 重启隧道 +# [10:32:22] 重启完 +# [10:32:24] 又只剩 1 条 -> 距上次才 27 秒,先不重启 +# [10:32:47] 距上次 50 秒 -> 又重启 +# ... 每 50 秒一次,永远不消停 +# +# 根因: +# 隧道在**质量差的 4G 上重建 4 条连接要 30~60 秒**。 +# 而我上一版把 MIN_RESTART_GAP 设成 45 秒, +# 并且"公网不通 + 连接数<=1"就立刻重启 —— +# 结果每次隧道还没把连接建齐,就被下一次重启打断, +# **永远停在"只剩 1 条"的状态**。 +# +# 等于我自己制造了不稳定。 +# +# 修法: +# 1. 重启后给**宽限期**(GRACE=150 秒):这段时间内绝不重启, +# 让 cloudflared 专心把连接建齐。 +# 2. MIN_RESTART_GAP 从 45 提到 150 秒(和宽限期一致)。 +# 3. "立刻重启"的判定加一条:距上次重启必须超过宽限期。 +# 确认故障还是立刻重启,但**不能在上次重启还没缓过来的时候**。 +set -u + +echo "════════ 1. 先停掉 watchdog,别一边改一边被它重启 ════════" +systemctl stop lanremote-watchdog.timer 2>&1 | sed 's/^/ /' +echo " timer: $(systemctl is-active lanremote-watchdog.timer)" + +echo +echo "════════ 2. 让隧道安静地重建一次(别再打断它)════════" +systemctl restart lanremote-tunnel +echo " 已重启,等它把连接建齐…" +for i in $(seq 1 24); do + sleep 10 + n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \ + | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') + pub=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 \ + -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health 2>/dev/null) + echo " ${i}0s: 连接 ${n:-?} 公网 $pub" + [ "$n" = "4" ] && [ "$pub" = "200" ] && { echo " ✓ 已经建齐且公网通"; break; } +done + +echo +echo "════════ 3. 写入修好的 watchdog ════════" +# 只在几处关键参数上打补丁,其余逻辑不动 +python3 - <<'PY' +from pathlib import Path + +path = Path("/root/lanremote-relay/watchdog.sh") +text = path.read_text(encoding="utf-8") + +# 间隔 45 -> 150 +text = text.replace( + "MIN_RESTART_GAP=45", + "# 两次重启至少隔这么久。\n" + "# 150 秒不是拍脑袋:隧道在差的 4G 上重建 4 条连接要 30~60 秒,\n" + "# 原来设 45 秒会导致**隧道还没建齐就被下一次重启打断**,\n" + "# 永远停在只剩 1 条连接的状态(实测死循环,每 50 秒重启一次)。\n" + "# 给足宽限期,让它有机会稳定下来。\n" + "MIN_RESTART_GAP=150\n" + "GRACE=150") + +# "确认故障立刻重启" 加上宽限期判断 +old = '''if [ "$DEGRADED" = "1" ]; then + restart_tunnel "公网不通且只剩 ${CONNS} 条隧道连接(确认故障,立即重启)" +fi''' +new = '''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 old in text: + text = text.replace(old, new, 1) + print(" 已加宽限期判断") +else: + print(" [警告] 找不到'立即重启'那段,没改") + +path.write_text(text, encoding="utf-8", newline="\n") +print(" 参数: MIN_RESTART_GAP=150 GRACE=150") +PY + +echo +echo " 语法检查: $(bash -n /root/lanremote-relay/watchdog.sh 2>&1 && echo OK || echo 失败)" +grep -E '^MIN_RESTART_GAP|^GRACE|^THRESHOLD' /root/lanremote-relay/watchdog.sh | sed 's/^/ /' + +echo +echo "════════ 4. 重新开 watchdog ════════" +systemctl start lanremote-watchdog.timer +echo " timer: $(systemctl is-active lanremote-watchdog.timer)" + +echo +echo "════════ 5. 公网连测 15 次 ════════" +ok=0 +for i in $(seq 1 15); do + c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \ + -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/) + [ "$c" = "200" ] && ok=$((ok+1)) + printf " %2d: %s\n" "$i" "$c" + sleep 2 +done +echo " 可用 $ok/15" diff --git a/relay/watchdog.sh b/relay/watchdog.sh index 6a06b6a..9ebecaf 100644 --- a/relay/watchdog.sh +++ b/relay/watchdog.sh @@ -31,7 +31,13 @@ STALE_MINUTES=10 # 日志多久没动静算卡死 # 真断的时候会干等(日志里出现过"距上次重启才 118 秒,先不重启", # 结果多断了 2 分钟)。 # 设 45 秒:既够 cloudflared 把连接建起来,又不会干等太久。 -MIN_RESTART_GAP=45 +# 两次重启至少隔这么久。 +# 150 秒不是拍脑袋:隧道在差的 4G 上重建 4 条连接要 30~60 秒, +# 原来设 45 秒会导致**隧道还没建齐就被下一次重启打断**, +# 永远停在只剩 1 条连接的状态(实测死循环,每 50 秒重启一次)。 +# 给足宽限期,让它有机会稳定下来。 +MIN_RESTART_GAP=150 +GRACE=150 log() { logger -t "$LOG_TAG" "$1" @@ -144,7 +150,15 @@ log "公网不通($DETAIL),连续第 $FAIL 次(降级 $ZERO 次)" # 这种情况立刻重启,不等攒够 3 次 —— 每次检查间隔 20 秒, # 等 3 次就是白断 40 秒。 if [ "$DEGRADED" = "1" ]; then - restart_tunnel "公网不通且只剩 ${CONNS} 条隧道连接(确认故障,立即重启)" + # 宽限期内不重启 —— 让上一次重启有机会把连接建齐。 + # (不加这条会死循环:隧道还没建齐就被再次打断。) + 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