​Spring Boot 分片上传文件

在文件上传功能上,客户经常会问到文件上传大小的是否有限制或是上传大小能否设置,用户经常需要上传好几个G的资料文件,如图纸,视频等,并且需要在上传大文件过程中进行优化实时展现进度条,
进行技术评估后针对框架文件上传进行扩展升级,扩展接口支持大文件分片上传处理,减少服务器瞬时的内存压力,同一个文件上传失败后可以从成功上传分片位置进行断点续传,文件上传成功后再次上传无需等待达到秒传的效果,优化用户交互体验。

文件MD5计算
对于文件md5的计算我们使用spark-md5第三方库,大文件我们可以分片分别计算再合并节省时间。
但是经测试1G文件计算MD5需要20s左右的时间,所以经过优化我们抽取文件部分特征信息(文件第一片+文件最后一片+文件修改时间),来保证文件的相对唯一性,只需要2s左右,大大提高前端计算效率,对于前端文件内容块的读取我们需要使用html5的api中fileReader.readAsArrayBuffer方法,因为是异步触发,封装的方法提供一个回调函数进行使用。

createSimpleFileMD5(file, chunkSize, finishCaculate) {var fileReader = new FileReader();var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;var chunks = Math.ceil(file.size / chunkSize);var currentChunk = 0;var spark = new SparkMD5.ArrayBuffer();var startTime = new Date().getTime();loadNext();fileReader.onload = function() {spark.append(this.result);if (currentChunk == 0) {currentChunk = chunks - 1;loadNext();} else {var fileMD5 = hpMD5(spark.end() + file.lastModifiedDate);finishCaculate(fileMD5)}};functionloadNext() {var start = currentChunk * chunkSize;var end = start + chunkSize >= file.size ? file.size : start + chunkSize;fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));}}

文件分片切割
我们通过定义好文件分片大小,使用blob对象支持的file.slice方法切割文件,分片上传请求需要同步按顺序请求,因为使用了同步请求,前端ui会阻塞无法点击,需要开启worker线程进行操作,完成后通过postMessage方法传递消息给主页面通知ui进度条的更新,需要注意的是,worker线程方法不支持window对象,所以尽量不要使用第三方库,使用原生的XMLHttpRequest对象发起请求,需要的参数通过onmessage方法传递获取。

upload() {var file = document.getElementById("file").files[0];if (!file) {alert("请选择需要上传的文件");return;}if (file.size < pageData.chunkSize) {alert("选择的文件请大于" + pageData.chunkSize / 1024 / 1024 + "M")}var filesize = file.size;var filename = file.name;pageData.chunkCount = Math.ceil(filesize / pageData.chunkSize);this.createSimpleFileMD5(file, pageData.chunkSize, function(fileMD5) {console.log("计算文件MD:" + fileMD5);pageData.showProgress = true;var worker = new Worker('worker.js');var param = {token: GetTokenID(),uploadUrl: uploadUrl,filename: filename,filesize: filesize,fileMD5: fileMD5,groupguid: pageData.groupguid1,grouptype: pageData.grouptype1,chunkCount: pageData.chunkCount,chunkSize: pageData.chunkSize,file: file}worker.onmessage = function(event) {var workresult = event.data;if (workresult.code == 0) {pageData.percent = workresult.percent;if (workresult.percent == 100) {pageData.showProgress = false;worker.terminate();}} else {pageData.showProgress = false;worker.terminate();}}worker.postMessage(param);})}

worker.js执行方法如下:

