提交 ff7e8a3f 编写于 作者: 沉默王二's avatar 沉默王二 💬

面试题

上级 7df5ab49
......@@ -400,16 +400,19 @@
> - **学习了那么多 Java 知识,耗费了无数的脑细胞,熬掉了无数根秀发,为的是什么?当然是谋取一份心仪的 offer 了**。那八股文、面试题、城市选择、优质面经又怎能少得了呢?
> - 千淘万漉虽辛苦,吹尽狂沙始到金。
## 精选面&八股文
## 面试题&八股文
- [34 道 Java 精选面试题👍](docs/interview/java-34.md)
- [13 道 Java HashMap 精选面试题👍](docs/interview/java-hashmap-13.md)
- [12 道 Redis 精选面试题👍](docs/interview/redis-12.md)
- [40 道 Nginx 精选面试题👍](docs/interview/nginx-40.md)
- [60 道 MySQL 精选面试题👍](docs/interview/mysql-60.md)
- [Java 基础背诵版八股文必看🍉](docs/baguwen/java-basic.md)
- [Java 并发编程背诵版八股文必看🍉](docs/baguwen/java-thread.md)
- [Java 虚拟机背诵版八股文必看🍉](docs/baguwen/jvm.md)
- [Java 基础背诵版八股文必看🍉](docs/interview/java-basic-baguwen.md)
- [Java 并发编程背诵版八股文必看🍉](docs/interview/java-thread-baguwen.md)
- [Java 虚拟机背诵版八股文必看🍉](docs/interview/java-jvm-baguwen.md)
- [携程面试官👤:大文件上传时如何做到秒传?](docs/interview/mianshiguan-bigfile-miaochuan.md)
- [阿里面试官👤:为什么要分库分表?](docs/interview/mianshiguan-fenkufenbiao.md)
- [淘宝面试官👤:优惠券系统该如何设计?](docs/interview/mianshiguan-youhuiquan.md)
## 优质面经
......@@ -454,17 +457,17 @@
## PDF下载
- [👏下载→2022年全网最全关于程序员学习和找工作的PDF资源](docs/nice-article/itmind/miansjavamsdhmsmsbdjavabdjavaxxzlmsxxzlmszlzlxzmszlfxjlzl.md)
- [👏下载→深入浅出Java多线程PDF](docs/nice-article/weixin-shenrjcjavabfbchwjdhl.md)
- [👏下载→GitHub星标115k+的Java教程](docs/nice-article/weixin-githubxbkdjavajccjyh.md)
- [👏下载→重学Java设计模式PDF](docs/nice-article/weixin-shejmsnb.md)
- [👏下载→Java版LeetCode刷题笔记](docs/nice-article/weixin-mozdsdzfjavableetcodetxxk.md)
- [👏下载→阿里巴巴Java开发手册](docs/nice-article/weixin/sulwgalcpdssbjavakfsc.md)
- [👏下载→阮一峰C语言入门教程](docs/nice-article/yuanyifeng-c-language.md)
- [👏下载→BAT大佬的刷题笔记](docs/nice-article/bat-shuati.md)
- [👏下载→给操作系统捋条线PDF](docs/nice-article/weixin-piaolwzjztqdtskwlzfpdf.md)
- [👏下载→豆瓣9.1分的Pro Git中文版](docs/nice-article/progit.md)
- [👏下载→简历模板](docs/nice-article/jianli.md)
- [👏下载→2022年全网最全关于程序员学习和找工作的PDF资源](docs/pdf/programmer-111.md)
- [👏下载→深入浅出Java多线程PDF](docs/pdf/java-concurrent.md)
- [👏下载→GitHub星标115k+的Java教程](docs/pdf/github-java-jiaocheng-115-star.md)
- [👏下载→重学Java设计模式PDF](docs/pdf/shejimoshi.md)
- [👏下载→Java版LeetCode刷题笔记](docs/pdf/java-leetcode.md)
- [👏下载→阿里巴巴Java开发手册](docs/pdf/ali-java-shouce.md)
- [👏下载→阮一峰C语言入门教程](docs/pdf/yuanyifeng-c-language.md)
- [👏下载→BAT大佬的刷题笔记](docs/pdf/bat-shuati.md)
- [👏下载→给操作系统捋条线PDF](docs/pdf/os.md)
- [👏下载→豆瓣9.1分的Pro Git中文版](docs/pdf/progit.md)
- [👏下载→简历模板](docs/pdf/jianli.md)
## 学习建议
......@@ -474,14 +477,13 @@
- [如何填报计算机大类高考填志愿,计科、人工智能、软工、大数据、物联网、网络工程该怎么选?](docs/xuexijianyi/gaokao-zhiyuan-cs.md)
- [测试开发工程师必读经典书籍有哪些?](docs/xuexijianyi/test-programmer-read-books.md)
- [校招 Java 后端开发应该掌握到什么程度?](docs/xuexijianyi/xiaozhao-java-should-master.md)
- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](docs/xuexijianyi/benksrhcnjrtxaldyldhlwgs.md)
- [大裁员下,程序员如何做“副业”?](docs/xuexijianyi/chengxuyuan-fuye.md)
- [如何在繁重的工作中持续成长?](docs/xuexijianyi/ruhzfzdgzzcxcz.md)
- [如何获得高并发的经验?](docs/xuexijianyi/gaobingfa-jingyan-hsmcomputer.md)
- [怎么跟 HR 谈薪资?](docs/xuexijianyi/hr-xinzi.md)
- [程序员 35 岁危机,如何破局?](docs/xuexijianyi/35-weiji.md)
- [大厂的优惠券系统是如何设计的?](docs/xuexijianyi/youhuiquan.md)
- [不到 20 人的 IT 公司该去吗?](docs/xuexijianyi/20ren-it-quma.md)
- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](docs/xuexijianyi/benkesheng-ali-tengxun.md)
# 知识库搭建
......
......@@ -703,6 +703,9 @@ export const sidebarConfig = sidebar({
"java-basic-baguwen",
"java-thread-baguwen",
"java-jvm-baguwen",
"mianshiguan-bigfile-miaochuan",
"mianshiguan-fenkufenbiao",
"mianshiguan-youhuiquan",
],
},
{
......@@ -773,16 +776,19 @@ export const sidebarConfig = sidebar({
collapsable: true,
prefix: "xuexijianyi/",
children: [
"LearnCS-ByYourself",
"read-csapp",
"electron-information-engineering",
"gaokao-zhiyuan-cs",
"test-programmer-read-books",
"xiaozhao-java-should-master",
"benksrhcnjrtxaldyldhlwgs",
"chengxuyuan-fuye",
"ruhzfzdgzzcxcz",
"gaobingfa-jingyan-hsmcomputer",
"hr-xinzi",
"20ren-it-quma",
"35-weiji",
"benkesheng-ali-tengxun",
],
},
{
......
......@@ -412,7 +412,7 @@ head:
**学习了那么多 Java 知识,耗费了无数的脑细胞,熬掉了无数根秀发,为的是什么?当然是谋取一份心仪的 offer 了**。那八股文、面试题、城市选择、优质面经又怎能少得了呢?千淘万漉虽辛苦,吹尽狂沙始到金。
### 精选面试题
### 面试题&八股文
- [34 道 Java 精选面试题👍](interview/java-34.md)
- [13 道 Java HashMap 精选面试题👍](interview/java-hashmap-13.md)
......@@ -422,6 +422,9 @@ head:
- [Java 基础背诵版八股文必看🍉](interview/java-basic-baguwen.md)
- [Java 并发编程背诵版八股文必看🍉](interview/java-thread-baguwen.md)
- [Java 虚拟机背诵版八股文必看🍉](interview/java-jvm-baguwen.md)
- [携程面试官👤:大文件上传时如何做到秒传?](interview/mianshiguan-bigfile-miaochuan.md)
- [阿里面试官👤:为什么要分库分表?](interview/mianshiguan-fenkufenbiao.md)
- [淘宝面试官👤:优惠券系统该如何设计?](interview/mianshiguan-youhuiquan.md)
### 优质面经
......@@ -464,13 +467,11 @@ head:
- [如何填报计算机大类高考填志愿,计科、人工智能、软工、大数据、物联网、网络工程该怎么选?](xuexijianyi/gaokao-zhiyuan-cs.md)
- [测试开发工程师必读经典书籍有哪些?](xuexijianyi/test-programmer-read-books.md)
- [校招 Java 后端开发应该掌握到什么程度?](xuexijianyi/xiaozhao-java-should-master.md)
- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](xuexijianyi/benksrhcnjrtxaldyldhlwgs.md)
- [大裁员下,程序员如何做“副业”?](xuexijianyi/chengxuyuan-fuye.md)
- [如何在繁重的工作中持续成长?](xuexijianyi/ruhzfzdgzzcxcz.md)
- [如何获得高并发的经验?](xuexijianyi/gaobingfa-jingyan-hsmcomputer.md)
- [怎么跟 HR 谈薪资?](xuexijianyi/hr-xinzi.md)
- [程序员 35 岁危机,如何破局?](xuexijianyi/35-weiji.md)
- [大厂的优惠券系统是如何设计的?](xuexijianyi/youhuiquan.md)
- [不到 20 人的 IT 公司该去吗?](xuexijianyi/20ren-it-quma.md)
- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](xuexijianyi/benkesheng-ali-tengxun.md)
- [大厂的优惠券系统是如何设计的?](xuexijianyi/youhuiquan.md)
......
--- title: 携程面试官👤:大文件上传时如何做到秒传? shortTitle: 大文件上传时如何做到秒传? category: - 求职面试 tag: - 面试题&八股文 head: - - meta - name: keywords content: Java,java,面试题,八股文 --- 大家好,我是二哥呀~ 文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用这种方式进行上传,可不是一个好的办法,毕竟很少有用户能忍受,尤其是当文件上传到一半中断后,继续上传却只能重头开始上传,让用户的体验尤其不爽。 那有没有比较好的上传体验呢,答案有的,就是下边要介绍的几种上传方式。 ## 秒传 ### 1、什么是秒传 通俗的说,你把要上传的东西上传,服务器会先做 MD5 校验,如果服务器上有同样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让 MD5 改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5 就变了,就不会秒传了. ### 2、本文实现的秒传核心逻辑 a、利用 redis 的 set 方法存放文件上传状态,其中 key 为文件上传的 md5,value 为是否上传完成的标志位; b、当标志位为 true 表示上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为 false,则说明还没上传完成,此时需要再调用 set 方法,保存块号文件记录的路径,其中 key 为上传文件的 md5 + 一个固定前缀,value 为块号文件的记录路径 ## 分片上传 ### 1、什么是分片上传 分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。 ### 2、分片上传的场景 1.大文件上传 2.网络环境环境不好,存在需要重传风险的场景 ## 断点续传 ### 1、什么是断点续传 断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。 PS:本文的断点续传主要是针对断点上传场景。 ### 2、应用场景 断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。 ### 3、实现断点续传的核心逻辑 在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。 为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端也可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传。 ### 4、实现流程步骤 a、方案一,常规步骤 - 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块; - 初始化一个分片上传任务,返回本次分片上传唯一标识; - 按照一定的策略(串行或并行)发送各个分片数据块; - 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。 b、方案二、本文实现的步骤 - 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小 - 服务端创建 conf 文件用来记录分块位置,conf 文件长度为总分片数,每上传一个分块即向 conf 文件中写入一个 127,那么没上传的位置就是默认的 0,已上传的就是 Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤) - 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件。 #### 5、分片上传/断点上传代码实现 a、前端采用百度提供的 webuploader 插件,进行分片。因本文主要介绍服务端代码实现,webuploader 如何进行分片,具体实现可以查看如下链接: [http://fex.baidu.com/webuploader/getting-started.html](http://fex.baidu.com/webuploader/getting-started.html) b、后端用两种方式实现文件写入,一种是用 RandomAccessFile,如果对 RandomAccessFile 不熟悉的朋友,可以查看如下链接: [https://blog.csdn.net/dimudan2015/article/details/81910690](https://blog.csdn.net/dimudan2015/article/details/81910690) 另一种是使用 MappedByteBuffer,对 MappedByteBuffer 不熟悉的朋友,可以查看如下链接进行了解: [https://www.jianshu.com/p/f90866dcbffc](https://www.jianshu.com/p/f90866dcbffc) ## 后端进行写入操作的核心代码 ### 1、RandomAccessFile 实现方式 ``` @UploadMode(mode = UploadModeEnum.RANDOM_ACCESS)   @Slf4j   public class RandomAccessUploadStrategy extends SliceUploadTemplate {        @Autowired     private FilePathUtil filePathUtil;        @Value("${upload.chunkSize}")     private long defaultChunkSize;        @Override     public boolean upload(FileUploadRequestDTO param) {       RandomAccessFile accessTmpFile = null;       try {         String uploadDirPath = filePathUtil.getPath(param);         File tmpFile = super.createTmpFile(param);         accessTmpFile = new RandomAccessFile(tmpFile, "rw");         //这个必须与前端设定的值一致         long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024             : param.getChunkSize();         long offset = chunkSize * param.getChunk();         //定位到该分片的偏移量         accessTmpFile.seek(offset);         //写入该分片数据         accessTmpFile.write(param.getFile().getBytes());         boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);         return isOk;       } catch (IOException e) {         log.error(e.getMessage(), e);       } finally {         FileUtil.close(accessTmpFile);       }      return false;     }      }   ``` ### 2、MappedByteBuffer 实现方式 ``` @UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER)   @Slf4j   public class MappedByteBufferUploadStrategy extends SliceUploadTemplate {        @Autowired     private FilePathUtil filePathUtil;        @Value("${upload.chunkSize}")     private long defaultChunkSize;        @Override     public boolean upload(FileUploadRequestDTO param) {          RandomAccessFile tempRaf = null;       FileChannel fileChannel = null;       MappedByteBuffer mappedByteBuffer = null;       try {         String uploadDirPath = filePathUtil.getPath(param);         File tmpFile = super.createTmpFile(param);         tempRaf = new RandomAccessFile(tmpFile, "rw");         fileChannel = tempRaf.getChannel();            long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024             : param.getChunkSize();         //写入该分片数据         long offset = chunkSize * param.getChunk();         byte[] fileData = param.getFile().getBytes();         mappedByteBuffer = fileChannel   .map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);         mappedByteBuffer.put(fileData);         boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);         return isOk;          } catch (IOException e) {         log.error(e.getMessage(), e);       } finally {            FileUtil.freedMappedByteBuffer(mappedByteBuffer);         FileUtil.close(fileChannel);         FileUtil.close(tempRaf);          }          return false;     }      }   ``` ### 3、文件操作核心模板类代码 ``` @Slf4j   public abstract class SliceUploadTemplate implements SliceUploadStrategy {        public abstract boolean upload(FileUploadRequestDTO param);        protected File createTmpFile(FileUploadRequestDTO param) {          FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);       param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));       String fileName = param.getFile().getOriginalFilename();       String uploadDirPath = filePathUtil.getPath(param);       String tempFileName = fileName + "_tmp";       File tmpDir = new File(uploadDirPath);       File tmpFile = new File(uploadDirPath, tempFileName);       if (!tmpDir.exists()) {         tmpDir.mkdirs();       }       return tmpFile;     }        @Override     public FileUploadDTO sliceUpload(FileUploadRequestDTO param) {          boolean isOk = this.upload(param);       if (isOk) {         File tmpFile = this.createTmpFile(param);         FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile);         return fileUploadDTO;       }       String md5 = FileMD5Util.getFileMD5(param.getFile());          Map<Integer, String> map = new HashMap<>();       map.put(param.getChunk(), md5);       return FileUploadDTO.builder().chunkMd5Info(map).build();     }        /**      * 检查并修改文件上传进度      */     public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) {          String fileName = param.getFile().getOriginalFilename();       File confFile = new File(uploadDirPath, fileName + ".conf");       byte isComplete = 0;       RandomAccessFile accessConfFile = null;       try {         accessConfFile = new RandomAccessFile(confFile, "rw");         //把该分段标记为 true 表示完成         System.out.println("set part " + param.getChunk() + " complete");         //创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE 127         accessConfFile.setLength(param.getChunks());         accessConfFile.seek(param.getChunk());         accessConfFile.write(Byte.MAX_VALUE);            //completeList 检查是否全部完成,如果数组里是否全部都是127(全部分片都成功上传)         byte[] completeList = FileUtils.readFileToByteArray(confFile);         isComplete = Byte.MAX_VALUE;         for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {           //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE           isComplete = (byte) (isComplete & completeList[i]);           System.out.println("check part " + i + " complete?:" + completeList[i]);         }          } catch (IOException e) {         log.error(e.getMessage(), e);       } finally {         FileUtil.close(accessConfFile);       }    boolean isOk = setUploadProgress2Redis(param, uploadDirPath, fileName, confFile, isComplete);       return isOk;     }        /**      * 把上传进度信息存进redis      */     private boolean setUploadProgress2Redis(FileUploadRequestDTO param, String uploadDirPath,         String fileName, File confFile, byte isComplete) {          RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);       if (isComplete == Byte.MAX_VALUE) {         redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "true");         redisUtil.del(FileConstant.FILE_MD5_KEY + param.getMd5());         confFile.delete();         return true;       } else {         if (!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS, param.getMd5())) {           redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "false");           redisUtil.set(FileConstant.FILE_MD5_KEY + param.getMd5(),               uploadDirPath + FileConstant.FILE_SEPARATORCHAR + fileName + ".conf");         }            return false;       }     }   /**      * 保存文件操作      */     public FileUploadDTO saveAndFileUploadDTO(String fileName, File tmpFile) {          FileUploadDTO fileUploadDTO = null;          try {            fileUploadDTO = renameFile(tmpFile, fileName);         if (fileUploadDTO.isUploadComplete()) {           System.out               .println("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName);           //TODO 保存文件信息到数据库            }          } catch (Exception e) {         log.error(e.getMessage(), e);       } finally {          }       return fileUploadDTO;     }   /**      * 文件重命名      *      * @param toBeRenamed 将要修改名字的文件      * @param toFileNewName 新的名字      */     private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) {       //检查要重命名的文件是否存在,是否是文件       FileUploadDTO fileUploadDTO = new FileUploadDTO();       if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {         log.info("File does not exist: {}", toBeRenamed.getName());         fileUploadDTO.setUploadComplete(false);         return fileUploadDTO;       }       String ext = FileUtil.getExtension(toFileNewName);       String p = toBeRenamed.getParent();       String filePath = p + FileConstant.FILE_SEPARATORCHAR + toFileNewName;       File newFile = new File(filePath);       //修改文件名       boolean uploadFlag = toBeRenamed.renameTo(newFile);          fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());       fileUploadDTO.setUploadComplete(uploadFlag);       fileUploadDTO.setPath(filePath);       fileUploadDTO.setSize(newFile.length());       fileUploadDTO.setFileExt(ext);       fileUploadDTO.setFileId(toFileNewName);          return fileUploadDTO;     }   }   ``` ## 总结 在实现分片上传的过程,需要前端和后端配合,比如前后端上传块号的文件大小,前后端必须得要一致,否则上传就会有问题。其次文件相关操作正常都是要搭建一个文件服务器的,比如使用 fastdfs、hdfs 等。 本示例代码在电脑配置为 4 核内存 8G 情况下,上传 24G 大小的文件,上传时间需要 30 多分钟,主要时间耗费在前端的 md5 值计算,后端写入的速度还是比较快。 如果项目组觉得自建文件服务器太花费时间,且项目的需求仅仅只是上传下载,那么推荐使用阿里的 oss 服务器,其介绍可以查看官网: [https://help.aliyun.com/product/31815.html](https://help.aliyun.com/product/31815.html) 阿里的 oss 它本质是一个对象存储服务器,而非文件服务器,因此如果有涉及到大量删除或者修改文件的需求,oss 可能就不是一个好的选择。 文末提供一个 oss 表单上传的链接 demo,通过 oss 表单上传,可以直接从前端把文件上传到 oss 服务器,把上传的压力都推给 oss 服务器: [https://www.cnblogs.com/ossteam/p/4942227.html](https://www.cnblogs.com/ossteam/p/4942227.html) ---- 最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) 关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 ![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
---
title: 阿里二面:为什么要分库分表?
title: 阿里面试官👤:为什么要分库分表?
shortTitle: 为什么要分库分表?
description: 在高并发系统当中,分库分表是必不可少的技术手段之一,同时也是BAT等大厂面试时,经常考的热门考题。
author: 苏三呀
tags:
- 学习建议
category:
- 学习建议
- 求职面试
tag:
- 面试题&八股文
head:
- - meta
- name: keywords
content: Java,java,面试题,八股文
---
## 前言
......
---
title: 大厂的优惠券系统是如何设计的
title: 淘宝面试官👤:优惠券系统该如何设计
shortTitle: 大厂的优惠券系统是如何设计的?
description: 大厂的优惠券系统是如何设计的?
tags:
......
......@@ -791,7 +791,6 @@ Proxy_set_header THE-TIME $date_gmt;
看完这 40 个 Nginx 的面试题,小二是感激涕零,感动的一塌糊涂。我拍了拍他的肩膀,安慰他说:“加油,未来是你的。”
----
>原文链接:[blog.csdn.net/wuzhiwei549/article/details/122758937](blog.csdn.net/wuzhiwei549/article/details/122758937),整理:沉默王二
......
--- title: 大文件上传时如何做到 秒传? shortTitle: 大文件上传时如何做到 秒传? description: 大文件上传时如何做到 秒传? tags: - 学习建议 category: - 学习建议 head: - - meta - name: keywords content: 大文件,秒传 --- 大家好,我是二哥呀~ 文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用这种方式进行上传,可不是一个好的办法,毕竟很少有用户能忍受,尤其是当文件上传到一半中断后,继续上传却只能重头开始上传,让用户的体验尤其不爽。 那有没有比较好的上传体验呢,答案有的,就是下边要介绍的几种上传方式。 ## 秒传 ### 1、什么是秒传 通俗的说,你把要上传的东西上传,服务器会先做 MD5 校验,如果服务器上有同样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让 MD5 改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5 就变了,就不会秒传了. ### 2、本文实现的秒传核心逻辑 a、利用 redis 的 set 方法存放文件上传状态,其中 key 为文件上传的 md5,value 为是否上传完成的标志位; b、当标志位为 true 表示上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为 false,则说明还没上传完成,此时需要再调用 set 方法,保存块号文件记录的路径,其中 key 为上传文件的 md5 + 一个固定前缀,value 为块号文件的记录路径 ## 分片上传 ### 1、什么是分片上传 分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。 ### 2、分片上传的场景 1.大文件上传 2.网络环境环境不好,存在需要重传风险的场景 ## 断点续传 ### 1、什么是断点续传 断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。 PS:本文的断点续传主要是针对断点上传场景。 ### 2、应用场景 断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。 ### 3、实现断点续传的核心逻辑 在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。 为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端也可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传。 ### 4、实现流程步骤 a、方案一,常规步骤 - 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块; - 初始化一个分片上传任务,返回本次分片上传唯一标识; - 按照一定的策略(串行或并行)发送各个分片数据块; - 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。 b、方案二、本文实现的步骤 - 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小 - 服务端创建 conf 文件用来记录分块位置,conf 文件长度为总分片数,每上传一个分块即向 conf 文件中写入一个 127,那么没上传的位置就是默认的 0,已上传的就是 Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤) - 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件。 #### 5、分片上传/断点上传代码实现 a、前端采用百度提供的 webuploader 插件,进行分片。因本文主要介绍服务端代码实现,webuploader 如何进行分片,具体实现可以查看如下链接: [http://fex.baidu.com/webuploader/getting-started.html](http://fex.baidu.com/webuploader/getting-started.html) b、后端用两种方式实现文件写入,一种是用 RandomAccessFile,如果对 RandomAccessFile 不熟悉的朋友,可以查看如下链接: [https://blog.csdn.net/dimudan2015/article/details/81910690](https://blog.csdn.net/dimudan2015/article/details/81910690) 另一种是使用 MappedByteBuffer,对 MappedByteBuffer 不熟悉的朋友,可以查看如下链接进行了解: [https://www.jianshu.com/p/f90866dcbffc](https://www.jianshu.com/p/f90866dcbffc) ## 后端进行写入操作的核心代码 ### 1、RandomAccessFile 实现方式 ``` @UploadMode(mode = UploadModeEnum.RANDOM_ACCESS)   @Slf4j   public class RandomAccessUploadStrategy extends SliceUploadTemplate {        @Autowired     private FilePathUtil filePathUtil;        @Value("${upload.chunkSize}")     private long defaultChunkSize;        @Override     public boolean upload(FileUploadRequestDTO param) {       RandomAccessFile accessTmpFile = null;       try {         String uploadDirPath = filePathUtil.getPath(param);         File tmpFile = super.createTmpFile(param);         accessTmpFile = new RandomAccessFile(tmpFile, "rw");         //这个必须与前端设定的值一致         long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024             : param.getChunkSize();         long offset = chunkSize * param.getChunk();         //定位到该分片的偏移量         accessTmpFile.seek(offset);         //写入该分片数据         accessTmpFile.write(param.getFile().getBytes());         boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);         return isOk;       } catch (IOException e) {         log.error(e.getMessage(), e);       } finally {         FileUtil.close(accessTmpFile);       }      return false;     }      }   ``` ### 2、MappedByteBuffer 实现方式 ``` @UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER)   @Slf4j   public class MappedByteBufferUploadStrategy extends SliceUploadTemplate {        @Autowired     private FilePathUtil filePathUtil;        @Value("${upload.chunkSize}")     private long defaultChunkSize;        @Override     public boolean upload(FileUploadRequestDTO param) {          RandomAccessFile tempRaf = null;       FileChannel fileChannel = null;       MappedByteBuffer mappedByteBuffer = null;       try {         String uploadDirPath = filePathUtil.getPath(param);         File tmpFile = super.createTmpFile(param);         tempRaf = new RandomAccessFile(tmpFile, "rw");         fileChannel = tempRaf.getChannel();            long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024             : param.getChunkSize();         //写入该分片数据         long offset = chunkSize * param.getChunk();         byte[] fileData = param.getFile().getBytes();         mappedByteBuffer = fileChannel   .map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);         mappedByteBuffer.put(fileData);         boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);         return isOk;          } catch (IOException e) {         log.error(e.getMessage(), e);       } finally {            FileUtil.freedMappedByteBuffer(mappedByteBuffer);         FileUtil.close(fileChannel);         FileUtil.close(tempRaf);          }          return false;     }      }   ``` ### 3、文件操作核心模板类代码 ``` @Slf4j   public abstract class SliceUploadTemplate implements SliceUploadStrategy {        public abstract boolean upload(FileUploadRequestDTO param);        protected File createTmpFile(FileUploadRequestDTO param) {          FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);       param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));       String fileName = param.getFile().getOriginalFilename();       String uploadDirPath = filePathUtil.getPath(param);       String tempFileName = fileName + "_tmp";       File tmpDir = new File(uploadDirPath);       File tmpFile = new File(uploadDirPath, tempFileName);       if (!tmpDir.exists()) {         tmpDir.mkdirs();       }       return tmpFile;     }        @Override     public FileUploadDTO sliceUpload(FileUploadRequestDTO param) {          boolean isOk = this.upload(param);       if (isOk) {         File tmpFile = this.createTmpFile(param);         FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile);         return fileUploadDTO;       }       String md5 = FileMD5Util.getFileMD5(param.getFile());          Map<Integer, String> map = new HashMap<>();       map.put(param.getChunk(), md5);       return FileUploadDTO.builder().chunkMd5Info(map).build();     }        /**      * 检查并修改文件上传进度      */     public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) {          String fileName = param.getFile().getOriginalFilename();       File confFile = new File(uploadDirPath, fileName + ".conf");       byte isComplete = 0;       RandomAccessFile accessConfFile = null;       try {         accessConfFile = new RandomAccessFile(confFile, "rw");         //把该分段标记为 true 表示完成         System.out.println("set part " + param.getChunk() + " complete");         //创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE 127         accessConfFile.setLength(param.getChunks());         accessConfFile.seek(param.getChunk());         accessConfFile.write(Byte.MAX_VALUE);            //completeList 检查是否全部完成,如果数组里是否全部都是127(全部分片都成功上传)         byte[] completeList = FileUtils.readFileToByteArray(confFile);         isComplete = Byte.MAX_VALUE;         for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {           //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE           isComplete = (byte) (isComplete & completeList[i]);           System.out.println("check part " + i + " complete?:" + completeList[i]);         }          } catch (IOException e) {         log.error(e.getMessage(), e);       } finally {         FileUtil.close(accessConfFile);       }    boolean isOk = setUploadProgress2Redis(param, uploadDirPath, fileName, confFile, isComplete);       return isOk;     }        /**      * 把上传进度信息存进redis      */     private boolean setUploadProgress2Redis(FileUploadRequestDTO param, String uploadDirPath,         String fileName, File confFile, byte isComplete) {          RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);       if (isComplete == Byte.MAX_VALUE) {         redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "true");         redisUtil.del(FileConstant.FILE_MD5_KEY + param.getMd5());         confFile.delete();         return true;       } else {         if (!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS, param.getMd5())) {           redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "false");           redisUtil.set(FileConstant.FILE_MD5_KEY + param.getMd5(),               uploadDirPath + FileConstant.FILE_SEPARATORCHAR + fileName + ".conf");         }            return false;       }     }   /**      * 保存文件操作      */     public FileUploadDTO saveAndFileUploadDTO(String fileName, File tmpFile) {          FileUploadDTO fileUploadDTO = null;          try {            fileUploadDTO = renameFile(tmpFile, fileName);         if (fileUploadDTO.isUploadComplete()) {           System.out               .println("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName);           //TODO 保存文件信息到数据库            }          } catch (Exception e) {         log.error(e.getMessage(), e);       } finally {          }       return fileUploadDTO;     }   /**      * 文件重命名      *      * @param toBeRenamed 将要修改名字的文件      * @param toFileNewName 新的名字      */     private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) {       //检查要重命名的文件是否存在,是否是文件       FileUploadDTO fileUploadDTO = new FileUploadDTO();       if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {         log.info("File does not exist: {}", toBeRenamed.getName());         fileUploadDTO.setUploadComplete(false);         return fileUploadDTO;       }       String ext = FileUtil.getExtension(toFileNewName);       String p = toBeRenamed.getParent();       String filePath = p + FileConstant.FILE_SEPARATORCHAR + toFileNewName;       File newFile = new File(filePath);       //修改文件名       boolean uploadFlag = toBeRenamed.renameTo(newFile);          fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());       fileUploadDTO.setUploadComplete(uploadFlag);       fileUploadDTO.setPath(filePath);       fileUploadDTO.setSize(newFile.length());       fileUploadDTO.setFileExt(ext);       fileUploadDTO.setFileId(toFileNewName);          return fileUploadDTO;     }   }   ``` ## 总结 在实现分片上传的过程,需要前端和后端配合,比如前后端上传块号的文件大小,前后端必须得要一致,否则上传就会有问题。其次文件相关操作正常都是要搭建一个文件服务器的,比如使用 fastdfs、hdfs 等。 本示例代码在电脑配置为 4 核内存 8G 情况下,上传 24G 大小的文件,上传时间需要 30 多分钟,主要时间耗费在前端的 md5 值计算,后端写入的速度还是比较快。 如果项目组觉得自建文件服务器太花费时间,且项目的需求仅仅只是上传下载,那么推荐使用阿里的 oss 服务器,其介绍可以查看官网: [https://help.aliyun.com/product/31815.html](https://help.aliyun.com/product/31815.html) 阿里的 oss 它本质是一个对象存储服务器,而非文件服务器,因此如果有涉及到大量删除或者修改文件的需求,oss 可能就不是一个好的选择。 文末提供一个 oss 表单上传的链接 demo,通过 oss 表单上传,可以直接从前端把文件上传到 oss 服务器,把上传的压力都推给 oss 服务器: [https://www.cnblogs.com/ossteam/p/4942227.html](https://www.cnblogs.com/ossteam/p/4942227.html) ---- 最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) 关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 ![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
---
title: 大裁员下,程序员如何做“副业”?
shortTitle: 大裁员下,程序员如何做“副业”?
description: 最近大厂裁员不断,好多人思考要不要搞个副业抵御风险。那你知道程序员的副业怎么做吗?来和我一探究竟吧~
category:
- 学习建议
tags:
- 程序员
- 学习建议
author: 杨成功
category:
- 掘金社区
head:
- - meta
- name: description
content: 最近大厂裁员不断,好多人思考要不要搞个副业抵御风险。那你知道程序员的副业怎么做吗?来和我一探究竟吧~
- - meta
- name: keywords
content: 程序员,前端
---
......
---
title: 电子信息工程最好的出路的是什么?
shortTitle: 电子信息工程最好的出路的是什么?
description: 电子信息工程最好的出路的是什么?
tags:
- 学习建议
category:
- 学习建议
head:
- - meta
- name: keywords
content: 电子信息工程
---
......
---
title: 如何获得高并发的经验?
shortTitle: 如何获得高并发的经验?
description: 如何获得高并发的经验?先说结论。1 首先得在自己电脑上鼓捣出Redis,kafka,dubbo,mycat等高并发分布式组件的环境,通过这些环…
tags:
- 互联网
- 高并发
- 程序员
- 编程
- Java 程序员
- 学习建议
author: hsmcomputer
category:
- 知乎
head:
- - meta
- name: description
content: 如何获得高并发的经验?先说结论。1 首先得在自己电脑上鼓捣出Redis,kafka,dubbo,mycat等高并发分布式组件的环境,通过这些环…
- - meta
- name: keywords
content: 互联网,程序员,编程,高并发,Java 程序员
- 学习建议
---
**先说结论。**
......
---
title: 如何阅读《深入理解计算机系统》这本书?
shortTitle: 如何阅读《CSAPP》这本书?
description: 如何阅读《深入理解计算机系统》这本书?
tags:
- 学习建议
category:
- 学习建议
head:
- - meta
- name: keywords
content: CSAPP,深入理解计算机系统
---
......
......@@ -3,10 +3,10 @@ title: 如何在繁重的工作中持续成长?
shortTitle: 如何在繁重的工作中持续成长?
description: 在加班文化盛行的互联网文化中,很多开发者为自己的个人成长感到迷茫。这篇文章将会从我的个人经历出发,从打破成长的误区、如何高效的利用时间、以及坚持沉淀和写作三个方面介绍,如何在繁重的工作中也可以做到持续
tags:
- 程序员
- 学习建议
author: ConardLi
category:
- 掘金社区
- 学习建议
head:
- - meta
- name: description
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册