后台服务接口间大文件的流式发送和读取

news2024/11/16 21:44:31

文章目录

  • 介绍
  • 代码设计
  • 代码参考
    • 客户端代码
    • 服务器端代码
    • 测试实例

介绍

使用HTTP协议进行数据流式传输是一种常见的方法。对于大文件数据传输可以使用HTTP的chunked编码或使用多部分响应来实现数据流式传输。
【HTTP的chunked编码】在发送数据的服务中,可以将数据切分为较小的块,并使用HTTP的chunked编码将这些块发送给接收数据的服务。接收数据的服务在接收到每个块时可以进行相应的处理。
【使用多部分响应】发送数据的服务可以将数据分割为多个部分,并使用多部分响应将这些部分作为独立的消息发送给接收数据的服务。接收数据的服务可以逐个处理这些部分。
使用流式传输时,您需要确保发送数据的服务和接收数据的服务都能处理流式数据,并且网络环境和服务器配置允许流式传输。

代码设计

【1】本次实现的是大文件的接口传输,大约2G,但是在项目中为了避免占用内存过大影响其他接口,所以测试代码限制了总体的项目的JVM参数为-Xms100m -Xmx100m
【2】客户端读取本地大文件,因为读取的文件过大,如果一次性读取到内存会OOM异常,所以使用了BufferedInputStream读取,当从输入流中读取数据时,BufferedInputStream 会一次性读取更多的数据,并将其存储在内部缓冲区中。然后,当从 BufferedInputStream 中读取数据时,它将从缓冲区中返回数据,而不是每次都直接从底层输入流中读取数据。通过使用缓冲区,BufferedInputStream 可以减少对底层输入流的直接读取次数,从而提高读取性能。更大的缓冲区大小通常意味着更少的 I/O 操作,但也会占用更多的内存。bufferSize 参数用于指定 BufferedInputStream 内部缓冲区的大小,影响读取操作的性能和内存占用。
【3】本次测试程序中读取缓存大小和发送数据块的大小设置都是5M左右,具体设置可以根据项目情况决定。
【4】客户端每读取完一次缓冲区数据后就发送给服务端接口。
【5】设置ContentType为application/octet-stream。application/octet-stream 是一种通用的 MIME 类型,用于表示未知的二进制数据流。它是一个字节流的二进制数据类型,没有指定具体的数据格式或文件类型。使用 application/octet-stream MIME 类型时,接收方可能需要根据其他信息(例如文件扩展名、附加的元数据等)来推断数据的实际类型和处理方式。
【6】因为需要传输多个数据块,客户端需要传递一个文件的唯一标识,用户服务端接口将不同的客户端发来的数据进行匹配和追加写入。
【7】服务端接口接收文件,根据传入的唯一标识,当前代码通过文件名标识的,决定是否创建不存在的文件。
【8】如果插入的文件已存在,则表示当前传入的是该文件的后续数据块,则进行追加写入,此处使用了RandomAccessFile,然后将文件指针移到末尾写入数据块。
【9】依次执行完上述操作后,完成写入,上面的主要存在的问题是对于大文件存在网络异常或者其他原因导致某一个块文件传输失败,对于这个文件客户端和服务端可以进行数据块索引的匹配验证,在传输后进行验证,如果传输没有问题则成功,如果失败可以选择全部重新传输,或者仅传输失败的那一个编号的数据块。

代码参考

客户端代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.io.*;
import java.util.UUID;


/**
 *  后台服务接口间大文件的流式发送和读取
 *
 * @author zhangyu
 * @date 2023/6/11
 */
@Slf4j
@RequestMapping("/stream")
@RestController
public class DataStreamClientController {

    public static final String SEND_FILE="/Users/zhangyu/code/test_data/demo2.mp4";

    /**
     * 每个块的大小
     */
    private static final int CHUNK_SIZE = 5242880;

    @GetMapping("/split/send")
    public String sendFileBySplit() throws IOException {
        sendDataToService();
        return "ok";
    }

