修看门狗重启死循环(我上一版改过头了)

用户报 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,
是我自己后台还跑着一个观察任务在并发打接口造成的干扰。
**测稳定性的时候,测量工具本身不能成为干扰源。**
This commit is contained in:
xmz7891
2026-10-07 10:37:26 +08:00
parent b630028112
commit 2b204cae07
5 changed files with 294 additions and 11 deletions
-9
View File
@@ -1,9 +0,0 @@
看门狗检查间隔 20 秒 -> 10 秒
每次 530 窗口从"出现"到"被看门狗发现并重启",最长要等一个检查周期。
20 秒缩到 10 秒,用户撞上 1033 的概率窗口直接减半。
代价:公网探测从每 20 秒一次变成每 10 秒一次。
这台设备本来就在长轮询,这点流量可以忽略。
设备上已生效(OnUnitActiveSec=10s, AccuracySec=3s)。
+102
View File
@@ -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())
+66
View File
@@ -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/^/ /'
+110
View File
@@ -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"
+15 -1
View File
@@ -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
# 宽限期内不重启 —— 让上一次重启有机会把连接建齐。
# (不加这条会死循环:隧道还没建齐就被再次打断。)
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