当前位置: 首页 > article >正文

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用户和组运行。您可以使用groupadduseradd命令创建用户和组。以下示例创建用户和组,并设置访问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

(三)核心代码解读

  1. 配置 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 服务器建立连接,为后续文件上传操作奠定基础。

  1. 文件分片上传逻辑:以下是一段基于 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 等缓存工具记录上传状态等。一旦上传过程中断,无论是由于网络故障、设备断电还是用户主动暂停,下次上传时,系统首先会查询记录的上传进度,识别出已上传的分片,随后仅传输尚未完成的分片,避免了重复劳动。这种机制极大地提升了用户体验,特别是在上传大文件时,若因意外中断而需从头开始,将耗费大量时间与资源,而断点续传则有效解决了这一痛点,让文件上传更加智能、高效。

(二)核心代码解读

  1. 记录上传进度:以下是一段使用 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 连接,确保资源的正确释放。这里使用集合数据结构来存储已上传分片索引,能够方便地进行成员判断与批量操作,适用于频繁的上传进度记录与查询场景。

  1. 续传逻辑实现:在文件上传控制器中,结合上述记录进度的功能,实现断点续传逻辑:
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查询已上传的分片集合,在循环上传分片时,跳过已存在于集合中的分片,仅上传尚未完成的部分,并在每次成功上传后,及时记录该分片的上传状态。最后,通过对比已上传分片数量与总分片数,判断文件是否全部上传完毕,若完成则可触发后续的合并操作或进行相应的业务处理,若未完成,则告知用户上传处于暂停状态,下次将从断点处继续。如此一来,系统便能在面对复杂多变的网络环境与用户操作时,稳定、高效地实现文件的断点续传功能。

五、优化与拓展

在实现了基本的分段、断点续传功能后,还可以从多个方面对文件上传系统进行优化与拓展,进一步提升性能与用户体验。

(一)上传性能优化

  1. 调整分片大小:分片大小的选择对上传性能有着显著影响。若分片过大,在网络不稳定时,单个分片传输失败的概率会增加,且重传成本高;若分片过小,会产生过多的网络请求,增加服务器的处理开销与延迟。一般而言,可依据网络带宽的稳定程度、服务器的处理能力以及文件类型来动态调整分片大小。例如,在网络带宽充裕且稳定时,适当增大分片大小,以减少请求次数,提高传输效率;反之,在网络波动较大时,减小分片大小,降低单个分片传输失败的风险。通过实时监测网络状况,结合文件大小,智能地选择最优分片大小,能够极大提升上传的稳定性与速度。
  1. 优化网络请求:在文件上传过程中,减少不必要的网络请求开销至关重要。一方面,可采用连接池技术,复用已建立的网络连接,避免频繁地创建与销毁连接带来的性能损耗。例如,在 Spring Boot 项目中配置合适的连接池参数,如最大连接数、最小空闲连接数等,确保在高并发上传场景下,网络连接资源能够得到高效利用。另一方面,对网络请求进行批量处理,将多个小的上传请求合并为一个大的请求,减少请求头、响应头等额外信息的传输开销,提高网络传输的有效负载率。同时,结合异步编程模型,让文件上传操作在后台线程执行,避免阻塞主线程,提升系统的响应性,使得用户在上传文件时,仍能流畅地进行其他操作。

(二)功能拓展思路

  1. 结合秒传功能:秒传功能是提升用户上传体验的一大利器。其原理是在上传文件之前,先计算文件的哈希值(如常用的 MD5、SHA-256 等算法),然后将该哈希值发送至服务器。服务器依据此哈希值在存储系统中快速检索,判断是否已存在相同哈希值的文件。若存在,则直接返回该文件的访问链接,用户无需重复上传实际的文件内容,瞬间完成 “上传” 操作。这对于一些常见的、已在服务器存储过的文件,如常用的图片、文档模板等,能够极大地节省上传时间与带宽资源。在结合 Spring Boot 与 MinIO 实现时,可在文件上传控制器中新增一个接口,用于接收文件哈希值并进行秒传判断。若文件不存在,则按照常规的分段、断点续传流程进行上传,从而实现秒传与分段上传的无缝融合。
  1. 增加文件校验:为确保上传文件的完整性与准确性,文件校验必不可少。除了在断点续传中记录已上传分片信息,防止重复上传外,还可在上传完成后,对整个文件进行校验。一种常见的方式是在客户端计算文件的哈希值,并在上传完成后,将该哈希值与服务器端重新计算的文件哈希值进行比对。若两者一致,则表明文件在传输过程中未发生损坏或篡改;若不一致,则提示用户文件可能存在问题,需要重新上传。此外,还可引入数据冗余技术,如在 MinIO 存储中采用纠删码(Erasure Coding),即使部分数据丢失或损坏,仍能通过冗余信息恢复文件,进一步提高数据的可靠性,为用户提供更加安全、可靠的文件上传服务。

七、总结

通过 Spring Boot 与 MinIO 的强强联合,我们成功实现了文件的分段、断点续传功能,有效解决了大文件上传在不稳定网络环境下的难题。这一技术方案不仅提升了文件上传的稳定性与效率,还为用户带来了更加流畅的使用体验。在实际应用中,开发者可根据具体业务场景,灵活调整分片大小、优化网络请求,进一步拓展秒传、文件校验等功能,满足多样化的需求。未来,随着云计算与大数据技术的不断发展,相信这一技术组合将在更多领域发挥重要作用,如视频直播、云存储服务、大数据分析等,为海量数据的高效传输与处理提供坚实保障,助力企业实现数字化转型与升级。

2024年的最后一篇文章 在这祝大家新年快乐,一夜暴富!!!


http://www.kler.cn/a/461596.html

相关文章:

  • 38 Opencv HOG特征检测
  • xdoj isbn号码
  • 【GO基础学习】gin的使用
  • C++ 并发专题 - std::promise 和 std::future 介绍
  • 基于Matlab的变压器仿真模型建模方法(12):单相降压自耦变压器的等效电路和仿真模型
  • 硬件工程师面试题 21-30
  • RabbitMQ生产消息【交换机、路由键】与消费消息的简单使用
  • Javascript数据结构常见面试题目(全)
  • leetcode 3046. 分割数组 简单
  • Druid密码错误重试导致数据库超慢
  • 网络安全专有名词详解_2
  • vant 地址记录
  • InfoNCE Loss详解(上)
  • Swift Combine 学习(一):Combine 初印象
  • 解析 HTTP:了解 Web 通信的基础
  • libvirt学习
  • 超大规模分类(一):噪声对比估计(Noise Contrastive Estimation, NCE)
  • 【亲测有效】k8s分布式集群安装部署
  • Scala的隐式对象和隐式类
  • 使用R语言绘制标准的中国地图和世界地图
  • Python使用matplotlib绘图时出现的中文乱码问题
  • 详细介绍如何选择云服务提供商
  • QComboBox中使用树形控件进行选择
  • 【Domain Generalization(1)】增量学习/在线学习/持续学习/迁移学习/多任务学习/元学习/领域适应/领域泛化概念理解
  • DevOps流程CICD之Jenkins使用操作
  • android知识巩固(二.非线性数据结构)