    public void sendDataToService() throws IOException {
        // 设置文件名称
        String fileName = UUID.randomUUID() +"."+ getFileExtension(SEND_FILE);
        log.info("文件:{} 开始发送",fileName);
        // 读取文件,设置缓冲区读取,避免一次读取OOM
        try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(SEND_FILE),CHUNK_SIZE)) {
            byte[] buffer = new byte[CHUNK_SIZE];
            int bytesRead;
            int chunkIndex=0;
            while ((bytesRead = inputStream.read(buffer, 0, CHUNK_SIZE)) != -1) {
                chunkIndex++;
                processChunk(fileName, buffer, bytesRead, chunkIndex);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        log.info("文件:{} 发送完成",fileName);

    }

    private static void processChunk(String fileName, byte[] buffer, int bytesRead, int chunkIndex) {
        // 创建RestTemplate
        RestTemplate restTemplate = new RestTemplate();
        // 设置请求头application/octet-stream
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 分块传输数据给服务
        byte[] chunk = new byte[bytesRead];
        System.arraycopy(buffer, 0, chunk, 0, bytesRead);
        // 构建请求实体
        HttpEntity<byte[]> requestEntity = new HttpEntity<>(chunk, headers);
        // 发送数据块给服务A
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(
                "http://127.0.0.1:9999/stream/upload?fileName=" + fileName,
                requestEntity,
                String.class
        );
        // 处理响应
        if (responseEntity.getStatusCode().is2xxSuccessful()) {
            // 响应成功
            log.info("数据分块 {} 发送成功", (chunkIndex + 1) );
        } else {
            // 响应失败
            log.error("数据分块 {} 发送失败", (chunkIndex + 1) );
        }
    }

    public static String getFileExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf(".");
        if (dotIndex == -1 || dotIndex == fileName.length() - 1) {
            return "";
        } else {
            return fileName.substring(dotIndex + 1);
        }
    }
}

服务器端代码


import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 *  后台服务接口间大文件的流式发送和读取
 *
 * @author zhangyu
 * @date 2023/6/11
 */
@RequestMapping("/stream")
@Slf4j
@RestController
public class DataStreamServerController {

    private static final String UPLOAD_DIR = "demo";

    @PostMapping("/upload")
    public ResponseEntity<String> uploadDataChunk(@RequestBody byte[] dataChunk, @RequestParam("fileName") String fileName) {
        try {
            log.info("接收到上传文件:{}",fileName);
            // 处理数据块,例如将数据写入文件
            writeDataChunkToFile(dataChunk, fileName);
            // 返回成功响应
            return ResponseEntity.ok("数据上传成功");
        } catch (Exception e) {
            // 处理上传失败情况
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("数据块处理异常: " + e.getMessage());
        }
    }

    private void writeDataChunkToFile(byte[] dataChunk,String curFileName) throws IOException {
        // 判断文件是否已创建,如果未创建,则进行初始化
        if (!Files.exists(getFilePath(curFileName))) {
            initializeFile(curFileName);
        }
        File file = new File(UPLOAD_DIR, curFileName);
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
            // 将文件指针移到末尾
            raf.seek(raf.length());
            // 写入数据块
            raf.write(dataChunk);
        }
        log.info("完成文件读取写入:{}",curFileName);
    }

    /**
     * 初始化文件
     */
    private void initializeFile(String curFileName) throws IOException {
        Path filePath = getFilePath(curFileName);
        Files.createDirectories(filePath.getParent());
        Files.createFile(filePath);
    }

    /**
     * 获取文件路径
     */
    private Path getFilePath(String curFileName) {
        Path uploadDir = Paths.get(UPLOAD_DIR);
        return uploadDir.resolve(curFileName);
    }
}

测试实例

