文章目录
- 1. 前言
- 2. 多线程读取下载+异步上传
- 2-1. 多线程读取下载
- 2-2. 异步上传异步更新cosUrl
- 3. 线程池单个任务提交
- 4. 关于异步上传的文件oosUrl地址返回问题
1. 前言
实际开发中经常遇到,文件上传或者文件批量下载的任务。单线程下载大批量文件,排队处理会比较慢。读取下载+上传文件同步处理非常耗时。
对于75M的视频文件读取下载耗时严重时达到20s左右,45M视频文件读取下载大概也要5-6s。同样大小的视频文件上传oos或者cos需要花5-8s。单线程读取下载+同步上传处理一个文件要花费25s左右。
对于这种任务考虑文件读取下载引入多线程批量处理,上传文件动作改为异步处理。
多线程批量读取上传可以提高文件读取下载的效率,并行处理多个文件的下载。
异步上传可以释放上传动作的耗时时间,让主线程只关注读取下载文件动作即可。节省了文件上传的时间。
2. 多线程读取下载+异步上传
单线程读取下载+同步上传方案改为多线程读取下载+异步上传方案
业务场景背景:
下述方法是定时任务调用的文件转存储cos业务方法。对于待转存cos的文件,查出来之后,根据文件md5去重复,防止同文件重复上传耗资源。然后遍历对象,读取文件resourceUrl,下载到服务器临时目录,然后上传到cos云存储。并更新cosUrl到表记录。
2-1. 多线程读取下载
1、文件读取下载转存cos业务方法
此处查出待转存的视频素材对象集合,根据文件md5去重复防止重复上传。
引入ConcurrentHashMap记录文件上传结果,比如文件读取失败、下载失败、上传结果标识、失败原因等信息,ConcurrentHashMap线程安全。
引入线程池,多线程批量读取下载文件,提高文件下载效率。
引入Future或者CompletableFuture,批量提交任务并跟踪任务处理结果。使用 Future 对象列表来保存每个任务的执行结果,并在所有任务执行完毕后等待它们完成,确保所有任务都已经执行完毕后再继续执行后续操作。
future.get()方法会等待当前线程任务执行完成,主线程中调用该方法相当于阻塞主线程,等待多线程中的任务执行完成后,才会往下执行。保证了多线程任务全部执行完成后,也就是批量读取下载文件完成后,再进行后续动作。
2、文件读取下载和上传业务方法
文件读取下载,同步处理。文件上传调用异步方法,不需要等待上传完成即可返回。缩短了当前线程的执行时间。
3、文件同步读取下载
2-2. 异步上传异步更新cosUrl
1、文件异步上传cos
@Async标识异步执行,默认使用框架内置线程池。
也可以追加参数指定自定义线程池。比如@Async(“TranslatePool”),自定义一个bean如下所示。此处使用框架默认线程池。
2、自定义线程池
3. 线程池单个任务提交
修改单线程读取下载+同步上传方案为多线程批量读取下载+异步上传,整体耗时更短,更高效。45M~75M大小不等的视频文件,15分钟大约可以处理完成1000条视频素材(仅作参考,不严谨)。包括文件读取下载、上传cos、更新cosUrl到表记录。
注意线程池使用,任务提交尽量使用批量提交任务。批量提交任务,避免了每个任务都立即提交到线程池中,减少了线程池中的任务切换开销。
单个提交任务,存在线程池的线程全部进行无限期等待状态的情况。
在大批量任务,且任务处理比较耗时的场景下,由于单个任务提交很快。前面的任务都还没执行完成,后面的任务就提交了,导致只能等待,最后会出现全部核心线程进入无限期等待卡死。建议使用批量提交任务。
通过jstack 命令分析进程ID的线程执行情况,在下面这种写法出现了线程全部无限期等待的情况。注意规避。
4. 关于异步上传的文件oosUrl地址返回问题
如果上传文件后的存储地址,在上传前就可以拼接出来,可以在异步上传之前就拿到url等待入库。
在上传动作之后才能拿到oosUrl的话,可以在异步任务中拿到url之后,单独根据业务标识更新对应记录的oosUrl。
参考文章:【代码片】文件多线程读取下载+异步上传云存储
Powered By niaonao