背景
今天写了一个数据处理任务,用于后端定时触发。并为这个定时任务提供了一个触发接口,用于手动触发。 这个任务的处理逻辑,大概是从SFTP上拉取文件,并对文件进行解析。任务写完后,在测试时,数据量较少,并未发现问题。
现象
今天在正式环境手动触发任务时,由于处理时间较长,大概在30分钟左右。开始时,任务处理正常。处理了一段时间后,任务持续报异常,后台日志持续飘红。跟踪日志发现,SFTP连接被意外中断,导致任务处理失败。即使程序自动重新创建连接,仍然会被意外中断。
重试多次,现象可以复现,且均是出现在任务执行一段时间之后。
通过在任务处理的入口处增加日志,发现了问题原因。一段时间之后,后端收到了第二次任务处理的请求。第二次任务处理时,对第一次还未执行完成的任务造成了影响。
但是又有了两个问题:
- 第二次触发的请求是哪里来的?我用postman只触发了一次请求,且这次请求的结果还未返回。
- 为什么第二次的任务处理会关闭第一次的任务连接。
猜想
- 第二次请求
通过观察,猜想,第二次请求是postman触发的。因请求长时间未得到响应,所以postman自动触发了一次重试。于是,我再次使用postman触发任务,并取消postman的等待响应。10分钟之后,后端未收到二次请求,且第一次任务正常执行完成。由此可知,postman在一段时间内未得到响应,将触发重试。 - 关闭连接
通过查看代码发现,SFTP的连接工具类,使用的是单例模式,且共享一个连接。于是第二个任务在调用关闭处理时,第一次任务的连接也被关闭了。于是后端出现了连接意外关闭的错误。
解决方案
- 针对postman自动重试,如果不经常使用,可以考虑在触发任务后,手动停止postman的响应等待。但如果接口调用频繁的话,需要做特殊处理。此处提供几个思路。
- 将任务处理包装成线程任务。在接口触发时,创建一个线程任务,另起线程执行,同时返回接口响应。对于这种方式,需要考虑接口的并发处理。
- 使用内存锁,任务触发时,获取内存锁,如果获取到则触发任务执行,否则直接返回。该方式,可以阻断postman的自动重试。但同时也会阻止手动的重复执行,有利有弊。
- 针对连接异常关闭
如果是单线程模式,则无需过多处理。 对于并发模式,则可以考虑避免使用单例模式的连接管理。考虑将SFTP的连接进行池化管理。采用连接池,可以避免不同任务中的连接影响。
|