mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 23:00:44 +00:00
现象:用户早上打开 relay.xxmxxs.top 看到 Cloudflare Error 1033。
watchdog 日志把过程记下来了:
公网不通(HTTP 000,ha_connections=2) <- 隧道显示连着,公网不通
公网不通(HTTP 502,ha_connections=2)
公网恢复(HTTP 200) <- 自己又好了
也就是说:**间歇性假活**,短样本测不出来。
我之前只测 15~25 次就说"可用率 100%",太草率了。
根因:4G 链路会**静默断开** TCP 连接 —— 不发 FIN/RST,双方都以为还连着。
Cloudflare 把请求发到这条僵尸连接上,就回 502/530/1033。
Linux 默认 tcp_keepalive_time = **7200 秒(2 小时)**才发现连接死了,
所以中间这段时间服务一直"看着在线、实际打不开"。
tunnel.log 最近 3000 行的佐证:
i/o timeout 212 次
Retrying connection 127 次
Lost connection 22 次
修法:
1. TCP keepalive 调到 30 秒 + 每 10 秒探测 3 次
=> 最多约 60 秒发现僵尸连接,cloudflared 立刻重连
(/etc/sysctl.d/97-ykxmz-keepalive.conf)
2. 隧道连接数 2 -> 4,多几条冗余
3. 修 relay 从不清理设备记录的问题:
self.devices 只增不减,被控端每换端口/重装就多一条,
实测一天攒了 75 条僵尸记录,/v1/health 从几 KB 涨到 20KB。
加 DEVICE_TTL(6 小时)在 snapshot() 里清掉。
实测(修完立刻验证):
设备侧 公网 15/15 HTTP 200
Windows 公网 25/25,中位 520 ms,最快 463 ms
health 20931 字节 / 75 台 -> 152 字节 / 0 台
138 lines
5.4 KiB
Bash
138 lines
5.4 KiB
Bash
#!/bin/bash
|
||
# 真修:让死掉的 TCP 连接被**快速发现**
|
||
#
|
||
# 根因(watchdog 日志 + 隧道日志看出来的):
|
||
# 隧道显示 ha_connections=2(本地认为连着),但公网 HTTP 000/502/530。
|
||
# 隧道日志最近 3000 行里 i/o timeout 212 次、Retrying 127 次。
|
||
#
|
||
# => 4G 链路会把 TCP 连接**静默断开**:连接实际死了,
|
||
# 但双方都没有收到 FIN/RST,于是都以为还连着。
|
||
# Cloudflare 把请求发到这条"僵尸连接"上,就回 502/530/1033。
|
||
#
|
||
# Linux 默认参数:
|
||
# tcp_keepalive_time = 7200 秒(2 小时!)才发现连接死了
|
||
# tcp_keepalive_intvl = 75 秒
|
||
# tcp_keepalive_probes = 9 次
|
||
# => 最坏要 2 小时才知道连接没了。中间这段时间服务就是"假活"。
|
||
#
|
||
# 修法:
|
||
# 1. 把 keepalive 调到 30 秒 + 10 秒探测 3 次
|
||
# => 最多 ~60 秒发现僵尸连接,cloudflared 立刻重连
|
||
# 2. ha-connections 从 2 提到 4,多几条冗余,一条死了还有别的
|
||
# 3. 顺手清掉测试留下的 ~90 台僵尸设备(health 接口都被撑到 20KB 了)
|
||
set -u
|
||
|
||
RELAY_DIR=/root/lanremote-relay
|
||
|
||
echo "════════ 1. 改 TCP keepalive(核心修复)════════"
|
||
cat > /etc/sysctl.d/97-ykxmz-keepalive.conf <<'EOF'
|
||
# ykxmz:快速发现"静默断开"的 TCP 连接
|
||
#
|
||
# 背景:这台设备走 4G,运营商链路会静默丢连接(不发 FIN/RST)。
|
||
# Linux 默认 7200 秒才发现,期间 cloudflared 以为自己连着、
|
||
# Cloudflare 也以为隧道活着,但请求发过来就是 502/1033。
|
||
# 把 keepalive 调到 30 秒,最多一分钟就能发现并重连。
|
||
net.ipv4.tcp_keepalive_time = 30
|
||
net.ipv4.tcp_keepalive_intvl = 10
|
||
net.ipv4.tcp_keepalive_probes = 3
|
||
EOF
|
||
sysctl -p /etc/sysctl.d/97-ykxmz-keepalive.conf 2>&1 | sed 's/^/ /'
|
||
|
||
echo
|
||
echo " 生效确认:"
|
||
for k in tcp_keepalive_time tcp_keepalive_intvl tcp_keepalive_probes; do
|
||
printf " %-26s = %s\n" "$k" "$(sysctl -n net.ipv4.$k)"
|
||
done
|
||
|
||
echo
|
||
echo "════════ 2. 隧道连接数提到 4 ════════"
|
||
CURRENT=$(grep -o 'ha-connections [0-9]*' /etc/systemd/system/lanremote-tunnel.service)
|
||
echo " 当前: $CURRENT"
|
||
sed -i 's/--ha-connections [0-9]*/--ha-connections 4/' /etc/systemd/system/lanremote-tunnel.service
|
||
NEW=$(grep -o 'ha-connections [0-9]*' /etc/systemd/system/lanremote-tunnel.service)
|
||
echo " 改为: $NEW"
|
||
|
||
echo
|
||
echo "════════ 3. 清掉测试留下的僵尸设备 ════════"
|
||
python3 - <<'PY'
|
||
import json
|
||
import os
|
||
import time
|
||
from pathlib import Path
|
||
|
||
# 中转把设备登记在 data 目录里,找找在哪个文件
|
||
base = Path("/root/lanremote-relay/data")
|
||
if not base.is_dir():
|
||
print(" 没有 data 目录")
|
||
raise SystemExit(0)
|
||
|
||
for path in sorted(base.glob("*.json")):
|
||
try:
|
||
data = json.loads(path.read_text(encoding="utf-8"))
|
||
except Exception as exc:
|
||
print(" %s 读不了: %s" % (path.name, exc))
|
||
continue
|
||
if not isinstance(data, dict):
|
||
print(" %s 不是字典,跳过" % path.name)
|
||
continue
|
||
# 找设备表
|
||
for key in ("devices", "deviceMap", "device_map", "clients"):
|
||
if key in data and isinstance(data[key], (dict, list)):
|
||
items = data[key]
|
||
before = len(items)
|
||
now = time.time()
|
||
if isinstance(items, dict):
|
||
keep = {k: v for k, v in items.items()
|
||
if isinstance(v, dict)
|
||
and now - float(v.get("lastSeen", 0) or 0) < 3600}
|
||
removed = before - len(keep)
|
||
if removed:
|
||
data[key] = keep
|
||
else:
|
||
keep = [v for v in items
|
||
if isinstance(v, dict)
|
||
and now - float(v.get("lastSeen", 0) or 0) < 3600]
|
||
removed = before - len(keep)
|
||
if removed:
|
||
data[key] = keep
|
||
if removed:
|
||
path.write_text(json.dumps(data, ensure_ascii=False),
|
||
encoding="utf-8")
|
||
print(" %s: %s 从 %d 清到 %d(删了 %d)"
|
||
% (path.name, key, before, len(keep), removed))
|
||
else:
|
||
print(" %s: %s 共 %d 条,都是新的" % (path.name, key, before))
|
||
PY
|
||
|
||
echo
|
||
echo " 清理后 health 大小:"
|
||
curl -s --max-time 10 http://127.0.0.1:8080/v1/health -o /tmp/h.json
|
||
echo " $(wc -c < /tmp/h.json) 字节"
|
||
python3 -c "
|
||
import json
|
||
d = json.load(open('/tmp/h.json'))
|
||
print(' 设备数:', len(d.get('devices', [])))
|
||
" 2>/dev/null | sed 's/^/ /'
|
||
|
||
echo
|
||
echo "════════ 4. 重启隧道让新参数生效 ════════"
|
||
systemctl daemon-reload
|
||
systemctl restart lanremote-tunnel
|
||
sleep 30
|
||
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')"
|
||
echo " 落点:"
|
||
grep -E 'Registered tunnel connection' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -4 \
|
||
| grep -oE 'location=[a-z0-9]+ protocol=[a-z0-9]+' | sed 's/^/ /'
|
||
|
||
echo
|
||
echo "════════ 5. 立刻测公网 ════════"
|
||
ok=0
|
||
for i in $(seq 1 10); do
|
||
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \
|
||
-H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health)
|
||
[ "$c" = "200" ] && ok=$((ok+1))
|
||
printf " %2d: %s\n" "$i" "$c"
|
||
sleep 1
|
||
done
|
||
echo " 可用 $ok/10"
|