性能指标定义:吞吐量、延迟、并发连接数、CPU/内存占用率的测量方法

做网络编程的人,最怕什么?

怕系统上线后,用户一多就崩。怕明明本地压测没问题,生产环境却慢得像蜗牛。

我见过太多团队,代码写得花里胡哨,但一问性能指标怎么测,全懵了。说白了,没有度量就没有优化。你连当前系统跑多快、瓶颈在哪都不知道,谈什么调优?

这一章,咱们就把四个最核心的性能指标讲透。怎么定义,怎么测,测的时候要注意什么坑。

核心观点:性能调优的第一步,不是改代码,而是建立可复现的测量体系。没有基线数据,一切优化都是瞎蒙。

Socket 性能指标测量体系 性能测量体系 吞吐量 延迟 并发连接数 CPU/内存占用 统计单位时间 请求/字节数 测量请求往返 时间(RTT) 统计同时活跃 TCP 连接数 top/pidstat /psutil 四维联动分析,定位系统瓶颈

吞吐量:系统到底能扛多少

吞吐量,说白了就是单位时间内系统能处理多少活儿。对于 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。奇怪吧?最后发现是上游服务改了超时时间,客户端大量连接在等待响应,实际上没干活。这就是典型的「虚假并发」——连接数看着多,但都在等,没一个在真正处理请求。

所以你看,这四个指标必须放在一起看。单独看任何一个,都可能被表象骗了。

嗯,这一章的内容就到这。测量方法有了,下一件事就是搭一套自动化的压测脚本,把这些指标跑起来,拿到基线数据。有了基线,你才知道优化到底有没有效果。