一、背景
基于一些业务场景,比如客户端需要将本地的数据文件上传服务端保存,或者进行大文件传输,现代社会中,大文件上传是一个常见的需求,因为大数据时代,数据的体量越来越大, 在这些大数据上传的场景下,或多或少都会遇到相同的技术难题,尤其是在下面的一些场景中:
1、视频、音频文件内容上传
- 短视频平台
- 音乐播放平台
- 在线教育课程平台
2、大数据上传
- 机器训练模型大数据
- 企业系统日志数据、数据库数据
- 数据仓、数据湖
3、医疗影像数据上传
- 患者的医疗检查等影像数据
- 就诊问诊、病例与治疗记录
4、软件与游戏分发
- 大型游戏安装包下载
- 游戏补丁与更新包
5、云备份与恢复
- 用户将大量重要数据备份到云端
- 云端数据恢复
二、遇到的难题:
- 文件大小限制:大数据文件通常非常大,可能达到数GB甚至更大。需要处理大文件上传的技术方案。
- 数据完整性:备份和恢复过程中需要确保数据的完整性和一致性,避免数据丢失或损坏。
- 传输效率:大规模数据备份和恢复需要高效的传输方案,减少时间消耗。
- 安全性:备份数据通常包含敏感信息,需要确保数据传输和存储的安全性。
- 可扩展性:系统需要具备良好的可扩展性,能够处理不断增长的数据量。
- 存储和处理:上传后的文件需要高效存储和处理,确保文件完整性和安全性。
三、上传、下载代码实现
比如服务端接口接收文件上传的格式如下:
{file:文件对象Author:文件作者FileDesc:文件描述CreatTime:文件创建时间
}
1、上传文件的代码Demo
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;class Program
{static async Task Main(string[] args){var filePath = "D:\\Test\\test.zip";var url = "http://localhost:5000/api/upload/uploadFile";using (var client = new HttpClient()){// 设置超时时间为10分钟client.Timeout = TimeSpan.FromMinutes(10);using (var content = new MultipartFormDataContent()){byte[] fileBytes = File.ReadAllBytes(filePath);var fileContent = new ByteArrayContent(fileBytes);fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");content.Add(fileContent, "file", Path.GetFileName(filePath));content.Add(new StringContent("张三"), "Author");content.Add(new StringContent("测试数据压缩包"), "FileDesc");content.Add(new StringContent("2024-12-15 14:23:56"), "CreatTime");try{var response = await client.PostAsync(url, content);if (response.IsSuccessStatusCode){Console.WriteLine("File uploaded successfully.");}else{Console.WriteLine($"Error: {response.StatusCode}");}}catch (Exception ex){Console.WriteLine($"An error occurred: {ex.Message}");}}}}
}
2、API接口:接收多部分表单数据(multipart/form-data)接口代码Demo
[Route("api/[controller]")]
[ApiController]
public class UploadController : ControllerBase
{[HttpPost("uploadFile")]public IActionResult Upload([FromForm] IFormFile file, [FromForm] string? Author, [FromForm] string? FileDesc, [FromForm] string? CreatTime){if(file == null || file.Length == 0){return BadRequest("No file upload!");}// 保存文件到本地目录var filePath = Path.Combine("UploadedFiles", file.FileName);Directory.CreateDirectory(Path.GetDirectoryName(filePath));using (var stream = new FileStream(filePath, FileMode.Create)){file.CopyTo(stream);}// 将字段信息生成实体类var Data = new{author = Author,fileDesc = FileDesc,creatTime = CreatTime,fileName = file.FileName,path = filePath};//保存到数据库...return Ok(new { message = "File uploaded successfully.", filePath });}
2、ASP .NET CORE 应用程序对请求体大小的限制
需要支持允许传输大数据文件,需要增加如下设置:
客户端:设置多部分表单的Body长度限制,Program.cs文件
// 配置FormOptions类: 在 ASP.NET Core 中用于配置处理表单数据的选项,特别是多部分表单(multipart/form-data)上
builder.Services.Configure<FormOptions>(options =>
{options.MultipartBodyLengthLimit = 10L * 1024 * 1024 * 1024; //10G
});
服务端:配置Kestrel服务器限制,Program.cs文件
// 配置Kestrel服务器限制
builder.WebHost.ConfigureKestrel(options =>
{options.Limits.MaxRequestBodySize = 10L * 1024 * 1024 * 1024; //10Goptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30);options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(30);
});
可以看到,同时我们还需要设置请求的超时时间很长,防止请求时间过期导致大数据传输中断。
四、难题突破
虽然上面的代码案例实现了文件的上传与下载保存,但是对于一些大文件的传输,面临的主要难题包括文件大小限制、网络不稳定、上传速度慢、数据隐私和安全、数据一致性、高并发、版本控制、带宽消耗等,需要逐一解决,才能保证大数据文件的上传过程稳定、高效、安全。
1、分块上传、断点续传、流式处理、异步处理
// GET api/<UploadController>/5
[HttpGet("OffsetUpload")]
public async void Get()
{var filePath = "D:\\TX_L10TestLog\\text.txt"; var url = "https://localhost:8085/api/Upload";var fileInfo = new FileInfo(filePath);var fileName = Path.GetFileNameWithoutExtension(filePath);long totalSize = fileInfo.Length;int chunkSize = 10 * 1024 * 1024; //分块上传,每块10MBusing(HttpClient client = new HttpClient()){//断点续传:检查服务端已经接收到的文件部分,从上次中断的位置继续上传var response = await client.GetAsync($"{url}/getOffSize?fileName={fileName}");response.EnsureSuccessStatusCode();string result = await response.Content.ReadAsStringAsync();long offset = long.Parse(result);//分块传输for(; offset < totalSize; offset += chunkSize){using(var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)){fileStream.Seek(offset, SeekOrigin.Begin);byte[] buffer = new byte[chunkSize];int bytesRead = await fileStream.ReadAsync(buffer, 0, chunkSize);using(var content = new ByteArrayContent(buffer,0, bytesRead)) {//请求头中添加Content-Range信息,以便服务端知道当前块的位置和总大小content.Headers.Add("Content-Range", $"bytes {offset}-{offset + bytesRead - 1}/{totalSize}");content.Headers.Add("File-Name", fileInfo.Name);var responeUpolad = await client.PostAsync(url+"/UploadByOffsize", content);responeUpolad.EnsureSuccessStatusCode();}}}}
}
[HttpGet("getOffSize")]
public IActionResult GetOffsize([FromQuery] string fileName)
{string UploadFolder = "uploads";var filePath = Path.Combine(UploadFolder, $"{fileName}.tmp");if (System.IO.File.Exists(filePath)){var fileinfo = new FileInfo(filePath);return Ok(fileinfo.Length.ToString());}return Ok("0");
}
// POST api/<UploadController>
[HttpPost("UploadByOffsize")]
public async Task<IActionResult> UploadByOffsize()
{var request = HttpContext.Request;//检查并解析Content-Range和File-Name头信息if (!request.Headers.ContainsKey("Content-Range") || !request.Headers.ContainsKey("File-Name")){return BadRequest("Missing headers");}var contentRange = request.Headers["Content-Range"].ToString();var range = contentRange.Split(new char[] { ' ', '-', '/' }, StringSplitOptions.RemoveEmptyEntries);long start = long.Parse(range[1]);long end = long.Parse(range[2]);long total = long.Parse(range[3]);string UploadFolder = "uploads";var fileName = request.Headers["File-Name"].ToString();var filePath = Path.Combine(UploadFolder, $"{fileName}.tmp");Directory.CreateDirectory(Path.GetDirectoryName(filePath));using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)){//使用FileStream以追加模式写入文件,将接收到的块数据写入指定位置fileStream.Seek(start, SeekOrigin.Begin);await request.Body.CopyToAsync(fileStream);}//如果接收到的块是最后一块,则完成文件上传并重命名文件if (end + 1 == total){// File upload completedSystem.IO.File.Move(filePath, Path.Combine(UploadFolder, fileName));}return Ok();
}
2、考虑压缩,采用更精确的压缩算法
压缩原理:
- TAR:只是简单地将多个文件和目录合并成一个文件,不进行任何压缩。
- GZIP:使用DEFLATE算法进行压缩,这是一种基于LZ77和哈夫曼编码的无损数据压缩算法。
- ZIP:也使用DEFLATE算法,但每个文件单独压缩,并且包含文件目录信息以支持随机访问。
压缩效率:
- TGZ通常会有更好的压缩率,特别是在处理大量小文件时
tgz压缩代码实现:
public class TgzHelperClass
{public void myCreateTgz(string sourceFilePath, string tgzFilePath){// 先创建一个.tar的打包文件string tarFilePath = Path.ChangeExtension(tgzFilePath, ".tar");// 首先将文件打包成.tarusing (var tarStream = File.OpenWrite(tarFilePath)){using (var tarArchive = TarArchive.Create()){tarArchive.AddEntry(Path.GetFileName(sourceFilePath), sourceFilePath);tarArchive.SaveTo(tarStream, new WriterOptions(CompressionType.None));}}// 然后使用GZIP算法将.tar打包文件压缩成.tgz文件using (var tarStream = new FileStream(tarFilePath, FileMode.Open, FileAccess.Read))using (var tgzStream = new FileStream(tgzFilePath, FileMode.Create, FileAccess.Write))using (var gzipStream = new GZipStream(tgzStream, CompressionLevel.Optimal)){tarStream.CopyTo(gzipStream);}// 最后删除临时中间.tar文件File.Delete(tarFilePath);}
}
五、总结
在这些业务场景中,大文件上传面临的主要难题包括文件大小限制、网络不稳定、上传速度慢、数据隐私和安全、数据一致性、高并发处理、版本控制、带宽消耗、数据预处理、成本控制等。为了应对这些挑战,技术方案需要综合考虑分块上传、断点续传、流式处理、异步处理、数据加密、压缩传输、负载均衡等技术手段,确保大文件上传过程稳定、高效、安全。
