TL;DR 版本
观看这个 ASCII 转换或者这个视频- 然后找出发生这种情况的任何原因。下面的文字描述提供了更多上下文。
设置详情
- 机器 1 是一台 Arch Linux 笔记本电脑,在其上
ssh
生成,连接到运行 Armbian 的 SBC(Orange PI Zero)。 - SBC 本身通过以太网连接到 DSL 路由器,IP 为 192.168.1.150
- 笔记本电脑通过 WiFi 连接到路由器 - 使用官方 Raspberry PI WiFi 适配器。
- 还有另一台笔记本电脑(机器 2)通过以太网连接到 DSL 路由器。
使用 iperf3 对链路进行基准测试
当使用 进行基准测试时iperf3
,笔记本电脑和 SBC 之间的链接低于理论上的 56 MBits/秒 - 正如预期的那样,因为这是非常“拥挤的 2.4GHz”内的 WiFi 连接(公寓楼)。
更具体地说:iperf3 -s
在SBC上运行后,在笔记本电脑上执行以下命令:
# iperf3 -c 192.168.1.150
Connecting to host 192.168.1.150, port 5201
[ 5] local 192.168.1.89 port 57954 connected to 192.168.1.150 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 2.99 MBytes 25.1 Mbits/sec 0 112 KBytes
...
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 28.0 MBytes 23.5 Mbits/sec 5 sender
[ 5] 0.00-10.00 sec 27.8 MBytes 23.4 Mbits/sec receiver
iperf Done.
# iperf3 -c 192.168.1.150 -R
Connecting to host 192.168.1.150, port 5201
Reverse mode, remote host 192.168.1.150 is sending
[ 5] local 192.168.1.89 port 57960 connected to 192.168.1.150 port 5201
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 3.43 MBytes 28.7 Mbits/sec
...
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 39.2 MBytes 32.9 Mbits/sec 375 sender
[ 5] 0.00-10.00 sec 37.7 MBytes 31.6 Mbits/sec receiver
所以基本上,上传到 SBC 的速度约为 24MBits/秒,从 SBC 下载 ( -R
) 的速度达到 32MBits/秒。
使用 SSH 进行基准测试
鉴于此,让我们看看 SSH 的表现如何。我第一次遇到导致这篇文章出现的问题是在使用rsync
and时borgbackup
- 它们都使用 SSH 作为传输层...所以让我们看看 SSH 在同一链路上的执行情况:
# cat /dev/urandom | \
pv -ptebar | \
ssh [email protected] 'cat >/dev/null'
20.3MiB 0:00:52 [ 315KiB/s] [ 394KiB/s]
嗯,这速度真是逆天了!比预期的链接速度慢得多...
(如果您不知道pv -ptevar
:它显示通过它的当前和平均数据速率。在这种情况下,我们看到通过/dev/urandom
SSH 读取数据并将数据发送到 SBC 的平均速度达到 400KB/s - 即 3.2 MBits/秒,远低于预期的 24MBits/秒。)
为什么我们的链路仅以 13% 的容量运行?
也许是我们/dev/urandom
的错?
# cat /dev/urandom | pv -ptebar > /dev/null
834MiB 0:00:04 [ 216MiB/s] [ 208MiB/s]
不,绝对不是。
也许是 SBC 本身?也许处理速度太慢?让我们尝试运行相同的 SSH 命令(即将数据发送到 SBC),但这次是从通过以太网连接的另一台机器(机器 2)运行:
# cat /dev/urandom | \
pv -ptebar | \
ssh [email protected] 'cat >/dev/null'
240MiB 0:00:31 [10.7MiB/s] [7.69MiB/s]
不,这工作得很好 - SBC 上的 SSH 守护进程可以(轻松)处理以太网链路提供的 11MBytes/sec(即 100MBits/sec)。
执行此操作时,SBC 的 CPU 是否已加载?
没有。
所以...
- 网络方面(根据
iperf3
)我们应该能够实现 10 倍的速度 - 我们的CPU可以轻松适应负载
- ...并且我们不涉及任何其他类型的 I/O(例如驱动器)。
到底发生了什么?
Netcat 和 ProxyCommand 来救援
让我们尝试一下普通的旧netcat
连接 - 它们运行得像我们期望的那么快吗?
在 SBC 中:
# nc -l -p 9988 | pv -ptebar > /dev/null
在笔记本电脑中:
# cat /dev/urandom | pv -ptebar | nc 192.168.1.150 9988
117MiB 0:00:33 [3.82MiB/s] [3.57MiB/s]
有用!并以预期的速度运行 - 好得多,好 10 倍。
那么如果我使用 ProxyCommand 运行 SSH 来使用 nc 会发生什么?
# cat /dev/urandom | \
pv -ptebar | \
ssh -o "Proxycommand nc %h %p" [email protected] 'cat >/dev/null'
101MiB 0:00:30 [3.38MiB/s] [3.33MiB/s]
作品! 10 倍速度。
现在我有点困惑 - 当使用“naked”nc
作为 时Proxycommand
,你基本上不是在做与 SSH 完全相同的事情吗?即创建一个套接字,连接到 SBC 的端口 22,然后在其上添加 SSH 协议?
为什么最终的速度会有如此巨大的差异?
PS 这不是一个学术练习-borg
因此,我的备份运行速度提高了 10 倍。我只是不知道为什么:-)
编辑:添加了过程的“视频”这里。计算从 ifconfig 的输出发送的数据包,很明显,在这两个测试中,我们发送了 40MB 的数据,以大约 30K 数据包的形式传输它们 - 只是在不使用时速度要慢得多ProxyCommand
。
答案1
非常感谢在评论中提出想法的人们。我经历了所有这些:
使用tcpdump记录数据包并在WireShark中比较内容
# tcpdump -i wlan0 -w good.ssh & \
cat signature | ssh -o "ProxyCommand nc %h %p" \
[email protected] 'cat | md5sum' ; \
killall tcpdump
# tcpdump -i wlan0 -w bad.ssh & \
cat signature | ssh [email protected] 'cat | md5sum' ; \
killall tcpdump
记录的数据包没有任何重要性的差异。
检查流量整形
对此一无所知 - 但在查看“tc”联机帮助页后,我能够验证这一点
tc filter show
什么也不返回tc class show
什么也不返回tc qdisc show
...返回这些:
qdisc noqueue 0: dev lo root refcnt 2
qdisc noqueue 0: dev docker0 root refcnt 2
qdisc fq_codel 0: dev wlan0 root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn
...这似乎没有区分“ssh”和“nc” - 事实上,我什至不确定流量整形是否可以在进程级别上运行(我希望它可以在地址/端口/差异化上运行) IP 标头中的服务字段)。
Debian Chroot,以避免 Arch Linux SSH 客户端中潜在的“聪明”
不,结果相同。
最后——内格尔
在发送方中执行 strace...
pv data | strace -T -ttt -f ssh 192.168.1.150 'cat | md5sum' 2>bad.log
...并查看传输数据的套接字上到底发生了什么,我在实际传输开始之前注意到了这个“设置”:
1522665534.007805 getsockopt(3, SOL_TCP, TCP_NODELAY, [0], [4]) = 0 <0.000025>
1522665534.007899 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000021>
这会设置 SSH 套接字以禁用 Nagle 算法。你可以谷歌并阅读所有相关内容 - 但它的意思是,SSH 优先考虑响应能力而不是带宽 - 它指示内核立即传输在此套接字上写入的任何内容,而不是“延迟”等待来自远程的确认。
简单来说,这意味着在默认配置下,SSH 不是一种传输数据的好方法 - 当使用的链路速度较慢时(许多 WiFi 链路都是这种情况)。如果我们通过空中发送“主要是标头”的数据包,那么带宽就会被浪费!
为了证明这确实是罪魁祸首,我使用 LD_PRELOAD 来“删除”这个特定的系统调用:
$ cat force_nagle.c
#include <stdio.h>
#include <dlfcn.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
int (*osetsockopt) (int socket, int level, int option_name,
const void *option_value, socklen_t option_len) = NULL;
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len)
{
int ret;
if (!osetsockopt) {
osetsockopt = dlsym(RTLD_NEXT, "setsockopt");
}
if (option_name == TCP_NODELAY) {
puts("No, Mr Nagle stays.");
return 0;
}
ret = osetsockopt(socket, level, option_name, option_value, option_len);
return ret;
}
$ gcc -fPIC -D_GNU_SOURCE -shared -o force_nagle.so force_nagle.c -ldl
$ pv /dev/shm/data | LD_PRELOAD=./force_nagle.so ssh [email protected] 'cat >/dev/null'
No, Mr Nagle stays.
No, Mr Nagle stays.
100MiB 0:00:29 [3.38MiB/s] [3.38MiB/s] [================================>] 100%
那里 - 完美的速度(嗯,和 iperf3 一样快)。
故事的士气
永不放弃 :-)
如果您确实使用类似rsync
或borgbackup
之类的工具通过 SSH 传输数据,并且您的链接速度很慢,请尝试阻止 SSH 禁用 Nagle(如上所示) - 或使用ProxyCommand
将 SSH 切换为通过nc
.这可以在 $HOME/.ssh/config 中自动执行:
$ cat .ssh/config
...
Host orangepi
Hostname 192.168.1.150
User root
Port 22
# Compression no
# Cipher None
ProxyCommand nc %h %p
...
...以便以后在 ssh/rsync/borgbackup 中使用“orangepi”作为目标主机的所有用途都将用于nc
连接(因此不要打扰 Nagle)。