OpenVPN 流量计数器与磁盘 I/O:三个不同的数字
接口字节、进程字符计数器与存储写入衡量的是不同的工作。在按隧道流量估算磁盘之前,先把它们区分开。
让一个大文件穿过隧道,接口计数器会按文件大小增长。这个数字并不意味着同样的数据量落到了磁盘上。这类讨论里常被拿来比较的是三种不同的测量,它们回答三个不同的问题:多少流量经过网络层、进程通过系统调用读写多少字节,以及多少工作到达存储层。
把第一种和第三种放在一起比较是常见的错误,结果要么是一个并不存在的磁盘问题,要么是一份忽略了真实写入的容量规划。三者可以分开,各自都有正确的用法。
三个被混为一谈的数字
接口计数器描述的是网络层处理的流量。带宽、隧道规模以及按流量计费的计费模型,都应该看这个数字。
进程字符计数器描述的是通过任意描述符的读、写调用所搬运的字节,包括管道、终端和字符设备。要问进程在系统调用边界上有多忙,看这个数字;它不是存储账单。
存储计数器描述的是内核试图写入或读出块设备的字节。做容量规划要看这个数字,但要注意内核自己也记录了统计规则的边界:命中缓存的读取不会变成存储读取,之后被取消的写入也不会变成已完成的设备写入。
| Measurement | Question it answers |
|---|---|
| Interface bytes | How much traffic crossed this layer |
rchar and wchar | How much data the process moved through system calls |
read_bytes and write_bytes | How much work reached the storage layer |
为什么文件描述符不是磁盘写入
用户态的 VPN 通过 TUN 设备与内核交换数据包,而 TUN 是字符设备,不是普通文件。从它读取会交出一个数据包,向它写入会注入一个数据包。这里没有为存储准备的文件内容页,也不涉及任何文件系统路径。
所以繁忙的隧道可以显示巨大的字符计数器,而存储计数器几乎不动。进程确实搬运了这些字节,内核也确实不需要磁盘来完成这件事。
cat /proc/$(pgrep -n openvpn)/io
ip -s link show tun0
第一个输出要当成进程启动以来的累计值来读,而不是速率。第二个给出同一份流量的接口视角。两者出现很大差距是正常的,并不说明泄漏、队列或者配置错误。
这些字节到底来自哪里
持续输出有自己的预算,推动它的是操作而不是载荷。啰嗦的日志级别、频繁变化的客户端列表,或者在一个紧凑循环里反复出现的错误,都能在隧道几乎不传数据时产生可观的写入。
状态文件是按间隔重写的快照,所以它的开销取决于间隔以及其中记录的数量。它不是每个客户端流量的实时副本,缩短间隔会改变写入量,却不改变流量。
追查这些写入时,归属很重要。如果服务把消息交给日志守护进程,日志文件是由那个守护进程写的,字节会出现在守护进程的账上,而不是 VPN 进程自己的计数器里。只看 VPN 进程,既解释不了磁盘活动,也解释不了它为什么不活动。
其他来源很容易被忽略:启动时读取证书和配置、写入自身记录的钩子、计费插件、崩溃转储,以及同一台主机上无关的负载。这些都不该用“隧道流量等于写入字节”的方式去估计。
正确读每一个计数器
有三个细节决定比较是否有意义。进程身份很重要,因为重启的服务会把计数器清零。时间间隔很重要,因为累计数字和每秒图表是两种测量。单位也很重要,因为日志文件的当前大小同样不是速率。
grep -E 'rchar|wchar|read_bytes|write_bytes' /proc/$(pgrep -n openvpn)/io
du -sh /var/log/openvpn 2>/dev/null
journalctl --disk-usage
如果怀疑确实存在载荷写入,就去找那些职责就是存储载荷的组件:抓包工具、缓存正文的代理、做归档的插件。它们产生的存储流量与要求记录的量成正比,属于另一项关于留存与访问的策略决定。
用正确的数字做容量规划
用网络指标来估算数据包路径:吞吐、加解密处理与内存搬运。用你实际做出的可观测性选择来估算存储:日志级别、留存期、状态间隔,以及任何被有意开启的抓包。
把这两份预算分开,也让事故更好读。隧道流量不大而磁盘满了,指向日志、抓包或者别的无关原因,接口计数器永远不会解释它。链路跑满而日志安静,则指向相反的方向。
值得做的比较,是同类测量之间的比较。接口字节与随时间的接口字节对比,说明路径情况。存储写入与存储容量对比,说明留存情况。跨类比较,也就是流量与磁盘对比,看着有意义,其实从来没有。