ITPub博客

首页 > 数据库 > 数据库开发技术 > 编程日记:小小的文件下载居然也有这么大的坑

编程日记:小小的文件下载居然也有这么大的坑

原创 数据库开发技术 作者:DevXTalk 时间:2024-10-24 14:17:04 0 删除 编辑

编程日记:小小的文件下载居然也有这么大的坑

最近做的任务涉及到文件下载,起初并没有太当回事,在潜意识里都认为文件下载是很简单的两行代码就搞定了。 随着遇到一些问题发现还是有不少坑的简要的记录一下我的编程日记。

Java 下载文件的几种方式

java IO


try (InputStream in = new URL("url").openStream();
OutputStream out = new ByteArrayOutputStream()) {

byte[] buffer = new byte[1024];
int length;

while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}

System.out.println("文件下载完成");

} catch (IOException e) {
e.printStackTrace();
System.err.println("下载文件时发生错误");
}

java NIO

URL url = new URL("https://image.longpean.com/files/Img_Upscale_0094.tif");
try (ReadableByteChannel rbc = Channels.newChannel(url.openStream())) {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); // 分配1MB的缓冲区
while ((rbc.read(buffer)) != -1) {
buffer.flip(); // 切换到读模式
if (buffer.remaining() > 0) {
byte[] chunk = new byte[buffer.remaining()];
buffer.get(chunk);
// 将 chunk 追加到最终的字节数组中
// 这里为了简化,直接使用 ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(chunk);
byte[] bytes = baos.toByteArray();
}
buffer.clear(); // 清空缓冲区,准备下一次读取
}
}

Guava

import com.google.common.io.ByteStreams;
import java.net.URL;

byte[] bytes = ByteStreams.toByteArray(new URL("url").openStream());

Apache Commons IO

import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.net.URL;

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
IOUtils.copy(new URL("url").openStream() , outputStream , 1024 * 1024);
byte[] bytes = outputStream.toByteArray();

注意要根据实际情况,文件大小,服务器配置等,设置一个合适的缓冲区大小让下载效率提高。

多个大文件同时下载

当存在多个大文件同时进行下载时每一个文件下载都都会很慢,从表象上看就像是线程被卡死了,等到该线程再次执行时好像时丢失了上下文,上一个任务莫名其妙 的消失了,文件下载完成后的代码并没有被执行也没有抛出异常。

一开始怀疑是不是线程被阻塞时间太长,被认为是空闲线程被线程池回收了。理论上来说线程被 IO 阻塞时线程是不会被标记为空闲线程的,所以不应该被回收, 为了验证做了模拟试验。

这里设置线程的 keepAliveTime 为 1秒,如果下载文件阻塞较长线程被回收,“下载完成” 的日志就不会输出。


ThreadPoolExecutor WORKER_THREAD_POOL = new ThreadPoolExecutor(5,
5, 1,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(500),
new ThreadFactoryBuilder().setNameFormat("web-socket-worker-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());

WORKER_THREAD_POOL.submit(() -> {
System.out.println(Thread.currentThread().getName());
byte[] bytes1 = PictureUtil.download("url");
System.out.println(Thread.currentThread().getName() + " 下载完成");
});
WORKER_THREAD_POOL.submit(() -> {
System.out.println(Thread.currentThread().getName());
byte[] bytes1 = PictureUtil.download("url");
System.out.println(Thread.currentThread().getName() + " 下载完成");
});
WORKER_THREAD_POOL.submit(() -> {
System.out.println(Thread.currentThread().getName());
byte[] bytes1 = PictureUtil.download("url");
System.out.println(Thread.currentThread().getName() + " 下载完成");
});
WORKER_THREAD_POOL.submit(() -> {
System.out.println(Thread.currentThread().getName());
byte[] bytes1 = PictureUtil.download("url");
System.out.println(Thread.currentThread().getName() + " 下载完成");
});
WORKER_THREAD_POOL.submit(() -> {
try {
System.out.println(Thread.currentThread().getName() + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
} catch (Exception e) {
e.printStackTrace();
}
});

试验结果否定了线程被回收的猜想。不过这是在 windows 环境下做的测试,和事故现场使用的 linux 还是存在差异所以不能完全排除。

接着分析是因为网络带宽被打满了导致的每个文件下载的都很慢,每个文件单独下载时耗时不会很久,当同时下载多个大文件时网络带宽被 打满会影响到所有文件的下载速度。

同时开启对5个大文件的下载。

5 个线程全都执行网络IO (黄色是空闲状态,青色是 Net I/O)

每个文件的下载时间都很长。

单个文件的下载比同时下载耗时少很多。

检查网络流量发现被打满。

N兆带宽和下载速度转换计算公式:NM bit/s = N1024K bit/s = N*128KByte/s

解决方案

基本思路是排队,分流。

  1. 将线程池线程数调小,能够一定程度缓解该问题。最坏情况下会有两个大文件同时下载。弊端是小文件的下载会被前面大文件的下载任务阻塞。

  2. 将任务分开处理,大文件一个线程池,小文件一个线程池。并且大文件的线程池线程数不能太多,要根据服务器的带宽大小进行设置。这样让大文件 的下载任务排队执行并不会阻塞小文件的下载任务。当然前提是下载前需要知道要下载的文件的大小。


祝大家 1024 程序员节日快乐


欢迎加入 DevXTalk 的程序员、架构师、IT从业者交流群,在这里你可以和大佬们一起交流分享技术,行业经验,职位内推,摸鱼聊天。总之期待你的加入... 添加我的wx,备注:“进群”拉你进群(群二维码有效时间太短维持不了几天)

有招聘需求的也可以加我微信把招聘岗位发给我,我会贴在文章末尾,毕竟我的读者个个都是人才。

有需要推广自己的开源作品的也可以联系我,可以在公众号内帮助推广。


看完本文有收获?转发给更多的朋友吧

DevXTalk 不止于技术


原文作者:DevXTalk;如有侵权,请联系管理员删除。

上一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2019-03-23

  • 博文量
    178
  • 访问量
    82573