【1】分别设置客户端和服务器端项目的JVM参数: -Xms100m -Xmx100m
【2】客户端发送文件如下2G大小,总体的传输时间5S左右,因为设置的分块很小,所以传输交互很多次,可以根据应用的内存大小进行调整优化。
【3】完整代码:
https://github.com/zwzhangyu/ZyCodeHub/tree/main/spring-code-hub/mvc
【4】代码运行截图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

postman接口测试学习笔记(非常详细)

目录 引言 1. 资源 2.接口测试 3. 接口返回数据和JSON详解 4.接口测试协议 5.企业接口测试的流程和方案 6. 接口测试工具以及Postman介绍 7.Postman之内置的动态参数和应用 8.接口关联 引言 接口测试工具如何选择&#xff1f;在我看来&#xff0c;Postman是一个非常值…

电商--红包活动总结

文章目录 背景表结构设计实现流程流程图流程解析流程优化 背景 这是一个来自于NFT电商项目&#xff0c;这是个营销策略的需求&#xff0c;为了快狠准&#xff0c;短期内刺激消费&#xff0c;拉动销售增加购买量。运营人员可以在平台创建红包&#xff0c;并且设置该红包的发放时…

Android系统视角下对APK的分析(1)- 静态分析APK文件

声明 以Android手机用户角度来看&#xff0c;安装各式各样的APP&#xff0c;基本就是从应用市场上 “搜索->下载->安装” 三连。而对Android系统来说&#xff0c;这就是个大工程了&#xff0c;因为对Android系统来说APK是“外来户”&#xff0c;如何安装它、有限制地支持…

【用户认证】密码加密,用户状态保存,cookie,session,token

相关概念 认证与授权 认证&#xff08;authentication &#xff09;是验证你的身份的过程&#xff0c;而授权&#xff08;authorization&#xff09;是验证你有权访问的过程 用户认证的逻辑 获取用户提交的用户名和密码根据用户名&#xff0c;查询数据库&#xff0c;获得完…

redis知识复习

redis知识复习 redis基础知识redis的认识一. 非关系型数据库 与 传统数据库 的区别二. 安装redis并设置自启动三. 熟悉命令行客户端四.熟悉图形化工具RDM redis的命令与数据结构一. 数据结构介绍二. redis通用命令&#xff08;熟练掌握&#xff09; redis的Java客户端一. Jedis…

文心一言 VS 讯飞星火 VS chatgpt (38)-- 算法导论5.4 2题

二、假设我们将球投入到b个箱子里&#xff0c;直到某个箱子中有两个球。每一次投掷都是独立的并且每个球落入任何箱子的机会均等。请问投球次数期望是多少? 文心一言&#xff1a; 这是一个典型的鸽巢原理&#xff08;Pigeonhole Principle&#xff09;问题。假设每次投球时&…

2023年上半年系统分析师下午真题及答案解析

