Spring Boot + MinIO 实现分段、断点续传,让文件传输更高效
一、引言
在当今的互联网应用中,文件上传是一个常见的功能需求。然而,传统的文件上传方式在面对大文件或不稳定的网络环境时,可能会出现性能瓶颈和上传失败的问题。
传统文件上传,就像是用一辆小推车搬运大型家具,一次只能运送一件,不仅耗时久,还容易在途中 “翻车”,导致上传中断。而且,一旦上传失败,就得从头再来,极大地浪费了时间和资源。
为了解决这些问题,分片上传、断点续传技术应运而生。Spring Boot 作为一款流行的 Java 开发框架,提供了简洁高效的开发体验;MinIO 则是一个高性能的对象存储服务器,支持 S3 协议,能够可靠地存储文件。将它们结合使用,可以轻松实现文件的分段、断点续传功能,为用户提供更流畅的上传体验。就好比把大型家具拆分成零部件,用多辆小推车并行运输,即使某一辆车出了问题,后续也能快速从断点处继续,大大提高了运输效率。
二、实现分段上传
(一)原理剖析
分段上传,又称分片上传,其核心原理是将大文件依照特定大小分割成一系列小块,这些小块被称为分片。随后,各个分片作为独立单元,分别通过网络上传至服务器。以传输一部高清电影为例,若电影文件大小为 5GB,按照 5MB 为一个分片进行切割,可得到 1000 个分片。在上传时,这 1000 个分片能够依次或并行地向服务器发起传输请求。相较于传统的一次性上传整个大文件,分段上传优势显著。一方面,它能极大提高上传的稳定性。在网络环境不稳定,频繁出现丢包、延迟等情况时,一次性传输大文件极易导致上传失败,而分段上传即便某个分片传输受阻,只需重新传输该分片即可,不会影响其他已成功传输的分片,有效降低了整体上传失败的风险。另一方面,分段上传支持并行操作,多个分片可同时进行上传,充分利用网络带宽资源,大大加快了上传速度。尤其在面对网络状况良好的环境,并行上传多个分片能够显著缩短大文件的上传时间,提升用户体验。
(二)MinIo使用
1.Windows安装
下载路径:MinIO下载和安装 | 用于创建高性能对象存储的代码和下载内容
CMD执行 C:\minio.exe server F:\Data --console-address ":9001"
启动后
2.Linux安装
下载地址:MinIO下载 | 中国镜像下载加速站
使用minio方式安装
wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio
chmod +x minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server /mnt/data --console-address ":9001"
使用rpm方式安装
wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio-20241218131544.0.0-1.x86_64.rpm
rpm -ivh minio-20241218131544.0.0-1.x86_64.rpm
minio server ./
Systemd配置
.rpm
软件包将以下systemd服务文件安装到/usr/lib/systemd/system/minio.service
[Unit]
Description=MinIO
Documentation=https://min.io/docs/minio/linux/index.html
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/bin/minio
[Service]
WorkingDirectory=/usr/local
User=minio-user
Group=minio-user
ProtectProc=invisible
EnvironmentFile=-/etc/default/minio
ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi"
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
# MinIO RELEASE.2023-05-04T21-44-30Z adds support for Type=notify (https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=)
# This may improve systemctl setups where other services use `After=minio.server`
# Uncomment the line to enable the functionality
# Type=notify
# Let systemd restart this service always
Restart=always
# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536
# Specifies the maximum number of threads this process can create
TasksMax=infinity
# Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
# Built for ${project.name}-${project.version} (${project.name})
默认情况下,minio.service
文件作为minio-user
用户和组运行。您可以使用groupadd
和useradd
命令创建用户和组。以下示例创建用户和组,并设置访问MinIO要使用的文件夹路径的权限。这些命令通常需要root(sudo
)权限。
groupadd -r minio-user
useradd -M -r -g minio-user minio-user
chown minio-user:minio-user /mnt/data
创建环境变量文件
在/etc/default/minio
创建一个环境变量文件。MinIO Server容器可以使用此文件作为所有环境变量的源。
MINIO_OPTS="--address '0.0.0.0:9050' --console-address '0.0.0.0:9051' "
MINIO_VOLUMES="/data/minio/data"
MINIO_ROOT_USER=minio
MINIO_ROOT_PASSWORD=minio123
最后 sudo systemctl start minio.service 启动minio服务
常用命令
启动MinIO
systemctl start minio
查询运行状态systemctl status minio
停止MinIO
systemctl stop minio
3.minio配置
登录minio web端
创建桶
创建Key
(三)核心代码解读
- 配置 MinIO 客户端:在 Spring Boot 项目中引入 MinIO 依赖,首先需在 pom.xml 文件中添加相应依赖项:
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.4</version>
</dependency>
此处以 8.4.4 版本为例,实际使用时应根据 MinIO 官方发布的稳定版本进行调整。引入依赖后,接着在 application.yml 或 application.properties 配置文件中配置 MinIO 连接信息:
minio:
endpoint: http://localhost:9000 //minio api地址端口
access-key: accessKey //minio access-key
secret-key: secretKey //minio secretKey
bucket-name: your-bucket-name //桶
上述配置中,endpoint指定 MinIO 服务的访问地址,access-key与secret-key是访问 MinIO 服务器的凭证,用于身份验证,bucket-name则是文件存储的桶名称。通过这些配置,Spring Boot 项目便能顺利与 MinIO 服务器建立连接,为后续文件上传操作奠定基础。
- 文件分片上传逻辑:以下是一段基于 Spring Boot 结合 MinIO 实现文件分片上传的关键代码片段:
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
@RestController
public class FileUploadController {
@Autowired
private MinioClient minioClient;
// 定义分片大小,这里设为5MB,可根据实际情况调整
private static final long CHUNK_SIZE = 5 * 1024 * 1024;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
String bucketName = "your-bucket-name";
String objectName = file.getOriginalFilename();
InputStream inputStream = file.getInputStream();
long fileSize = file.getSize();
// 计算分片数量
long totalChunks = fileSize % CHUNK_SIZE == 0? fileSize / CHUNK_SIZE : fileSize / CHUNK_SIZE + 1;
for (int i = 0; i < totalChunks; i++) {
long offset = i * CHUNK_SIZE;
long chunkSize = Math.min(CHUNK_SIZE, fileSize - offset);
byte[] buffer = new byte[(int) chunkSize];
inputStream.read(buffer);
// 构建上传参数,上传每个分片
PutObjectArgs args = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName + "_chunk_" + i)
.stream(new ByteArrayInputStream(buffer), chunkSize, -1)
.build();
try {
minioClient.putObject(args);
} catch (Exception e) {
// 处理上传分片失败的情况,可记录日志、重试等
e.printStackTrace();
}
}
return "File uploaded successfully";
}
}
在这段代码中,首先通过@Autowired注解注入MinioClient,确保能够与 MinIO 服务器进行交互。接着定义了分片大小CHUNK_SIZE,这里设定为 5MB,开发者可依据文件特性、网络带宽等因素灵活调整。在uploadFile方法中,获取上传的文件信息,包括文件名、输入流以及文件大小,并根据分片大小计算出分片总数。随后,通过循环依次读取每个分片的数据,利用MinioClient的putObject方法将分片上传至指定的存储桶中。若上传过程中某个分片出现异常,代码中简单地进行了打印堆栈信息的处理,实际应用中,可根据需求进一步优化,如记录详细日志、尝试自动重试上传等,以增强系统的可靠性与稳定性。
四、实现断点续传
(一)原理剖析
断点续传是在分片上传基础上的一项关键优化,其核心原理在于精准记录上传进度,并在遭遇网络或操作中断后,能够从断点处继续上传。当文件开始分片上传时,系统会同步记录已成功上传的分片信息,常见的记录方式包括在数据库中存储已上传分片的序号、使用 Redis 等缓存工具记录上传状态等。一旦上传过程中断,无论是由于网络故障、设备断电还是用户主动暂停,下次上传时,系统首先会查询记录的上传进度,识别出已上传的分片,随后仅传输尚未完成的分片,避免了重复劳动。这种机制极大地提升了用户体验,特别是在上传大文件时,若因意外中断而需从头开始,将耗费大量时间与资源,而断点续传则有效解决了这一痛点,让文件上传更加智能、高效。
(二)核心代码解读
- 记录上传进度:以下是一段使用 Redis 记录上传进度的示例代码:
import redis.clients.jedis.Jedis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UploadProgressRecorder {
@Autowired
private Jedis jedis;
public void recordProgress(String uploadId, int chunkIndex) {
jedis.sadd(uploadId, String.valueOf(chunkIndex));
}
public boolean isChunkUploaded(String uploadId, int chunkIndex) {
return jedis.sismember(uploadId, String.valueOf(chunkIndex));
}
public void close() {
jedis.close();
}
}
在这段代码中,UploadProgressRecorder类负责与 Redis 交互,记录和查询上传进度。通过@Autowired注入Jedis实例,recordProgress方法使用SADD命令将已上传的分片索引添加到以uploadId为键的集合中,isChunkUploaded方法则借助SISMEMBER命令判断指定分片是否已上传,最后提供close方法关闭 Redis 连接,确保资源的正确释放。这里使用集合数据结构来存储已上传分片索引,能够方便地进行成员判断与批量操作,适用于频繁的上传进度记录与查询场景。
- 续传逻辑实现:在文件上传控制器中,结合上述记录进度的功能,实现断点续传逻辑:
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Set;
@RestController
public class FileUploadController {
@Autowired
private MinioClient minioClient;
@Autowired
private UploadProgressRecorder progressRecorder;
// 定义分片大小,这里设为5MB,可根据实际情况调整
private static final long CHUNK_SIZE = 5 * 1024 * 1024;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
String bucketName = "your-bucket-name";
String objectName = file.getOriginalFilename();
String uploadId = generateUploadId(); // 生成唯一的上传ID,用于标识本次上传任务
long fileSize = file.getSize();
long totalChunks = fileSize % CHUNK_SIZE == 0? fileSize / CHUNK_SIZE : fileSize / CHUNK_SIZE + 1;
// 检查已上传的分片
Set<String> uploadedChunks = progressRecorder.getUploadedChunks(uploadId);
for (int i = 0; i < totalChunks; i++) {
if (!uploadedChunks.contains(String.valueOf(i))) {
long offset = i * CHUNK_SIZE;
long chunkSize = Math.min(CHUNK_SIZE, fileSize - offset);
byte[] buffer = new byte[(int) chunkSize];
file.getInputStream().read(buffer);
// 构建上传参数,上传未完成的分片
PutObjectArgs args = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName + "_chunk_" + i)
.stream(new ByteArrayInputStream(buffer), chunkSize, -1)
.build();
try {
minioClient.putObject(args);
progressRecorder.recordProgress(uploadId, i); // 记录分片上传成功
} catch (Exception e) {
// 处理上传分片失败的情况,可记录日志、重试等
e.printStackTrace();
}
}
}
// 判断所有分片是否都已上传完成,若完成则可进行合并操作或其他后续处理
if (uploadedChunks.size() == totalChunks) {
// 执行合并分片等操作
return "File uploaded and merged successfully";
}
return "Upload paused, will resume from breakpoint next time";
}
}
在上述uploadFile方法中,首先生成一个唯一的上传 ID,用于在后续操作中准确标识本次上传任务。接着,依据文件大小与预设的分片大小计算出总分片数。随后,通过progressRecorder查询已上传的分片集合,在循环上传分片时,跳过已存在于集合中的分片,仅上传尚未完成的部分,并在每次成功上传后,及时记录该分片的上传状态。最后,通过对比已上传分片数量与总分片数,判断文件是否全部上传完毕,若完成则可触发后续的合并操作或进行相应的业务处理,若未完成,则告知用户上传处于暂停状态,下次将从断点处继续。如此一来,系统便能在面对复杂多变的网络环境与用户操作时,稳定、高效地实现文件的断点续传功能。
五、优化与拓展
在实现了基本的分段、断点续传功能后,还可以从多个方面对文件上传系统进行优化与拓展,进一步提升性能与用户体验。
(一)上传性能优化
- 调整分片大小:分片大小的选择对上传性能有着显著影响。若分片过大,在网络不稳定时,单个分片传输失败的概率会增加,且重传成本高;若分片过小,会产生过多的网络请求,增加服务器的处理开销与延迟。一般而言,可依据网络带宽的稳定程度、服务器的处理能力以及文件类型来动态调整分片大小。例如,在网络带宽充裕且稳定时,适当增大分片大小,以减少请求次数,提高传输效率;反之,在网络波动较大时,减小分片大小,降低单个分片传输失败的风险。通过实时监测网络状况,结合文件大小,智能地选择最优分片大小,能够极大提升上传的稳定性与速度。
- 优化网络请求:在文件上传过程中,减少不必要的网络请求开销至关重要。一方面,可采用连接池技术,复用已建立的网络连接,避免频繁地创建与销毁连接带来的性能损耗。例如,在 Spring Boot 项目中配置合适的连接池参数,如最大连接数、最小空闲连接数等,确保在高并发上传场景下,网络连接资源能够得到高效利用。另一方面,对网络请求进行批量处理,将多个小的上传请求合并为一个大的请求,减少请求头、响应头等额外信息的传输开销,提高网络传输的有效负载率。同时,结合异步编程模型,让文件上传操作在后台线程执行,避免阻塞主线程,提升系统的响应性,使得用户在上传文件时,仍能流畅地进行其他操作。
(二)功能拓展思路
- 结合秒传功能:秒传功能是提升用户上传体验的一大利器。其原理是在上传文件之前,先计算文件的哈希值(如常用的 MD5、SHA-256 等算法),然后将该哈希值发送至服务器。服务器依据此哈希值在存储系统中快速检索,判断是否已存在相同哈希值的文件。若存在,则直接返回该文件的访问链接,用户无需重复上传实际的文件内容,瞬间完成 “上传” 操作。这对于一些常见的、已在服务器存储过的文件,如常用的图片、文档模板等,能够极大地节省上传时间与带宽资源。在结合 Spring Boot 与 MinIO 实现时,可在文件上传控制器中新增一个接口,用于接收文件哈希值并进行秒传判断。若文件不存在,则按照常规的分段、断点续传流程进行上传,从而实现秒传与分段上传的无缝融合。
- 增加文件校验:为确保上传文件的完整性与准确性,文件校验必不可少。除了在断点续传中记录已上传分片信息,防止重复上传外,还可在上传完成后,对整个文件进行校验。一种常见的方式是在客户端计算文件的哈希值,并在上传完成后,将该哈希值与服务器端重新计算的文件哈希值进行比对。若两者一致,则表明文件在传输过程中未发生损坏或篡改;若不一致,则提示用户文件可能存在问题,需要重新上传。此外,还可引入数据冗余技术,如在 MinIO 存储中采用纠删码(Erasure Coding),即使部分数据丢失或损坏,仍能通过冗余信息恢复文件,进一步提高数据的可靠性,为用户提供更加安全、可靠的文件上传服务。
七、总结
通过 Spring Boot 与 MinIO 的强强联合,我们成功实现了文件的分段、断点续传功能,有效解决了大文件上传在不稳定网络环境下的难题。这一技术方案不仅提升了文件上传的稳定性与效率,还为用户带来了更加流畅的使用体验。在实际应用中,开发者可根据具体业务场景,灵活调整分片大小、优化网络请求,进一步拓展秒传、文件校验等功能,满足多样化的需求。未来,随着云计算与大数据技术的不断发展,相信这一技术组合将在更多领域发挥重要作用,如视频直播、云存储服务、大数据分析等,为海量数据的高效传输与处理提供坚实保障,助力企业实现数字化转型与升级。
2024年的最后一篇文章 在这祝大家新年快乐,一夜暴富!!!