paramiko - 与系统 rsync/sftp/scp 相比,sftp 传输速度较慢

问题描述 投票:0回答:2

我注意到使用 paramiko 的 sftp 执行 get 或 put 时无法获得相同的传输速度。

在我们的 mac mini 服务器(运行 Mac os 10.12.6)的千兆网络中,通过 rsync/sftp/scp/finder 的文件传输速度维持在 95-100MB/秒左右。如果我使用 paramiko 的 sftp.get 我发现它达到了最大 25MB/秒。

我使用的是 paramiko 1.17 并更新到 2.3.1,但我看到的速度几乎相同。

有什么想法可能导致限制吗?

谢谢!

亚当

python paramiko
2个回答
3
投票

我遇到了同样的问题并实施了其他人提出的一些建议。可以做三件事:

  1. 增加传输中的缓冲区大小。

      transport = paramiko.Transport(ftp_host, ftp_port)
      transport.default_window_size = 4294967294 # 2147483647
      transport.packetizer.REKEY_BYTES = pow(2, 40)
      transport.packetizer.REKEY_PACKETS = pow(2, 40)
    
  2. 在获取文件之前执行预读。

     ftp_file = ftp_conn.file(file_name, "r")  
     ftp_file_size = ftp_file.stat().st_size 
     ftp_file.prefetch(ftp_file_size)
     ftp_file.set_pipelined()  
     ftp_file_data = ftp_file.read(ftp_file_size)
    
  3. 传输较大文件时可以做的另一件事是实现“块”。这会将文件分割成单独传输的较小部分。我仅通过转移到 s3 进行了测试。

     chunk_size = 6000000 #6 MB
     chunk_count = int(math.ceil(ftp_file_size / float(chunk_size)))
     multipart_upload = s3_conn.create_multipart_upload(Bucket=bucket_name, Key=s3_key_val)
     parts = []
     for i in range(chunk_count):
         print("Transferring chunk {}...".format(i + 1), "of ", chunk_count)
    
         start_time = time.time()
         ftp_file.prefetch(chunk_size * (i+1) # This statement is where the magic was to keep speeds high.
         chunk = ftp_file.read(int(chunk_size))
         part = s3_conn.upload_part(
             Bucket=bucket_name,
             Key=s3_file_path,
             PartNumber=part_number,
             UploadId=multipart_upload["UploadId"],
             Body=chunk
         )
         end_time = time.time()
         total_seconds = end_time - start_time
         print("speed is {} kb/s total seconds taken {}".format(math.ceil((int(chunk_size) / 1024) / total_seconds), total_seconds))
         part_output = {"PartNumber": i, "ETag": part["ETag"]}
         parts.append(part)
     print("Chunk {} Transferred Successfully!".format(i + 1))
    
     part_info = {"Parts": parts}
     s3_conn.complete_multipart_upload(
         Bucket=bucket_name,
         Key=s3_key_val,
         UploadId=multipart_upload["UploadId"],
         MultipartUpload=part_info
     )
    

处理 chunk 时的重要部分是 ftp_file.prefetch(chunk_size * (i+1)),它会在每个循环中进一步增量读取。

实施所有这些后,下载速度从 200 kBps 提高到 5 MBps(最大隧道速度)。

在这段代码的后续迭代中,我遇到了来自 paramiko 的垃圾收集问题。我通过删除该行解决了它们:

ftp_file.set_pipelined() 

0
投票

受到@DataAlias答案的启发,这是我的方法,如何以接近

sftp
Linux util

的速度从sftp下载文件
def download_to_local(self, remote_filename, local_filename):
    with self.open(remote_filename) as sftp_file, open(local_filename, mode="wb") as local:
        file_size = sftp_file.stat().st_size

        chunk_size = 6 * 1024 * 1024  # 6 MB
        chunk_count = int(math.ceil(file_size / float(chunk_size))) + 1

        buffer_size = chunk_size
        byte_buffer = bytearray(buffer_size)
        total_read_bytes = 0.

        # read chunks of file
        for i in range(chunk_count):
            start_time = time.time()
            prefetch = chunk_size * (i + 1)
            # whatever you do, you do want to keep this line, it improves speed about x100 times
            sftp_file.prefetch(prefetch)
            bytes_read = sftp_file.readinto(byte_buffer)

            total_read_bytes += bytes_read
            progress = round(total_read_bytes / file_size, 4) * 100.
            get_logger().info('completed {} percent of the file download'.format(progress))
            # write chunks to file
            local.write(bytes(byte_buffer[0:bytes_read]))

            end_time = time.time()
            total_seconds = end_time - start_time
            speed = math.ceil((int(chunk_size) / 1024) / total_seconds)
            get_logger().info(f"speed is {speed} kb/s total seconds taken {total_seconds}")
© www.soinside.com 2019 - 2024. All rights reserved.