试题一(25分) (试题一为必答题&#xff0c;试题二~五选答两题) 某软件公司拟开发一套汽车租赁系统&#xff0c;科学、安全和方便的管理租赁公司的各项业务&#xff0c;提高公司效率&#xff0c;提升利用率。注册用户在使用系统进行车辆预约时需执行以下操作&#xff1a;(a)用…

从0-1一起学习live555设计思想之二 RTSP交互过程

流媒体服务系列 文章目录 流媒体服务系列前言一、OPTION二、DESCRIBE三、SETUP四、PLAY总结前言 本篇文章通过代码去分析rtsp交互过程与工作原理。由于live555的继承关系太过复杂,所以做了个图简单记录一下与h264文件传输相关的类继承关系。 一、OPTION OPTION比较简单,就…

【调制BFSK】二进制频移键控FSK的数字调制(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ARM学习(22)断点认识以及调试

笔者来聊聊断点以及断点的调试 1、断点原理 断电的原理一般分为两种&#xff0c;插入断点指令或者利用硬件调试寄存器进行断点。 前者程序如果在RAM&#xff08;SRAM、DDR&#xff09;上&#xff0c;则调试器可以直接在断点地址处插入断点指令&#xff0c;例如BKPT&#xff0…

python自动化测试-自动化基本技术原理

1 概述 在之前的文章里面提到过&#xff1a;做自动化的首要本领就是要会 透过现象看本质 &#xff0c;落实到实际的IT工作中就是 透过界面看数据。 掌握上面的这样的本领可不是容易的事情&#xff0c;必须要有扎实的计算机理论基础&#xff0c;才能看到深层次的本质东西。 …

家用电器-空调制冷、制热、除霜、除湿、换新风的基本原理及实现讲解

目录 一、空调历史 二、空调的作用 三、空调类型 四、基本原理 4.1 制冷过程 4.2 制热过程 4.3 除霜过程 4.4 除湿过程 4.5 换气过程 五、电路控制系统 六、核心部件 七、基本指标 1&#xff09;气候类型 2&#xff09;额定制冷量 3&#xff09;能效比 八、市场…

4年功能庸庸碌碌,进阶自动化测试拿到了24k,测试之路不再平凡...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 手工测试如何进阶…

多业务聚合查询设计思路与实践

文章目录 [toc] 1.需求2.方案2.1 方案架构图2.2 选用flink-cdc的原因 3.实践3.1 环境准备3.3 es集群搭建3.4 flink1.14.0环境搭建3.5 准备sql和jar包3.5.1[创建mysql的flink用户并授权](https://ververica.github.io/flink-cdc-connectors/master/content/connectors/mysql-cdc…

【树形DP+直径思想】代码源每日一题div1 三进制循环

三进制循环 - 题目 - Daimayuan Online Judge 题意&#xff1a; 思路&#xff1a; 有点像树的直径 回顾一下我们是怎么求直径的&#xff1a;维护根节点到其子树上的点的最大距离和次大距离&#xff0c;然后答案就是统计所有结点的次大值最大值 的 最大值 这道题也是一样的&a…

C盘爆满时的几个救命无害清理技巧

其实网上也有很多清理C盘的方法 但是很多就是为了弄成空间 不讲原理 也不计后果 很可能坑惨小伙伴 可以看到 我电脑的C盘都已经读红条了 非常危险 对了 可能有些朋友的电脑上没有此电脑的选项 我们可以在桌面上右键选择 个性化 找到 主题 并选择 向下拉 找到 桌面图标设置 …

经验总结:13 条自动化测试框架设计原则!

1.代码规范 测试框架随着业务推进&#xff0c;必然会涉及代码的二次开发&#xff0c;所以代码编写应符合通用规范&#xff0c;代码命名符合业界标准&#xff0c;并且代码层次清晰。特别在大型项目、多人协作型项目中&#xff0c;如果代码没有良好的规范&#xff0c;那么整个框…

微服务之负载均衡

Informal Essay By English I wish the students of the college entrance examination can win the gold toad and win the title of the gold list 参考书籍&#xff1a;“凤凰架构” 负载均衡&#xff08;load balance&#xff09; 负载平衡是指在一组后端服务器&#xf…

《计算机网络——自顶向下方法》精炼——4.4.1-4.4.2

敬教劝学&#xff0c;建国之大本;兴贤育才&#xff0c;为政之先务。——《朱舜水集。劝学》 文章目录 IPv4编址接口IP地址子网 无类别域间路由选择&#xff08;CIDR&#xff09;获取一块地址获取主机地址网络地址转换 IPv4编址 接口 主机或路由器与物理链路的边界称作接口。一…

深度学习卷积神经网络CNN之ResNet模型网络详解说明(超详细理论篇)

1.ResNet背景 2. ResNet论文 3. ResNet模型结构 4. ResNet优缺点 一、ResNet背景 ResNet 在2015 年由微软研究院提出的一种深度卷积神经网络结构&#xff0c;在ILSVRC&#xff08;ImageNet Large Scale Visual Recognition Challenge&#xff09;中取得了冠军&#xff08;分类…