mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 18:20:43 +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 台
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
r'''长时间观察公网可用率 —— 这才是判断"稳不稳"的正确方法。
|
||
|
||
我之前只测了 15~25 次就连着说"可用率 100%",太草率了。
|
||
用户早上打开就撞上 Error 1033 —— 说明**间断性掉线**,
|
||
短样本根本看不出来。
|
||
|
||
这个脚本跑 10 分钟,每 3 秒一次,统计:
|
||
* 总可用率
|
||
* 连续失败的最长时长(这个最要命:用户点进去就是白页)
|
||
* 失败的时间分布(是零星抖动还是成片断)
|
||
'''
|
||
from __future__ import annotations
|
||
|
||
import statistics
|
||
import sys
|
||
import time
|
||
import urllib.error
|
||
import urllib.request
|
||
|
||
URL = "https://relay.xxmxxs.top/v1/health"
|
||
HEADERS = {"User-Agent": "LanRemote/1.4.0"}
|
||
DURATION = 600 # 观察 10 分钟
|
||
INTERVAL = 3 # 每 3 秒一次
|
||
|
||
|
||
def probe(timeout: float = 12.0) -> tuple:
|
||
"""返回 (是否成功, 耗时秒, 状态码或错误名)。"""
|
||
start = time.perf_counter()
|
||
try:
|
||
with urllib.request.urlopen(
|
||
urllib.request.Request(URL, headers=HEADERS),
|
||
timeout=timeout) as resp:
|
||
resp.read()
|
||
ok = resp.status == 200
|
||
return ok, time.perf_counter() - start, str(resp.status)
|
||
except urllib.error.HTTPError as exc:
|
||
return False, time.perf_counter() - start, "HTTP%d" % exc.code
|
||
except Exception as exc: # noqa: BLE001
|
||
return False, time.perf_counter() - start, exc.__class__.__name__
|
||
|
||
|
||
def main() -> int:
|
||
print(" 观察 %d 分钟,每 %d 秒一次" % (DURATION // 60, INTERVAL))
|
||
print()
|
||
|
||
total = ok_count = 0
|
||
times = []
|
||
failures = []
|
||
streak = 0
|
||
worst_streak = 0
|
||
worst_start = None
|
||
current_start = None
|
||
|
||
deadline = time.time() + DURATION
|
||
while time.time() < deadline:
|
||
ok, elapsed, note = probe()
|
||
total += 1
|
||
if ok:
|
||
ok_count += 1
|
||
times.append(elapsed)
|
||
streak = 0
|
||
current_start = None
|
||
else:
|
||
failures.append((time.strftime("%H:%M:%S"), note))
|
||
if streak == 0:
|
||
current_start = time.time()
|
||
streak += 1
|
||
if streak > worst_streak:
|
||
worst_streak = streak
|
||
worst_start = current_start
|
||
# 每分钟报一次进度
|
||
if total % 20 == 0:
|
||
print(" 已测 %3d 次,成功 %3d,失败 %2d"
|
||
% (total, ok_count, total - ok_count))
|
||
time.sleep(INTERVAL)
|
||
|
||
print()
|
||
print(" ================ 结果 ================")
|
||
rate = ok_count / total * 100 if total else 0
|
||
print(" 样本 %d 次(约 %d 分钟)" % (total, DURATION // 60))
|
||
print(" 可用率 %.1f%% (%d/%d)" % (rate, ok_count, total))
|
||
|
||
if times:
|
||
print(" 成功时延迟 中位 %.0f ms 最快 %.0f ms 最慢 %.0f ms"
|
||
% (statistics.median(times) * 1000,
|
||
min(times) * 1000, max(times) * 1000))
|
||
|
||
if failures:
|
||
span = worst_streak * INTERVAL
|
||
print(" **最长连续失败 %d 次(约 %d 秒)**" % (worst_streak, span))
|
||
if worst_start:
|
||
print(" 开始于 %s" % time.strftime("%H:%M:%S",
|
||
time.localtime(worst_start)))
|
||
print(" 失败明细(最多列 20 条):")
|
||
for stamp, note in failures[:20]:
|
||
print(" %s %s" % (stamp, note))
|
||
else:
|
||
print(" 全程没有失败")
|
||
|
||
print()
|
||
print(" 判读:")
|
||
if rate >= 99.5:
|
||
print(" ≥99.5% —— 可以接受")
|
||
elif rate >= 97:
|
||
print(" 97~99.5% —— 有抖动,用户偶尔会遇到打不开")
|
||
else:
|
||
print(" <97% —— **不稳定**,必须查根因(用户会经常撞上 Error 1033)")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|