python的paramiko模块下载大文件失败问题解决

python的paramiko模块下载大文件失败问题解决

(当前官方新版本paramiko 3.3.1已经增加了参数,可以控制并发数量,大家直接用新的官方库配置好参数即可规避该问题。sftp_cli.get(sftp_path, local_file_path, callback=callback, max_concurrent_prefetch_requests=512))

1.发现问题

  使用python的paramiko(2.7.2版本)模块从sftp服务器上面下载文件,发现一个很奇怪的现象,下载大小为29.5M的文件时,没有发现异常,但是下载大小为37.6M的文件时,大概率会出现中途卡住的情况。实际调用方法如下:

python3.6\Lib\site-packages\paramiko\sftp_client.py

class SFTPClient(BaseSFTP, ClosingContextManager):
     ......
	def get(self, remotepath, localpath, callback=None)
	......

发现该问题后,下意识地先在网上进行了相关问题的搜索,搜索良久,找到一些相关的信息,但没有找到具体的解决方案。最后,自己尝试解决问题,下面记录一下相关过程。

2.分析解决问题

  首先,增加了上述方法中callback回调方法打印已经传递的字节数,发现出现问题时每次都是传递到30M左右时卡住不动。于是先从接收数据相关代码开始分析,增加了打印接收数据状态、读取数据状态的信息,发现每次卡住都是在读取数据时一直在那死等,考虑到我是在windows 7 64位机器上验证发现的该问题,不确定在linux上是否也存在该问题,于是在一台centos 7.6 64位的机器上进行了验证,问题依旧,大体判断可能是第三方库paramiko实现逻辑的问题,首先想到是不是出现了死锁,于是仔细查看了相关代码,没有发现什么异常,然后查看从sftp获取数据的相关代码,看到了触发获取数据逻辑如下:

python3.6\Lib\site-packages\paramiko\sftp_file.py

    def _prefetch_thread(self, chunks):
        # do these read requests in a temporary thread because there may be
        # a lot of them, so it may block.
        for offset, length in chunks:
            num = self.sftp._async_request(
                self, CMD_READ, self.handle, long(offset), int(length)
            )
            with self._prefetch_lock:
                self._prefetch_extents[num] = (offset, length)

  直观感觉这个地方可能存在并发过大的问题,作者的注释,也说明了可能存在该问题,保险起见,又看了相关的实现逻辑,最后决定实际验证一下该想法,修改代码如下:

python3.6\Lib\site-packages\paramiko\sftp_file.py

    def _prefetch_thread(self, chunks):
        # do these read requests in a temporary thread because there may be
        # a lot of them, so it may block.
        max_request_num = 512
        to_wait = False
        for offset, length in chunks:
            num = self.sftp._async_request(
                self, CMD_READ, self.handle, long(offset), int(length)
            )
            with self._prefetch_lock:
                self._prefetch_extents[num] = (offset, length)
                if len(self._prefetch_extents) >= max_request_num:
                    to_wait = True
            
            if to_wait:
                time.sleep(1)
                to_wait = False

总体思路就是限制同时存在的异步请求总数,最开始是将max_request_num设置为1024,实际验证发现问题依然存在,有点小失落,以为是前面猜测的问题点不成立,接下来,将max_request_num设置为512,验证发现可以正常下载,稳妥起见,又尝试下载多次,并且下载sftp服务器上其他大文件进行验证,发现都工作正常,至此确定前面猜测的问题点是对的,且该解决方案能解决问题。

3.进一步研究

  问题解决了,有点兴奋,又想到再次上网搜索一下相关信息,这次使用“paramiko 下载大文件”关键字进行了搜索,有一些收获,发现这个问题以前就有人定位了,并且给出了解决方案,相关信息如下,可以进行参考。

需要修改源文件或配置其他参数的方案:
https://bbs.csdn.net/topics/392299886

不需要修改源文件,亲测有效的方法:
https://zhuanlan.zhihu.com/p/102372919
关键信息摘录如下:

        # 旧方法下载大文件会出现Server connection dropped
        # self.sftp.get(filename, filename_fullpath, callback=self.sftp_get_callback)
        
        # 新方法下载大文件成功
        with self.sftp.open(filename, 'rb') as fp:
            shutil.copyfileobj(fp, open(filename_fullpath, 'wb'))

  上述不需要修改源文件的方法,实际测试时,感觉耗时较长,于是进行了具体的量化分析,从sftp服务器下载49.6M大小的文件,使用我修改源代码的方式,耗时5秒,使用上面不需要修改源文件的方式,耗时24秒,性能差别还是很大的,所以,实际使用时,可根据自己的需求选择相应的方式,如果采用修改源代码的方式,可以根据实际情况,调整max_request_num的值,达到最优效果。

4.过程备忘

  出错代码堆栈信息:

File "/data/dev/python/credittools/ftp_download.py", line 48, in _download_single_file
    sftp_handler = sftp_util.sftp_download_file(sftp_cli, remote_file_path, local_file_path, force=force, callback=callback)
  File "/home/tom/work/anaconda3/lib/python3.6/site-packages/creditutils/sftp_util.py", line 184, in sftp_download_file
    sftp_cli.get(sftp_file_path, local_file_path, callback=callback)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp_client.py", line 802, in get
    size = self.getfo(remotepath, fl, callback)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp_client.py", line 782, in getfo
    reader=fr, writer=fl, file_size=file_size, callback=callback
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp_client.py", line 678, in _transfer_with_callback
    data = reader.read(32768)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/file.py", line 219, in read
    new_data = self._read(read_size)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp_file.py", line 182, in _read
    data = self._read_prefetch(size)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp_file.py", line 162, in _read_prefetch
    self.sftp._read_response()
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp_client.py", line 843, in _read_response
    t, data = self._read_packet()
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp.py", line 201, in _read_packet
    x = self._read_all(4)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/sftp.py", line 185, in _read_all
    x = self.sock.recv(n)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/channel.py", line 699, in recv
    out = self.in_buffer.read(nbytes, self.timeout)
  File "/home/tom/.local/lib/python3.6/site-packages/paramiko/buffered_pipe.py", line 160, in read
    self._cv.wait(timeout)
  File "/home/tom/work/anaconda3/lib/python3.6/threading.py", line 295, in wait
    waiter.acquire()
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Python paramiko是一个用于SSH连接和执行远程命令的模块。它可以在多个平台上运行,包括Linux、Solaris、BSD、MacOS X和Windows。通过使用paramiko,我们可以从一个平台连接到另一个平台,并执行一系列操作。 要安装paramiko模块,可以使用以下命令: 1. 安装python-devel:`yum -y install python-devel` 2. 安装pycrypto:`pip3 install pycrypto` 3. 安装paramiko:`pip3 install paramiko` 以下是一个使用paramiko进行批量远程密码连接的应用案例: 1. 导入必要的模块和异常处理类。 2. 创建一个SSH对象,并设置自动添加主机的策略。 3. 连接目标服务器,如果连接失败会抛出相应的异常。 4. 执行需要的操作,比如执行命令。 5. 获取命令执行的结果。 6. 关闭连接。 以上是一个批量远程密码连接的示例,其中还包括从ip.txt文件中读取主机信息并进行连接的过程。 通过使用python paramiko,我们可以轻松地在不同平台之间进行SSH连接并执行远程命令。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [pythonparamiko模块](https://blog.csdn.net/forever_wen/article/details/82556154)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值