性能指标定义:吞吐量、延迟、并发连接数、CPU/内存占用率的测量方法
做网络编程的人,最怕什么?
怕系统上线后,用户一多就崩。怕明明本地压测没问题,生产环境却慢得像蜗牛。
我见过太多团队,代码写得花里胡哨,但一问性能指标怎么测,全懵了。说白了,没有度量就没有优化。你连当前系统跑多快、瓶颈在哪都不知道,谈什么调优?
这一章,咱们就把四个最核心的性能指标讲透。怎么定义,怎么测,测的时候要注意什么坑。
核心观点:性能调优的第一步,不是改代码,而是建立可复现的测量体系。没有基线数据,一切优化都是瞎蒙。
吞吐量:系统到底能扛多少
吞吐量,说白了就是单位时间内系统能处理多少活儿。对于 Socket 编程来说,通常有两种口径:
- 请求吞吐量:每秒处理多少个请求(QPS/TPS)
- 数据吞吐量:每秒传输多少字节(带宽利用率)
我个人习惯用 wrk 或 ab 做 HTTP 层面的吞吐量测试。但如果是自定义 TCP 协议,我会写一个简单的压测客户端。
我的经验:吞吐量不是越高越好。我曾经遇到一个项目,QPS 冲到 5 万,但延迟从 2ms 飙到了 200ms。这就是典型的「虚假吞吐量」——系统在拼命处理请求,但每个请求都变慢了。所以吞吐量必须和延迟一起看。
测量吞吐量的核心代码片段:
import time
import socket
def measure_throughput(host, port, num_requests=10000):
"""测量 TCP 请求吞吐量"""
start = time.perf_counter()
success = 0
for i in range(num_requests):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect((host, port))
sock.sendall(b"ping")
resp = sock.recv(1024)
sock.close()
success += 1
except Exception as e:
pass
elapsed = time.perf_counter() - start
qps = success / elapsed
print(f"吞吐量: {qps:.2f} QPS")
return qps
注意:压测时一定要考虑「冷启动」效应。刚启动的服务,JIT 还没预热、连接池还没填满,前几百个请求的数据不能算数。我一般会先发 1000 个请求做预热,再开始正式记录。
延迟:用户感知到的「快」与「慢」
延迟就是一次请求从发出到收到响应所花的时间。对于 Socket 编程,延迟通常包含:
- 网络传输延迟(物理链路 + 路由跳数)
- 服务端处理延迟(业务逻辑 + 线程调度)
- 排队延迟(请求在队列里等着被处理)
你想想看,用户说「卡」,其实说的就是延迟高。但延迟不能只看平均值,我见过太多人拿平均延迟说事,结果 P99 延迟高得离谱。
测量延迟时,我习惯记录这几个分位值:
| 指标 | 含义 | 典型阈值(内部服务) |
|---|---|---|
| 平均延迟 | 所有请求延迟的算术平均 | < 50ms |
| P50(中位数) | 一半请求比这个值快 | < 20ms |
| P99 | 99% 的请求比这个值快 | < 200ms |
| P999 | 99.9% 的请求比这个值快 | < 500ms |
避坑指南:我曾经优化过一个网关服务,平均延迟从 100ms 降到了 30ms,大家都觉得稳了。结果上线后用户投诉不断。一查 P99 延迟,好家伙,800ms。原因是某个第三方接口偶尔超时,拖慢了整个尾部延迟。从那以后,我每次压测必看 P99。
测量延迟的 Python 代码:
import time
import numpy as np
def measure_latency(host, port, num_samples=1000):
"""测量延迟分布"""
latencies = []
for _ in range(num_samples):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
t0 = time.perf_counter_ns()
sock.connect((host, port))
sock.sendall(b"ping")
sock.recv(1024)
t1 = time.perf_counter_ns()
latencies.append((t1 - t0) / 1_000_000) # 转为毫秒
sock.close()
latencies.sort()
print(f"P50: {np.percentile(latencies, 50):.2f} ms")
print(f"P99: {np.percentile(latencies, 99):.2f} ms")
print(f"P999: {np.percentile(latencies, 99.9):.2f} ms")
并发连接数:系统能同时伺候多少大爷
并发连接数,就是同一时刻系统保持的活跃 TCP 连接数量。这个指标特别容易和吞吐量搞混。
举个例子:一个服务器有 1 万个并发连接,但每个连接每秒只发一个请求,那吞吐量可能只有 1 万 QPS。另一个服务器只有 100 个并发连接,但每个连接每秒发 1000 个请求,吞吐量能到 10 万 QPS。你想想看,这两个场景的优化方向完全不同。
测量并发连接数,我一般用 ss 命令或者 /proc/net/tcp:
import subprocess
import re
def count_connections(port):
"""统计指定端口的 TCP 连接数"""
result = subprocess.run(
["ss", "-tan", "state", "established"],
capture_output=True, text=True
)
lines = result.stdout.strip().split("\n")[1:] # 跳过表头
count = 0
for line in lines:
if f":{port}" in line:
count += 1
return count
# 每秒采样一次
import time
for i in range(10):
conns = count_connections(8080)
print(f"第 {i+1} 秒: {conns} 个并发连接")
time.sleep(1)
我的习惯:并发连接数要配合「连接状态分布」一起看。如果大量连接处于 TIME_WAIT 状态,说明客户端频繁创建短连接,这时候应该考虑连接池。如果大量 CLOSE_WAIT,说明服务端没正确关闭连接——嗯,代码有 bug。
CPU/内存占用率:资源到底吃在哪
这个指标最直观,但也最容易误判。CPU 占用率高不一定是坏事,说明系统在干活。内存占用率高也不一定是坏事,可能是在做缓存。
关键是要看:资源用在了哪里。
我常用的测量工具组合:
top/htop:全局概览,看 CPU 和内存总量pidstat -p [PID] 1:按进程看 CPU 使用率psutil(Python 库):在代码里嵌入监控
用 Python 做进程级资源监控:
import psutil
import os
def monitor_process(pid=None, duration=10):
"""监控指定进程的 CPU 和内存"""
if pid is None:
pid = os.getpid()
proc = psutil.Process(pid)
print(f"监控进程 PID: {pid}")
for i in range(duration):
cpu_percent = proc.cpu_percent(interval=1)
mem_info = proc.memory_info()
mem_mb = mem_info.rss / 1024 / 1024
print(f"第 {i+1}s | CPU: {cpu_percent:.1f}% | 内存: {mem_mb:.1f} MB")
# 检查是否有子进程
children = proc.children()
if children:
for child in children:
child_cpu = child.cpu_percent(interval=0)
child_mem = child.memory_info().rss / 1024 / 1024
print(f" 子进程 {child.pid}: CPU {child_cpu:.1f}% | 内存 {child_mem:.1f} MB")
注意:Python 的 psutil.cpu_percent() 第一次调用会返回 0.0,因为它需要两次采样才能计算差值。我刚开始用的时候被这个坑过,还以为 CPU 没负载呢。
四个指标怎么联动分析
单独看任何一个指标都没意义。我总结了一个「四维诊断法」:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 吞吐量低 + CPU 高 | 业务逻辑太重,或序列化/反序列化开销大 | 优化算法,改用更高效的序列化协议 |
| 吞吐量低 + CPU 低 | IO 阻塞,网络带宽瓶颈,或锁竞争 | 检查 IO 模型,改用非阻塞/异步 |
| 延迟高 + 内存高 | GC 频繁,或内存泄漏导致频繁 swap | 分析堆内存,检查对象生命周期 |
| 并发连接数高 + 响应慢 | 线程/进程数不够,或连接池配置不合理 | 调整线程模型,增加 worker 数量 |
我的经验:有一次线上告警,说吞吐量掉了一半。我一看 CPU 才 20%,内存也正常,但并发连接数从 2000 降到了 800。奇怪吧?最后发现是上游服务改了超时时间,客户端大量连接在等待响应,实际上没干活。这就是典型的「虚假并发」——连接数看着多,但都在等,没一个在真正处理请求。
所以你看,这四个指标必须放在一起看。单独看任何一个,都可能被表象骗了。
嗯,这一章的内容就到这。测量方法有了,下一件事就是搭一套自动化的压测脚本,把这些指标跑起来,拿到基线数据。有了基线,你才知道优化到底有没有效果。