Paramiko SFTP上传速度远低于命令行sftp客户端的原因
速度差异根本原因
- 底层SSH传输层参数过于保守:Paramiko 2.7.1版本默认的SSH通道接收窗口仅为64KB,最大单包长度仅为32KB,且不会根据链路带宽、延迟动态调整窗口大小。这意味着任意时刻链路中最多只有64KB的未确认在途数据,只要链路RTT超过10ms,带宽就会被BDP(带宽延迟乘积)限制死,上层再怎么调应用层缓冲区大小都没有效果。而OpenSSH sftp客户端默认初始窗口为2MB,支持动态窗口扩容,单包最大支持256KB,能轻松填满传输管道。
- 流水线模式阈值设置过低:开启
set_pipelined()后,Paramiko默认最多只允许16个未确认的写请求在途,按默认32KB块大小计算,总在途数据量仅512KB,高带宽、高延迟链路下远达不到填满管道所需的在途数据量。OpenSSH sftp客户端默认允许64个在途请求,配合更大的块大小,管道利用率远高于默认配置的Paramiko。 - 读写块选择不合理:代码中用文件系统的
st_blksize作为读写块大小,FreeBSD下该值通常为4KB~16KB,过小的读写块会导致大量冗余的系统调用、SFTP协议帧封装开销,虽然CPU占用看起来不高,但大量上下文切换会直接拉低吞吐量。 - 实现层固有开销:Paramiko是纯Python实现,协议解析、加解密、IO调度都在Python解释器层执行,相比纯C实现、做了大量批量IO优化的OpenSSH,单块数据的处理开销本身就高30%~50%。
可落地的优化措施
- 首先调整底层SSH传输层参数,突破默认窗口限制,在SFTP连接建立完成后立刻执行以下配置:
tr = SFTP.get_channel().get_transport() # 调大初始窗口到16MB,单包最大长度到256KB,匹配大文件传输需求 tr.default_window_size = 16 * 1024 * 1024 tr.max_packet_size = 256 * 1024 # 调大重密钥触发阈值到1TB,避免大文件传输中途重密钥导致的掉速卡顿 tr.packetizer.REKEY_BYTES = pow(2, 40) tr.packetizer.REKEY_PACKETS = pow(2, 40)
- 调整应用层传输参数,对齐OpenSSH的默认配置:
import os # 固定读写块为256KB,不要使用文件系统块大小 CHUNK_SIZE = 256 * 1024 remote_path = os.path.split(fName)[-1] # 打开远程文件时指定匹配的块大小 with open(fName, "rb") as inp, SFTP.open(remote_path, "w", CHUNK_SIZE) as out: # 显式设置流水线最大在途请求数为64,和OpenSSH默认值对齐 out.set_pipelined(pipelined=True, max_requests=64) while True: buf = inp.read(CHUNK_SIZE) if not buf: break out.write(buf)
- 优先选择带硬件加速的加解密算法:连接SSH时优先协商
aes128-ctr、aes256-ctr这类支持AES-NI硬件加速的算法,避开chacha20-poly1305这类在纯Python实现下性能较差的算法,可降低30%左右的加解密开销。 - 如果以上优化后速度仍达不到OpenSSH的70%以上,可替换为带C扩展加速的SSH库比如asyncssh,其核心传输逻辑做了原生优化,大文件传输速度基本可以追平OpenSSH原生客户端。
内容的提问来源于stack exchange,提问作者Mikhail T.
相关产品推荐
相关产品推荐

