#!/bin/bash # 双协议隧道:同时跑 QUIC 和 HTTP/2 两个 cloudflared 实例。 # # == 为什么 == # # 实测两种协议各有各的病: # QUIC 尾部延迟好(P90 ~2.5 秒),但 DialContext 会 # "no recent network activity" 超时,建连慢、偶发失败 # HTTP/2 建连可靠(TCP + MSS 钳制保护),但尾部延迟差(P90 14 秒, # TCP 队头阻塞:一条连接上丢一个包,所有流一起卡) # # 同一个隧道 ID 可以起**多个副本**,Cloudflare 会把请求分摊到 # 所有副本注册上来的连接上。所以: # # 副本 A: --protocol quic --ha-connections 5 # 副本 B: --protocol http2 --ha-connections 5 # # 一共 10 条连接、横跨两种协议。QUIC 犯病时 HTTP/2 那半边还在服务, # 反过来也一样 —— 这是真正的**故障隔离**,而不只是加连接数。 # # == 资源 == # 每个 cloudflared 约占 25 MB。设备 460 MB 总内存、平时可用 300+ MB, # 两个实例没问题(原来 MemoryMax=160M 是给单实例的,两个各自限 128M)。 set -u TUNNEL_ID=7fd33d9c-f910-42e3-88aa-752d6b2a9f2f CONFIG=/root/.cloudflared/config.yml MAIN=/etc/systemd/system/lanremote-tunnel.service SECOND=/etc/systemd/system/lanremote-tunnel-h2.service echo "════════ 1. 备份现有主服务 ════════" cp -a "$MAIN" "/root/lanremote-tunnel.service.bak.dual.$(date +%s)" echo " 当前参数: $(grep -oE '\-\-protocol [a-z0-9]+|--ha-connections [0-9]+' "$MAIN" | tr '\n' ' ')" echo echo "════════ 2. 主服务改成 QUIC + 5 ════════" sed -i 's/--protocol [a-z0-9]*/--protocol quic/' "$MAIN" sed -i 's/--ha-connections [0-9]*/--ha-connections 5/' "$MAIN" sed -i 's/^MemoryMax=.*/MemoryMax=128M/' "$MAIN" grep -E 'ha-connections|protocol|MemoryMax' "$MAIN" | sed 's/^/ /' echo echo "════════ 3. 创建 HTTP/2 副本服务 ════════" cat > "$SECOND" <<'UNITEOF' [Unit] Description=ykxmz Cloudflare 具名隧道(HTTP/2 副本) # 和主实例跑同一个隧道 ID —— Cloudflare 会把请求分摊到 # 两个副本注册的全部连接上,形成协议级的故障隔离。 After=network-online.target lanremote-relay.service lanremote-nettune.service Wants=network-online.target lanremote-relay.service [Service] Type=simple ExecStart=/usr/local/bin/cloudflared tunnel \ --protocol http2 \ --edge-ip-version 4 \ --ha-connections 5 \ --no-autoupdate \ --retries 5 \ --config /root/.cloudflared/config.yml run lanremote Restart=always RestartSec=5 MemoryMax=128M StandardOutput=append:/root/lanremote-relay/tunnel-h2.log StandardError=append:/root/lanremote-relay/tunnel-h2.log [Install] WantedBy=multi-user.target UNITEOF echo " 已写入 $SECOND" cat "$SECOND" | grep -E 'protocol|ha-connections|MemoryMax' | sed 's/^/ /' echo echo "════════ 4. 启动,等连接建齐 ════════" systemctl daemon-reload systemctl enable lanremote-tunnel-h2 > /dev/null 2>&1 systemctl restart lanremote-tunnel systemctl restart lanremote-tunnel-h2 for i in $(seq 1 40); do sleep 4 n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \ | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') # 两个实例各自暴露 metrics 端口会冲突,靠下面分别看进程 [ "${n:-0}" -ge 10 ] && break done echo " metrics 报的连接数: ${n:-?}" echo echo "════════ 5. 两个实例的进程和内存 ════════" for svc in lanremote-tunnel lanremote-tunnel-h2; do pid=$(systemctl show $svc -p MainPID --value) mem=$(systemctl show $svc -p MemoryCurrent --value) proto=$(grep -oE '\-\-protocol [a-z0-9]+' /etc/systemd/system/$svc.service | head -1) conns=$(grep -oE '\-\-ha-connections [0-9]+' /etc/systemd/system/$svc.service | head -1) printf " %-26s PID=%-7s 内存=%s MB %s %s 状态=%s\n" \ "$svc" "$pid" "$(( ${mem:-0} / 1048576 ))" "$proto" "$conns" \ "$(systemctl is-active $svc)" done echo echo " ⚠ 两个实例都想占 20241 metrics 端口,后起的会失败 —— 这是正常的。" echo " 实际连接数看 cloudflared 日志里的 Registered 行。" echo " ---" echo " 主实例(QUIC)最近注册:" tail -60 /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection' | sed 's/^/ /' echo " 副本实例(HTTP/2)最近注册:" tail -60 /root/lanremote-relay/tunnel-h2.log 2>/dev/null | grep -c 'Registered tunnel connection' | sed 's/^/ /' echo echo "════════ 6. 等公网可用 ════════" for i in $(seq 1 25); do c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 12 -H 'User-Agent: LanRemote/1.8.2' https://relay.xxmxxs.top/v1/health) printf " 第 %d 次: %s\n" "$i" "$c" [ "$c" = "200" ] && break sleep 3 done echo echo "════════ 7. 快速验收(40 次)════════" ok=0; times=() for i in $(seq 1 40); do out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 -H 'User-Agent: LanRemote/1.8.2' https://relay.xxmxxs.top/v1/health) c=$(echo "$out" | awk '{print $1}'); ms=$(echo "$out" | awk '{printf "%d", $2*1000}') [ "$c" = "200" ] && { ok=$((ok+1)); times+=("$ms"); } done stats=$(printf '%s\n' "${times[@]:-0}" | sort -n | awk '{a[NR]=$1} END {printf "中位 %d P90 %d P99 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}') echo " 可用率 $ok/40 = $((ok*100/40))% $stats ms"