网络调优要同时优化有意义的工作量和用户等待,而不是只把某个测速数字推高。先定义目标:大文件有效吞吐、短请求 P99、连接建立速率还是小包转发 PPS;不同目标可能需要不同批次、队列和并发策略。
| 指标 | 必须附带的口径 |
| 吞吐 | 应用 payload、IP 字节还是链路速率;单向还是双向 |
| 延迟 | 空闲/负载状态、连接是否复用、P50/P95/P99 与错误率 |
| PPS | 包长、二层开销、协议、是否经过转发/加密 |
| CPU | 单核和队列分布、软中断、用户态及系统态 |
| 资源 | 并发连接、缓冲、队列、内存压力和丢弃计数 |
先确认瓶颈属于哪种限制
带宽跑不满可能是单流窗口、拥塞、应用供给、CPU、加密、存储、限速或接收处理等原因。cwnd 小并不自动证明 socket buffer 小;发送应用没有持续供给数据时,扩大窗口也不会凭空产生负载。
| 观察 | 下一步验证 | 暂不该直接得出的结论 |
| 单流慢,多流快 | 单流窗口、CPU/队列、限速与路径哈希 | 多流已修复所有性能问题 |
| 发送队列长期有未发送数据 | 接收窗口、拥塞许可、pacing 与应用背压 | 只需增加 send buffer |
| 负载升高时 RTT 大涨 | 队列、无线竞争、CPU 调度与接收处理 | 一定是某台路由器 bufferbloat |
| 某核打满,整机平均低 | RSS/IRQ/应用线程与 NUMA | CPU 不是瓶颈 |
| 接收端 ZeroWindow | 应用读取与缓冲释放速度 | 发送端换拥塞算法即可解决 |
BDP:估计需要多少在途额度
带宽时延积 BDP = 速率 × RTT 表示以该速率持续发送时,覆盖一个往返等待所需的在途数据量。它是容量规划起点,不是对每个 socket 预分配内存的命令。
| 目标速率与 RTT | BDP(十进制) | 约合二进制容量 |
| 1 Gbit/s,30 ms | 3.75 MB | 3.58 MiB |
| 1 Gbit/s,150 ms | 18.75 MB | 17.88 MiB |
| 10 Gbit/s,200 ms | 250 MB | 238.42 MiB |
将 cwnd、接收窗口和在途数据统一为字节后,可以估计某一固定窗口的速率上界。例如可用窗口 1 MiB、RTT 100 ms,即使不丢包,理想上界也只有约 83.9 Mbit/s;慢启动、pacing、应用供给等还会进一步影响结果。ss 的 cwnd 常以段数展示,需结合 MSS,不能直接和字节单位 buffer 比较。
from fractions import Fraction as F
bdp = 1_000_000_000 * F(30, 1000) / 8
assert bdp == 3_750_000
window_limit = 1_048_576 * 8 / F(100, 1000)
assert window_limit == F(83_886_080)
queue_ms = 4_000_000 * 8 / F(100_000_000) * 1000
assert queue_ms == 320
print('BDP 3.75 MB;1 MiB 窗口上界 83.88608 Mbit/s;积压延迟 320 ms')
若使用已经包含严重排队的 RTT 来扩大 buffer,可能把排队当成“必须继续填充的管道”。应结合基线/最小 RTT 与实际竞争条件,而非永远按最大 RTT 乘带宽。
更多在途量,什么时候只是在增加排队?
默认1 Gbit/s、30ms时,3.75MB恰好覆盖基线BDP。继续增加在途预算,金色排队变长但吞吐上界不再提高;减到BDP以下,队列消失而窗口开始限制吞吐。
BDP=CRTT0,R≤min(C,W/RTT0),dq=max(0,W−BDP)/C
正在呈现知识画面
更多在途量,什么时候只是在增加排队?达到基线BDP后,额外持续在途量在这个单瓶颈模型中形成队列,不能突破出口速率。用排队RTT再次扩大预算会追逐自己制造的等待。
{"id":"bdp-window-queue","title":"更多在途量,什么时候只是在增加排队?","summary":"达到基线BDP后,额外持续在途量在这个单瓶颈模型中形成队列,不能突破出口速率。用排队RTT再次扩大预算会追逐自己制造的等待。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"rate\" data-i18n=\"rate\"></label><output for=\"rate\" id=\"rate-value\"></output><input id=\"rate\" type=\"range\" min=\"100\" max=\"1000\" value=\"1000\" step=\"100\"></div><div class=\"control\"><label for=\"rtt\" data-i18n=\"rtt\"></label><output for=\"rtt\" id=\"rtt-value\"></output><input id=\"rtt\" type=\"range\" min=\"10\" max=\"200\" value=\"30\" step=\"10\"></div><div class=\"control\"><label for=\"flight\" data-i18n=\"flight\"></label><output for=\"flight\" id=\"flight-value\"></output><input id=\"flight\" type=\"range\" min=\"0.25\" max=\"30\" value=\"3.75\" step=\"0.25\"></div><div id=\"pipe\" class=\"budget\"></div><div class=\"metrics\"><div><span data-i18n=\"bdp\"></span><strong id=\"bdp\"></strong></div><div><span data-i18n=\"throughput\"></span><strong id=\"throughput\"></strong></div><div><span data-i18n=\"queue\"></span><strong id=\"queue\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.metrics strong{font-size:18px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const rate=+$('#rate').value,rtt=+$('#rtt').value,f=+$('#flight').value,bdp=rate*rtt/8000,q=Math.max(0,f-bdp),speed=Math.min(rate,f*8000/rtt);for(const id of ['rate','rtt','flight'])set(id+'-value',$('#'+id).value);band('pipe',[{name:'base pipe',value:Math.min(f,bdp)},{name:'queue',value:q}],Math.max(f,bdp));set('bdp',bdp.toFixed(2));set('throughput',speed.toFixed(1));set('queue',(q*8000/rate).toFixed(1));set('equation','BDP = '+rate+'×'+rtt+'/8000 = '+bdp.toFixed(2)+' MB\\nqueue = max(0,'+f+' − '+bdp.toFixed(2)+') = '+q.toFixed(2)+' MB');$('#queue').dataset.value=q*8000/rate;$('#throughput').dataset.value=speed;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"bdp":"基线BDP(MB)","flight":"在途预算(MB)","heading":"更多在途量,什么时候只是在增加排队?","intro":"改变瓶颈速率、基线RTT和在途预算,观察未填满、刚填满与超额排队。","queue":"新增排队延迟(ms)","rate":"瓶颈速率(Mbit/s)","reset":"重置","rtt":"基线 RTT(ms)","scope":"稳态流体模型,单流、固定速率、无丢包/ACK压缩/竞争,额度全部利用。MB按10^6 B,RTT不含此处新产生的队列。不是TCP拥塞算法仿真。","scopeLabel":"模型范围与约定","throughput":"理想吞吐上界(Mbit/s)"}}
Linux buffer:上限、实际分配和通告窗口不同
tcp_rmem 与 tcp_wmem 的三个值描述 min/default/max 的相关内存策略;接收自动调节还受 tcp_moderate_rcvbuf 等条件影响。应用显式设置 SO_RCVBUF/SO_SNDBUF 会改变相应自动调节行为,不能同时假定内核仍完全按自动上限增长。
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem \
net.ipv4.tcp_moderate_rcvbuf net.core.rmem_max net.core.wmem_max
ss -tinm
内核默认值可能随版本和内存变化,发行版还可能覆盖。不能以“Linux 默认接收最大约 6 MB”判断所有机器。Linux 7.2 文档明确区分自动调节上限与手动 socket 选项;tcp_wmem 的自动最大值也不覆盖 net.core.wmem_max。Linux IP Sysctl
Linux 对 SO_RCVBUF/SO_SNDBUF 参数的内部记账包含额外空间,getsockopt 常返回倍增后的数值;socket 内存占用、数据 payload 和 TCP 通告窗口不应视为同一个数字。socket(7)
把接收上限改为 64 MiB 并不表示每个连接立即占满 64 MiB;但 10,000 个活跃连接若平均实际占用 4 MiB,仅这一类内存就约 39.1 GiB,还没有算其他缓冲和元数据。调高上限应同时评估并发数、内存压力和是否真的触及原限制。
队列管理:只有控制到瓶颈才有意义
队列吸收短突发,也增加等待。100 Mbit/s 出口前的 4 MB 积压约增加 320 ms 序列化等待;无论把队列扩大多少,出口都不会因此变成 1 Gbit/s。持续输入超过输出时,需要拥塞反馈、整形或业务背压。
CoDel 观察包在队列中的驻留时间,并判断最小驻留时间是否在一个 interval 内持续高于 target,从而识别持久队列;并非任意一个包刚超过默认 5 ms 就立即丢弃。信号可以是丢弃,也可在适用条件下采用 ECN 标记。CoDel
FQ-CoDel 将流分到队列并调度,配合 CoDel 控制各队列中的持久等待。它可改善交互流与大流共存,但哈希分类、不同流数与实现条件意味着“绝对公平、永不饿死”不是通用承诺。FQ-CoDel
CAKE 将整形、队列管理、流/主机隔离和 DiffServ 等机制组合。它不是简单把 fq_codel 改个名字,也不是对所有接口必然最优。整形速率、链路层开销和实际瓶颈必须匹配;下行还需要能在适当 ingress/转发路径实施控制。tc-cake
| 场景 | 为什么本机改 qdisc 可能无效 |
| 1 Gbit/s 网卡后接 100 Mbit/s 不可控设备 | 本机发送太快,深队列仍留在下游 |
| 外部多个主机共用出口 | 本机看不到全部竞争流量 |
| 实际上行只有 100 Mbit/s,却按 950 Mbit/s 整形 | 没有把瓶颈移到可控位置 |
| 无线容量快速变化 | 固定整形值不一定匹配即时容量 |
先用 tc -s qdisc show dev eth0 观察现有结构,再决定在哪里调度或整形。更换 root qdisc 可能替换已有业务策略,操作前应记录并理解现有配置;调优文章不应直接给所有机器套一条 replace 命令。
CUBIC、BBR 与 pacing:算法名称不是性能结论
CUBIC 根据拥塞事件和窗口增长函数调整发送许可,RFC 9438 描述的是 CUBIC,不是 BBR。BBR 基于交付速率、RTT 等建立路径模型并控制 pacing/inflight;不同代际的丢包、ECN 和探测行为不同。CUBIC RFC 9438、BBR 官方项目
BBR 的启动和带宽探测可以制造队列,也不能保证始终优于 CUBIC。应报告确切内核/补丁版本、竞争流、RTT、丢包来源和测量目标。本地名为 bbr 的模块不自动意味着已部署 Google 仓库最新代际。
sysctl net.ipv4.tcp_available_congestion_control \
net.ipv4.tcp_congestion_control
tc -s qdisc show dev eth0
全局默认拥塞算法主要影响后续相应连接,应用也可能逐 socket 选择算法;改一次 sysctl 不会证明所有现存连接已切换。发送 pacing 与 qdisc/内核实现协作,fq、fq_codel 和 cake 不是同一机制,不能仅因名字含 fq 就认为它们的能力完全相同。tc-fq
小包:先算 PPS,再看 CPU 分布
理想 10 Gbit/s Ethernet 最小帧线速,按 64 B 帧(含 FCS)、8 B 前导码/SFD 与 12 B 帧间隙计算,每帧占 84 B 时间,约 14.88 Mpps。它与“10 Gbit/s 的大文件下载”给 CPU 的压力完全不同。
from fractions import Fraction as F
small_pps = F(10_000_000_000, (64 + 8 + 12) * 8)
large_pps = F(10_000_000_000, (1518 + 8 + 12) * 8)
assert 14_880_000 < small_pps < 14_890_000
assert 812_000 < large_pps < 813_000
assert 18 < small_pps / large_pps < 19
assert F(10_000 * 4 * 1024**2, 1024**3) == F(625, 16)
print('理想最小帧 14.88 Mpps,1518 B 帧 0.813 Mpps,平均缓冲约 39.06 GiB')
此计算忽略更高层物理编码等其他速率口径差异,且不表示当前主机能达到该 PPS。高包速下不应假定每包都触发一次独立中断;NAPI、中断合并和批处理会改变 CPU 成本。
| 机制 | 主要作用 | 取舍 |
| TSO | 硬件执行 TCP 分段 | 减少软件逐段工作,受硬件和报文条件约束 |
| GSO | 在软件栈中延后分段 | 不是把所有工作都搬到 NIC |
| GRO | 接收路径合并适用报文 | 改变上层处理粒度和抓包观察形态 |
| RSS | 硬件将流散列到接收队列 | 单流通常仍映射到一个队列,避免乱序 |
| RPS/RFS/XPS | 软件 CPU/队列选择与局部性优化 | 可能增加跨核传递,不是核数越多越好 |
| Ring / interrupt coalescing | 缓冲突发、减少中断频率 | 更深缓冲或更多合并可增加等待 |
按 CPU、IRQ、队列和 NUMA 位置查看负载,通常比先把所有 ring 调到最大更有意义。卸载问题也应先对照抓包位置与驱动统计;关闭卸载会改变 CPU 与吞吐,必须作为明确假设下的对照。Linux Scaling、Segmentation Offloads
常见参数应该对应什么证据
| 参数或机制 | 合理的调查入口 | 不能替代什么 |
| somaxconn / listen backlog | accept 队列积压、溢出与应用 accept 速度 | 增加实际业务处理能力 |
| tcp_max_syn_backlog | 未完成握手队列压力及相关计数 | 与已完成队列混为一谈 |
| TCP_NOTSENT_LOWAT | 未发送数据过多、应用可写通知和背压 | 精确限制线上在途字节 |
| TCP Fast Open | 支持 TFO 的应用、握手成本与重放语义 | 单改 tcp_fastopen=3 就让所有应用生效 |
| tcp_tw_reuse | 真实源端口/连接复用需求及协议条件 | TIME_WAIT 越少越好 |
Linux 7.2 的 tcp_tw_reuse 区分 0 禁用、1 全局启用和 2 仅 loopback,不能只给“设置 1、客户端安全”的结论。先检查连接复用、目的端分布、源端口约束和业务生命周期;删除大量 TIME_WAIT 并非优化目标。Linux IP Sysctl 参数
让测试回答一个具体问题
以下命令用于已有授权测试服务的环境;iperf3 的 server 需由测试双方事先准备。
iperf3 -c perf.example -t 30 -P 1 -J
iperf3 -c perf.example -t 30 -P 1 -R -J
iperf3 -c perf.example -t 30 -P 4 -J
-R 只反转方向;同时双向是另一个测试条件。iperf3 测得的是工具与端点在该条件下的吞吐,不是脱离 CPU、内核和 socket 的“裸网络带宽”。UDP 测试的发送目标速率、接收端 goodput、丢失和乱序也应分别报告。iperf3 官方手册
一次对照至少记录测试版本、方向、流数、持续时间、包长或块大小、两端 CPU、吞吐、负载下 RTT/尾延迟与错误率。为避免外部负载变化误导,可交替运行基线和候选配置。若吞吐提高 5%,但 P99 翻倍或错误率上升,结果应按原目标评价,而不是只报告最快的一次。
具体证据采集见tcpdump 与 Wireshark,阶段定位见网络故障诊断方法论。每项变更都应能对应一个已观察到的限制,并以相同工作负载验证其改善与代价。
从测量进入排队模型
若吞吐接近上限时延迟快速增长,先按排队论的测量与仿真固定请求口径、并发模型与测量窗口,再用Little 定律核对平均在途数量、吞吐与平均逗留时间。均值关系不能直接推出 p99;调参前先区分处理变慢、排队变长与客户端压测方式改变。