functionFormAjax_Sync(token, data, url, success) {var xmlHttp = new XMLHttpRequest();xmlHttp.open("post", url, false);xmlHttp.setRequestHeader("token", token);xmlHttp.onreadystatechange = function() {if (xmlHttp.status == 200) {var result = JSON.parse(this.responseText);var status = this.statussuccess(result, status);}};xmlHttp.send(data);}onmessage = function(evt) {var data = evt.data;console.log(data)//传递的参数var token = data.tokenvar uploadUrl = data.uploadUrlvar filename = data.filenamevar fileMD5 = data.fileMD5var groupguid = data.groupguidvar grouptype = data.grouptypevar chunkCount = data.chunkCountvar chunkSize = data.chunkSizevar filesize = data.filesizevar filename = data.filenamevar file = data.filevar start = 0;var end;var index = 0;var startTime = new Date().getTime();while (start < filesize) {end = start + chunkSize;if (end > filesize) {end = filesize;}var chunk = file.slice(start, end); //切割文件var formData = new FormData();formData.append("file", chunk, filename);formData.append("fileMD5", fileMD5);formData.append("chunkCount", chunkCount)formData.append("chunkIndex", index);formData.append("chunkSize", end - start);formData.append("groupguid", groupguid);formData.append("grouptype", grouptype);//上传文件FormAjax_Sync(token, formData, uploadUrl, function(result, status) {var code = 0;var percent = 0;if (result.code == 0) {console.log("分片共" + chunkCount + "个" + ",已成功上传第" + index + "个")percent = parseInt((parseInt(formData.get("chunkIndex")) + 1) * 100 / chunkCount);} else {filesize = -1;code = -1console.log("分片第" + index + "个上传失败")}self.postMessage({ code: code, percent: percent });})start = end;index++;}console.log("上传分片总时间:" + (new Date().getTime() - startTime));console.log("分片完成");
}

文件分片接收
前端文件分片处理完毕后,接下来我们详细介绍下后端文件接受处理的方案,分片处理需要支持用户随时中断上传与文件重复上传,我们新建表f_attachchunk来记录文件分片的详细信息,表结构设计如下:

