python的paramiko模块下载大文件失败问题解决
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的值,达到最优效果。
上一篇: 用大数据挖掘市场“金矿”
下一篇: Pytorch读取图片并显示