欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-01 21:39:39
...

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

  使用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)
	......

发现该问题后,下意识地先在网上进行了相关问题的搜索,搜索良久,找到一些相关的信息,但没有找到具体的解决方案。最后,自己尝试解决问题,下面记录一下相关过程。
  首先,增加了上述方法中callback回调方法打印已经传递的字节数,发现出现问题时每次都是传递到30M左右时卡住不动。于是先从接收数据相关代码开始分析,增加了打印接收数据状态、读取数据状态的信息,发现每次卡住都是在读取数据时一直在那死等,首先想到是不是出现了死锁,于是仔细查看了相关代码,没有发现什么异常,然后查看从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服务器上其他大文件进行验证,发现都工作正常,至此确定前面猜测的问题点是对的,且该解决方案能解决问题。
  问题解决了,有点兴奋,又想到再次上网摸索一下相关信息,这次使用“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的值,达到最优效果。