CREATE TABLE `f_attachchunk` (`ID` int(11) NOT NULL AUTO_INCREMENT,`ChunkGuid` varchar(50) NOT NULL,`FileMD5` varchar(100) DEFAULT NULL,`FileName` varchar(200) DEFAULT NULL,`ChunkSize` int(11) DEFAULT NULL,`ChunkCount` int(11) DEFAULT NULL,`ChunkIndex` int(11) DEFAULT NULL,`ChunkFilePath` varchar(500) DEFAULT NULL,`UploadUserGuid` varchar(50) DEFAULT NULL,`UploadUserName` varchar(100) DEFAULT NULL,`UploadDate` datetime DEFAULT NULL,`UploadOSSID` varchar(200) DEFAULT NULL,`UploadOSSChunkInfo` varchar(1000) DEFAULT NULL,`ChunkType` varchar(50) DEFAULT NULL,`MergeStatus` int(11) DEFAULT NULL,PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8mb4;
FileMD5:文件MD5唯一标识文件;
FileName:文件名称;
ChunkSize:分片大小;
ChunkCount:分片总数量;
ChunkIndex:分片对应序号;
ChunkFilePath:分片存储路径(本地存储文件方案使用);
UploadUserGuid:上传人主键;
UploadUserName:上传人姓名;
UploadDate:上传人日期;
UploadOSSID:分片上传批次ID(云存储方案使用);
UploadOSSChunkInfo:分片上传单片信息(云存储方案使用);
ChunkType:分片存储方式(本地存储,阿里云,华为云,Minio标识);
MergeStatus:分片合并状态(未合并,已合并)。
文件分片存储后端一共分为三步:
检查分片=》保存分片=》合并分片
我们这里先以本地文件存储为例讲解,云存储思路一致,后续会提供对应使用的api方法。

检查分片
检查分片以数据库文件分片记录的FIleMD5与ChunkIndex组合来确定分片的唯一性,因为本地分片temp文件是作为临时文件存储,可能会出现手动清除施放磁盘空间的问题,所以数据库存在记录我们还需要对应的检查实际文件情况。

boolean existChunk = false;AttachChunkDO dbChunk = attachChunkService.checkExistChunk(fileMD5, chunkIndex, "Local");if (dbChunk != null) {File chunkFile = new File(dbChunk.getChunkFilePath());if (chunkFile.exists()) {if (chunkFile.length() == chunkSize) {existChunk = true;} else {//删除数据库记录attachChunkService.delete(dbChunk.getChunkGuid());}} else {//删除数据库记录attachChunkService.delete(dbChunk.getChunkGuid());}}

保存分片
保存分片分为两块,文件存储到本地,成功后数据库插入对应分片信息。


//获取配置中附件上传文件夹String filePath = frameConfig.getAttachChunkPath() + "/" + fileMD5 + "/";//根据附件guid创建文件夹File targetFile = new File(filePath);if (!targetFile.exists()) {targetFile.mkdirs();}if (!existChunk) {//保存文件到文件夹String chunkFileName = fileMD5 + "-" + chunkIndex + ".temp";FileUtil.uploadFile(FileUtil.convertStreamToByte(fileContent), filePath, chunkFileName);//插入chunk表AttachChunkDO attachChunkDO = new AttachChunkDO(fileMD5, fileName, chunkSize, chunkCount, chunkIndex, filePath + chunkFileName, "Local");attachChunkService.insert(attachChunkDO);}

合并分片
在上传分片方法中,如果当前分片是最后一片,上传完毕后进行文件合并工作,同时进行数据库合并状态的更新,下一次同一个文件上传时我们可以直接拷贝之前合并过的文件作为新附件,减少合并这一步骤的I/O操作,合并文件我们采用BufferedOutputStream与BufferedInputStream两个对象,固定缓冲区大小。

if (chunkIndex == chunkCount - 1) {//合并文件String merageFileFolder = frameConfig.getAttachPath() + groupType + "/" + attachGuid;File attachFolder = new File(merageFileFolder);if (!attachFolder.exists()) {attachFolder.mkdirs();}String merageFilePath = merageFileFolder + "/" + fileName;merageFile(fileMD5, merageFilePath);attachChunkService.updateMergeStatusToFinish(fileMD5);//插入到附件库//设置附件唯一guidattachGuid = CommonUtil.getNewGuid();attachmentDO.setAttguid(attachGuid);attachmentService.insert(attachmentDO);}//java fhadmin.cn     publicvoidmerageFile(String fileMD5, String targetFilePath)throws Exception {String merageFilePath = frameConfig.getAttachChunkPath()+"/"+fileMD5+"/"+fileMD5+".temp";File merageFile = new File(merageFilePath);if(!merageFile.exists()){BufferedOutputStream destOutputStream = new BufferedOutputStream(new FileOutputStream(merageFilePath));List<AttachChunkDO> attachChunkDOList = attachChunkService.selectListByFileMD5(fileMD5, "Local");for (AttachChunkDO attachChunkDO : attachChunkDOList) {File file = new File(attachChunkDO.getChunkFilePath());byte[] fileBuffer = new byte[1024 * 1024 * 5];//文件读写缓存int readBytesLength = 0; //每次读取字节数BufferedInputStream sourceInputStream = new BufferedInputStream(new FileInputStream(file));while ((readBytesLength = sourceInputStream.read(fileBuffer)) != -1) {destOutputStream.write(fileBuffer, 0, readBytesLength);}sourceInputStream.close();}destOutputStream.flush();destOutputStream.close();}FileUtil.copyFile(merageFilePath,targetFilePath);}

云文件分片上传
云文件上传与本地文件上传的区别就是,分片文件直接上传到云端,再调用云存储api进行文件合并与文件拷贝,数据库相关记录与检查差异不大。

阿里云OSS
上传分片前需要生成该文件的分片上传组标识uploadid

 public String getUplaodOSSID(String key){key = "chunk/" + key + "/" + key;TenantParams.attach appConfig = getAttach();OSSClient ossClient = InitOSS(appConfig);String bucketName = appConfig.getBucketname_auth();InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);String uploadId = upresult.getUploadId();ossClient.shutdown();return uploadId;}

上传分片时需要指定uploadid,同时我们要将返回的分片信息PartETag序列化保存数据库,用于后续的文件合并。

  public String uploadChunk(InputStream stream,String key, int chunkIndex, int chunkSize, String uploadId){key = "chunk/" + key + "/" + key;String result = "";try{TenantParams.attach appConfig = getAttach();OSSClient ossClient = InitOSS(appConfig);String bucketName = appConfig.getBucketname_auth();UploadPartRequest uploadPartRequest = new UploadPartRequest();uploadPartRequest.setBucketName(bucketName);uploadPartRequest.setKey(key);uploadPartRequest.setUploadId(uploadId);uploadPartRequest.setInputStream(stream);// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。uploadPartRequest.setPartSize(chunkSize);// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。uploadPartRequest.setPartNumber(chunkIndex+1);// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);PartETag partETag = uploadPartResult.getPartETag();result = JSON.toJSONString(partETag);ossClient.shutdown();}catch (Exception e){logger.error("OSS上传文件Chunk失败:" + e.getMessage());}return result;}

合并分片时通过传递保存分片的PartETag对象数组进行操作,为了附件独立唯一性我们不直接使用合并后的文件,通过api进行文件拷贝副本使用。

public boolean merageFile(String uploadId, List<PartETag> chunkInfoList,String key,AttachmentDO attachmentDO,boolean checkMerge){key = "chunk/" + key + "/" + key;boolean result = true;try{TenantParams.attach appConfig = getAttach();OSSClient ossClient = InitOSS(appConfig);String bucketName = appConfig.getBucketname_auth();if(!checkMerge){CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, key, uploadId, chunkInfoList);CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);}String attachKey = getKey(attachmentDO);ossClient.copyObject(bucketName,key,bucketName,attachKey);ossClient.shutdown();}catch (Exception e){e.printStackTrace();logger.error("OSS合并文件失败:" + e.getMessage());result = false;}return result;}

华为云OBS
华为云api与阿里云api大致相同,只有个别参数名称不同,直接上代码:

 public String getUplaodOSSID(String key) throws Exception {key = "chunk/" + key + "/" + key;TenantParams.attach appConfig = getAttach();ObsClient obsClient = InitOBS(appConfig);String bucketName = appConfig.getBucketname_auth();InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);String uploadId = result.getUploadId();obsClient.close();return uploadId;}public String uploadChunk(InputStream stream, String key, int chunkIndex, int chunkSize, String uploadId) {key = "chunk/" + key + "/" + key;String result = "";try {TenantParams.attach appConfig = getAttach();ObsClient obsClient = InitOBS(appConfig);String bucketName = appConfig.getBucketname_auth();UploadPartRequest uploadPartRequest = new UploadPartRequest();uploadPartRequest.setBucketName(bucketName);uploadPartRequest.setUploadId(uploadId);uploadPartRequest.setObjectKey(key);uploadPartRequest.setInput(stream);uploadPartRequest.setOffset(chunkIndex * chunkSize);// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。uploadPartRequest.setPartSize((long) chunkSize);// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。uploadPartRequest.setPartNumber(chunkIndex + 1);// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest);PartEtag partETag = new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber());result = JSON.toJSONString(partETag);obsClient.close();} catch (Exception e) {e.printStackTrace();logger.error("OBS上传文件Chunk:" + e.getMessage());}return result;}public boolean merageFile(String uploadId, List<PartEtag> chunkInfoList, String key, AttachmentDO attachmentDO, boolean checkMerge) {key = "chunk/" + key + "/" + key;boolean result = true;try {TenantParams.attach appConfig = getAttach();ObsClient obsClient = InitOBS(appConfig);String bucketName = appConfig.getBucketname_auth();if (!checkMerge) {CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, key, uploadId, chunkInfoList);obsClient.completeMultipartUpload(request);}String attachKey = getKey(attachmentDO);obsClient.copyObject(bucketName, key, bucketName, attachKey);obsClient.close();} catch (Exception e) {e.printStackTrace();logger.error("OBS合并文件失败:" + e.getMessage());result = false;}return result;}

Minio
文件存储Minio应用比较广泛,框架也同时支持了自己独立部署的Minio文件存储系统,Minio没有对应的分片上传api支持,我们可以在上传完分片文件后,使用composeObject方法进行文件的合并。

publicbooleanuploadChunk(InputStream stream, String key, int chunkIndex){boolean result = true;try {MinioClient minioClient = InitMinio();String bucketName = frameConfig.getMinio_bucknetname();PutObjectOptions option = new PutObjectOptions(stream.available(), -1);key = "chunk/" + key + "/" + key;minioClient.putObject(bucketName, key + "-" + chunkIndex, stream, option);} catch (Exception e) {logger.error("Minio上传Chunk文件失败:" + e.getMessage());result = false;}return result;}publicbooleanmerageFile(String key, int chunkCount, AttachmentDO attachmentDO, boolean checkMerge){boolean result = true;try {MinioClient minioClient = InitMinio();String bucketName = frameConfig.getMinio_bucknetname();key = "chunk/" + key + "/" + key;if (!checkMerge) {List<ComposeSource> sourceObjectList = new ArrayList<ComposeSource>();for (int i = 0; i < chunkCount; i++) {ComposeSource composeSource = ComposeSource.builder().bucket(bucketName).object(key + "-" + i).build();sourceObjectList.add(composeSource);}minioClient.composeObject(ComposeObjectArgs.builder().bucket(bucketName).object(key).sources(sourceObjectList).build());}String attachKey = getKey(attachmentDO);minioClient.copyObject(CopyObjectArgs.builder().bucket(bucketName).object(attachKey).source(CopySource.builder().bucket(bucketName).object(key).build()).build());} catch (Exception e) {logger.error("Minio合并文件失败:" + e.getMessage());result = false;}return result;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言——数组转换

将的两行三列数组转换为三行两列的数组 #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int a[2][3]{{1,2,3},{4,5,6}};int b[3][2],i,j;for ( i 0; i <1; i){for ( j 0; j <2; j){printf("%5d",a[i][j]);b[j][i]a[i][j];}printf(&…

LTD253次升级 | 官网“活动“增报名 • 名片展示个人简介 • 合伙人设置个性邀请码

1、活动类型支持报名&#xff1b; 2、产品详情页支持房产类型产品的地图显示&#xff1b; 3、官微名片独立版支持个人简介&#xff1b; 4、多语言系统支持挪威语&#xff1b; 5、极速官微首页提速、合伙人页面优化&#xff1b; 6、 已知问题优化与修复&#xff1b; 01网站编辑器…

Mysql8.1.0 安装问题-缺少visual studio 2019x64组件

缺少visual studio x64组件的问题 使用Mysql8以上的安装包mysql-8.1.0-winx64.msi进行安装&#xff0c; 提示缺少visual studio 2019 x64可再发行组件 在微软官网下载vc可再发行程序包 Microsoft Visual C 可再发行程序包最新支持的下载 在Visual Studio 2015、2017、2019 和…

Arch Linux 安装 dwm 窗口管理器

窗口管理器是管理桌面上各种窗口的组件&#xff0c;主要功能有&#xff1a;窗口堆叠方式&#xff0c;窗口移动规则等。大多数人接触到的是堆叠式窗口管理器&#xff0c;一个窗口可以叠放在其他窗口之上&#xff0c;调整窗口的主要方式是鼠标。而dwm&#xff08;Dynamic Window …

【C++】异常处理 ① ( 异常概念引入 | 抛出异常语法 | 捕获异常语法 | 异常捕获流程 | 异常处理代码示例 )

文章目录 一、异常处理1、异常概念引入2、抛出异常语法3、捕获异常语法4、异常捕获流程 二、异常处理代码示例1、错误代码示例 - 抛出异常 / 不捕获异常2、正确代码示例 - 抛出异常 / 捕获异常3、正确代码示例 - 抛出异常 / 捕获异常不处理继续抛出异常 一、异常处理 1、异常概…

端口隔离度

端口隔离度 隔离度为&#xff08;本振或射频信号&#xff09;泄漏到其他端口的功率与输入功率之比&#xff0c;单位是dB。 比如 RF to LO Isolation 表示 射频输入信号的功率 与 泄漏到LO端口的功率 之比。 而 LO to RF Isolation 则表示 本振输入信号的功率 与 泄漏到RF端口的…

Cytoscape学习教程

写在前面 今天分享的内容是自己遇到问题后,咨询社群里面的同学,帮忙解决的总结。 关于Cytoscape,对于做组学或生物信息学的同学基本是陌生的,可能有的同学用这个软件作图是非常溜的,做出来的网络图也是十分的好看,“可玩性”很高,就像前面分享的aPEAR包一样aPEAR包绘制…

List操作的一些常见问题

文章目录 阿里巴巴开发手册强制规约&#xff1a;1. Arrays.asList转换基本类型数组2. Arrays.asList返回的List不支持增删操作3. 对原始数组的修改会影响到我们获得的那个List4. ArrayList.subList强转ArrayList导致异常5. ArrayList中的subList切片造成OOM6.Copy-On-Write 是什…

C语言——从键盘输人一个表示年份的整数,判断该年份是否为闰年,并显示判断结果。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int year 0;printf("请输入年份&#xff1a;");scanf("%d",&year);if((year%4 0) && (year%100!0) || (year%400 0)){printf("%d是闰年\n",year);}else{p…

QML Label 属性 pyside6

基本属性 text: 显示的文本内容 Label {text: "Hello, World!" }color: 文本颜色 Label {color: "blue" }font: 字体设置&#xff0c;包括 family、pointSize、weight 和 style Label {font.family: "Arial"font.pointSize: 20font.bold: tr…

set与map

set与map 一、序列式容器与关联式容器二、pair1、键值对2、作用3、构造函数4、make_pair&#xff08;1&#xff09;构造函数&#xff08;2&#xff09;作用 5、代码6、运行结果 三、set1、概念2、代码3、运行结果4、说明 四、multiset1、与set的关系2、代码3、运行结果 五、map…

【新手解答1】深入探索 C 语言:变量名、形参 + 主调函数、被调函数 + 类和对象 + 源文件(.c 文件)、头文件(.h 文件)+ 库

C语言的相关问题解答 写在最前面目录 问题1变量名与变量的关系与区别变量和数据类型形参&#xff08;形式参数&#xff09;的概念 问题2解析&#xff1a;主调函数和被调函数延伸解析&#xff1a;主调函数对于多文件程序的理解总结 问题3类和对象变量和数据类型变量是否为抽象的…

ArcGIS中基于人口数据计算人口密度的方法

文章目录 一、密度分析原理二、点密度分析三、线密度分析四、核密度分析一、密度分析原理 密度分析是指根据输入的要素数据集计算整个区域的数据聚集状况,从而产生一个联系的密度表面。通过密度计算,将每个采样点的值散步到整个研究区域,并获得输出栅格中每个像元的密度值。…

嵌入式八股 | 校招秋招 | 笔试面试 | 精选题目

欢迎关注微信公众号【赛博二哈】获取八股PDF 并加入嵌入式求职交流群。提供简历模板、学习路线、岗位整理等 欢迎加入知识星球【嵌入式求职星球】获取完整嵌入式八股。 提供简历修改、项目推荐、求职规划答疑。另有各城市、公司岗位、笔面难题、offer选择、薪资爆料等 嵌入式…

分布式数据恢复-hbase+hive分布式存储误删除如何恢复数据?

hbasehive分布式存储数据恢复环境&#xff1a; 16台某品牌R730XD服务器节点&#xff0c;每台物理服务器节点上有数台虚拟机&#xff0c;虚拟机上配置的分布式&#xff0c;上层部署hbase数据库hive数据仓库。 hbasehive分布式存储故障&初检&#xff1a; 数据库文件被误删除…

十八、初识elasticsearsh (索引)

目录 一、Elasticsearch的介绍&#xff1a; 二、正向索引和倒排索引 1、正向索引 2、倒排索引 3、索引(index) :相同类型的文档的集合 4、映射(mapping):索引中文档的字段约束信息&#xff0c;类似表的结构约束 一、Elasticsearch的介绍&#xff1a; Elasticsearch是一个…

企业数字化转型的作用是什么?_光点科技

在当今快速变化的商业环境中&#xff0c;数字化转型已成为企业发展的重要策略。企业数字化转型指的是利用数字技术改造传统业务模式和管理方式&#xff0c;以提升效率、增强竞争力和创造新的增长机会。 提升运营效率&#xff1a;数字化转型通过引入自动化工具和智能系统&#x…

JVM——产生内存溢出原因

目录 1.产生内存溢出原因一 &#xff1a;代码中的内存泄漏1.案例1&#xff1a;equals()和hashCode()导致的内存泄漏问题&#xff1a;**正常情况**&#xff1a;**异常情况&#xff1a;**解决方案&#xff1a; 2.案例2&#xff1a;内部类引用外部类问题&#xff1a;解决方案&…

计算机网络——网络可靠性及网络出口配置

1. 前言&#xff1a; 学习目标&#xff1a; 1.了解链路聚合的作用 2. 了解ACL的工作原理 3. 了解NAT的工作原理和配置 2. 网络可靠性方案 网络可靠性是指网络在面对各种异常情况或故障时&#xff0c;能够维持正常运行和提供服务的能力。这包括防止网络中断、减小数据丢失的可能…

深入Python元编程:了解声明与初始化定制元类

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 简介 在Python中&#xff0c;元编程是指在运行时创建或定制类的编程。元类是Python中最强大的元编程工具之一&#xff0c;允许您控制类的创建过程。元类是类的类&#xff0c;它控制类的实例化&#xff0c;允许您…