问题描述
前段时间由于tcp连接数超标导致阿里云发出了邮件警报,经初步定位已经重新发布了一个版本(见上篇一次线上tcp连接告警解决方案).但是时间过了还不到一周,银行反馈他们那边下载的文件很多都是.tmp临时文件,并不是最终的zip格式。而且生产上的线程堆栈显示其中某些线程连续很多天都没有释放.
解决思路
- 查看生产日志
确实存在文件改名的日志,然后将文件改成.tmp后缀的临时文件,再将tmp文件改成zip文件。且日志中存在大量的错误日志(包损坏日志和重连异常,导致正常的包还没完成改名就已经出错).
- 线程不释放的问题
会不会由于上次每次上传、下载的时候都重新打开一个session,释放的时候又没有释放完全呢?
查看代码
调用rename服务时,需要再次获取Channel,如果获取Channel报错,则又会重新调用init()方法。
见init()方法:
当调用完init()方法结束后,又调用了一次session.connect()方法,这应该就是导致线程不释放的问题,明明在init()方法中已经建立连接了,这里又一次调用connect()方法,导致多创建了一个线程,而在调用disconnect()方法的时候只是关闭了其中一个线程。
但是sftp临时文件又作何解释呢?
这里将Session定义成全局的?也就是说线程之间是共享的。
由于这个服务是被spring管理的,默认就是单例,由于上传和下载的文件很多,肯定会导致并发的调用upload、download、rename等方法,频繁的连接session、关闭session.
找到原因
其中一个线程调用文件的rename服务时,还没来得及改名成功,另外的线程刚好已经上传或者下载成功 并且已经调用disconnect()方法将session已经关闭.由于session是共享成员变量,其他正在使用这个session的所有操作都会抛出异常,导致失败。
最终解决办法
- 在重连时去掉第二次session.connect(),避免线程不释放的问题。
- 避免sftp session线程共享的问题。
总结
- 涉及到文件上传下载的时候 要考虑网络中断、重连、线程不释放、文件完整性等等一系列问题。
- jsch这个开源的sftp工具包 确实不太好用,希望其他人在使用它的时候特别小心,或者采用比较大众化的sftp工具。