#!/bin/bash # TCP 重传调优的受控 A/B。 # # 背景(实测): # TCPTimeouts: 3145 · TCPLostRetransmit: 2010 # 这些多秒/多分钟的尾巴全是 TCP 重传超时造成的。 # 这台机器内核是定制的,**没有 BBR**(只有 reno/cubic), # 也没有任何替代 qdisc(只有 noqueue),所以只能在 # 现有 cubic 上调 TCP 的重传行为。 # # 三个候选开关(都对症"细流 + 高丢包"这个场景): # tcp_thin_linear_timeouts 细流不搞 RTO 指数退避,重传来得更快 # tcp_fack 前向确认,丢包恢复更积极 # tcp_retries2 重传几次就放弃(8 太大,卡十几分钟) # # 每轮:设参数 -> 打 60 次 HTTP -> 记录成功率/耗时分布 -> 下一轮 # 全部跑完回滚,最后按数据决定采用哪组。 set -u BASE=https://relay.xxmxxs.top UA='LanRemote/1.8.1' N=60 # 保存原始值,跑完恢复 declare -A ORIG for k in tcp_thin_linear_timeouts tcp_fack tcp_retries2 tcp_autocorking; do ORIG[$k]=$(sysctl -n net.ipv4.$k 2>/dev/null || echo "") done restore() { for k in "${!ORIG[@]}"; do [ -n "${ORIG[$k]}" ] && sysctl -qw net.ipv4.$k="${ORIG[$k]}" 2>/dev/null || true done } measure() { local label="$1" local ok=0 fail=0 times=() for i in $(seq 1 "$N"); do out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \ -H "User-Agent: $UA" "$BASE/v1/health") c=$(echo "$out" | awk '{print $1}') ms=$(echo "$out" | awk '{printf "%d", $2*1000}') if [ "$c" = "200" ]; then ok=$((ok+1)); times+=("$ms"); else fail=$((fail+1)); fi done local pct=$((ok * 100 / N)) local stats="无数据" if [ "${#times[@]}" -gt 0 ]; then stats=$(printf '%s\n' "${times[@]}" | 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]}') fi printf " %-34s %3d%% (%d/%d) %s ms\n" "$label" "$pct" "$ok" "$N" "$stats" echo "$label|$pct|$ok|$stats" >> /tmp/tcpab.txt } rm -f /tmp/tcpab.txt echo "════════ 原始值 ════════" for k in "${!ORIG[@]}"; do printf " %-30s = %s\n" "$k" "${ORIG[$k]}"; done echo echo "════════ 各组合实测(每轮 $N 次)════════" echo " [1/6] 基线(不改)" measure "基线" echo " [2/6] thin_linear_timeouts=1" sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1 measure "thin=1" echo " [3/6] thin=1 + fack=1" sysctl -qw net.ipv4.tcp_fack=1 measure "thin=1 fack=1" echo " [4/6] thin=1 fack=1 + retries2=5" sysctl -qw net.ipv4.tcp_retries2=5 measure "thin=1 fack=1 retries2=5" echo " [5/6] thin=1 fack=1 retries2=5 + autocorking=0" sysctl -qw net.ipv4.tcp_autocorking=0 measure "全部开启" echo " [6/6] 回滚到基线再测一次(排除时间漂移)" restore measure "回滚后基线" echo echo "════════ 汇总(按可用率排序)════════" sort -t'|' -k2 -rn /tmp/tcpab.txt | while IFS='|' read -r label pct ok stats; do printf " %-34s %3s%% %s\n" "$label" "$pct" "$stats" done echo echo "════════ 按数据决定 ════════" BEST=$(sort -t'|' -k2 -rn /tmp/tcpab.txt | head -1) BEST_LABEL=$(echo "$BEST" | cut -d'|' -f1) BEST_PCT=$(echo "$BEST" | cut -d'|' -f2) echo " 最好的是: $BEST_LABEL ($BEST_PCT%)" case "$BEST_LABEL" in "基线"|"回滚后基线") echo " → 调优没有收益,保持默认(只留已验证过的 keepalive 那几项)" restore ;; "thin=1") sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1 echo " → 只启用 thin_linear_timeouts" ;; "thin=1 fack=1") sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1 sysctl -qw net.ipv4.tcp_fack=1 echo " → 启用 thin_linear_timeouts + fack" ;; *) sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1 sysctl -qw net.ipv4.tcp_fack=1 sysctl -qw net.ipv4.tcp_retries2=5 sysctl -qw net.ipv4.tcp_autocorking=0 echo " → 全部启用" ;; esac echo echo " 当前生效值:" for k in tcp_thin_linear_timeouts tcp_fack tcp_retries2 tcp_autocorking; do printf " %-30s = %s\n" "$k" "$(sysctl -n net.ipv4.$k 2>/dev/null)" done