• 多宽带叠加的分布式云盘:文件秒传、断点续传、大文件下载


    1. 前言

    家中有三条宽带,想通过搭建私有云盘,叠加上传和下载的速度。对比了多种私有云的系统,没找到满足的要求的,所以便萌生出搭建一个可宽带叠加的分布式私有云存储。

    2. 各种私有云盘对比

    可道云、Nextcloud、易有云:没有多节点。
    Cloidreve:有从服务器,但只是用来离线下载的
    Seafile:支持ceph分布式储存,只能单节点。
    IPFS:去中心化,p2p,未来之星。网关被污染了,有点费硬盘。
    Minio: 分布式储存,但传输时只有一个节点传输,别的节点负责加油。
    Resilio Sync、微力备份(接近要求,但属于备份软件,比较占储存空间)
    jigdo(实现不了,没找到部署教程)(大佬可是试试这个)

    3.本云盘实现的功能

    多节点
    部署简单
    文件秒传
    断点续传
    MD5值校验
    minio存储(防止文件上传漏洞)
    大文件下载(数据流的方式,内存占用小)
    下载和上传的速度基本取决于宽带上传的下载的速度。

    4. 界面

    上传界面

    文件浏览
    在这里插入图片描述

    4. 安装

    4.1 负载均衡服务器

    负责提供浏览界面,接收客户端的请求,返回文件的md5。


    只需修改config.php

    
    //添加节点地址
    $nodes = ['http://ip1:80',"http://ip2:80"];
    $dbh = new PDO('mysql:host=localhost;dbname=dbname', "dbuser", "password");
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.1 储存节点

    修改

    ########################需要修改########################
    minioClient = Minio('ip:9000',
                      access_key='minio_key',
                      secret_key='minio_secret',
                      secure=False) 
                      #如果是采用https则,secure=True。
    buckets = "mydisk"
    ########################需要修改########################
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行

    //运行minio
    /home/minio/app/minio server /home/minio/data --config-dir /home/minio/config --console-address :42606 --address :9000
    
    //运行node.py
    python3 node.py
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5. 代码说明

    计算文件的MD5 ,获取切片

    /**
     * 计算文件的MD5 ,获取切片
     * @param file 文件
     * @param chunkSize 分片大小
     * @returns Promise
     */
    function md5(file, chunkSize = 1024*1024*1) {
    	return new Promise((resolve, reject) => {
    		let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
    		let chunks = Math.ceil(file.size / chunkSize);
    		let currentChunk = 0;
    		let spark = new SparkMD5.ArrayBuffer();
    		let fileReader = new FileReader();
    	    var slices = [];
    		fileReader.onload = function(e) {
    			spark.append(e.target.result); 
    			currentChunk++;
    			
                num = 100*currentChunk/chunks;
                num = num.toFixed(2);
                jindutiao(num,"计算哈希","#7A0BE2")
        
    			if (currentChunk < chunks) {
    				loadNext();
    			} else {
    				slices.push(spark.end());
    				resolve(slices);
    			}
    		};
    	
    // 		fileReader.onerror = function(e) {
    // 			reject(e);
    // 		};
    	
    		function loadNext() {
    			let start = currentChunk * chunkSize;
    			let end = start + chunkSize;
    			if (end > file.size){
    				end = file.size;
    			}
    			slice = blobSlice.call(file, start, end);
    			slices.push(slice);
    			fileReader.readAsArrayBuffer(slice);
    		}
    		loadNext();
    	});
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    上传片段

    //上传片段
    function sendChunk(id, chunks,offset,name){
      // 逐个提交
      // 用于保证ajax发送完毕
    
      var  task=offset.length;
      var  alllen = chunks.length ;
    
      chunks.forEach(function(chunk, index){
    
    
            if (offset.length > 0) {text = "断点续传";}
            if (offset.length === 0){text = "文件上传";}
    
           if (offset.indexOf(index.toString()) >= 0) {
             return;
           }
        
            var formData = new FormData();
            formData.append('fileId', id);
            formData.append('myFileChunk', chunk);
            formData.append('chunkIndex', index);
            url = nodes[index%nodes.length]+"/success";
        
            console.log(url);
            $.ajax({
              type: "POST",
              url: url,
              data: formData,
              contentType: false,
              processData: false,
              async: true,
              success: function(done){
                  console.log("a00" + done + "a00");
                if(done == 200){
                    task =task + 1;
                    num = 100*task/alllen ;
                    num = num.toFixed(2);
                    console.log(index + "已完成" + num.toString() + "%");
                    jindutiao(num,text)  
                }
              }
            })
        })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    进度条

    //进度条
    function jindutiao(ini,text,color="pink") {
        let texth = document.getElementById("text");
        let cont = document.getElementById("cont");
        let container = document.getElementById("container");
        
        cont.style.backgroundColor = color;
        container.style.display = 'block';
        cont.style.width = ini + "%";
        if(ini>=100){
            text = text + "成功";
            ini = 100.00;
        }
        texth.innerText = text + ": " + ini + "%";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    下载文件,二进制的方式。

    //下载文件,二进制的方式。
    function getBinaryContent(url , i) {
        //console.log("getBinaryContent : " + url);
        return new Promise((resolve, reject) => {
            try {
                let xhr = new XMLHttpRequest();
                xhr.open("GET", url, true);
                xhr.responseType = "arraybuffer"; // 设置返回的类型为arraybuffer
                xhr.onload = function () {
                    resolve({
                        index: i, // 文件块的索引
                        buffer: xhr.response, // 范围请求对应的数据
                    });
                };
                xhr.onprogress=function(event){
                    if(event.lengthComputable) {
                        var percentComplete = event.loaded / event.total*100;
                            percentComplete = percentComplete.toFixed(2);
                        //console.log("当前下载进度: " + event.loaded + "," + event.total , percentComplete);
                        //updateProgress(percentComplete * 100);
                    }
                }
                xhr.send();
            } catch (err) {
                reject(new Error(err));
            }
        });
       
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    多进程

    //多进程
    //poolLimit 代表进程池的数量
    async function download( urls, poolLimit = 2 ) {
        // // Abort the download stream when leaving the page
        // window.isSecureContext && window.addEventListener('beforeunload', evt => {
        //   writer.abort()
        // })
        const fileStream= streamSaver.createWriteStream(filename);
        const writer = fileStream.getWriter();
        const all_part_num = Object.keys(urls).length;
        const pools = oneArrToTwoArr([...new Array(all_part_num).keys()],poolLimit*2);
        const MD5 = new SparkMD5();
    
        //分组下载
        for (let pool of pools) {
            var results = await asyncPool(
                poolLimit,
                pool,
                (i) => {
                    //console.log(urls[i])
                    ini = (i+1)/all_part_num*100;
                    ini = ini.toFixed(2)
                    jindutiao(ini,"文件下载")
                    //异步下载,返回到results
                    return getBinaryContent(urls[i], i);
                }
            );    
            //文件数据 格式转换,转换为数组的类型
            var sortedBuffers = results.map((item) => new Uint8Array(item.buffer));
        
            for (let array of sortedBuffers) {
                writer.write(array)
            }  
        }
        writer.close();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    async function asyncPool(poolLimit, array, iteratorFn) {
        const ret = []; // 存储所有的异步任务
        const executing = []; // 存储正在执行的异步任务
        for (const item of array) {
            // 调用iteratorFn函数创建异步任务
            const p = Promise.resolve().then(() => iteratorFn(item, array));
            ret.push(p); // 保存新的异步任务
     
            // 当poolLimit值小于或等于总任务个数时,进行并发控制
            if (poolLimit <= array.length) {
                // 当任务完成后,从正在执行的任务数组中移除已完成的任务
                const e = p.then(() => executing.splice(executing.indexOf(e), 1));
                executing.push(e); // 保存正在执行的异步任务
                if (executing.length >= poolLimit) {
                    await Promise.race(executing); // 等待较快的任务执行完成
                }
            }
        }
        return Promise.all(ret);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    下载文件,二进制的方式。

    //下载文件,二进制的方式。
    function getBinaryContent(url , i) {
        //console.log("getBinaryContent : " + url);
        return new Promise((resolve, reject) => {
            try {
                let xhr = new XMLHttpRequest();
                xhr.open("GET", url, true);
                xhr.responseType = "arraybuffer"; // 设置返回的类型为arraybuffer
                xhr.onload = function () {
                    resolve({
                        index: i, // 文件块的索引
                        buffer: xhr.response, // 范围请求对应的数据
                    });
                };
                xhr.onprogress=function(event){
                    if(event.lengthComputable) {
                        var percentComplete = event.loaded / event.total*100;
                            percentComplete = percentComplete.toFixed(2);
                        //console.log("当前下载进度: " + event.loaded + "," + event.total , percentComplete);
                        //updateProgress(percentComplete * 100);
                    }
                }
                xhr.send();
            } catch (err) {
                reject(new Error(err));
            }
        });
       
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    6. 源码

    源码链接: 点击下载

  • 相关阅读:
    小白还不懂电脑图片转PDF格式怎么弄吗?这些方法你都试过吗?
    安装vllm的时候卡主:Collecting vllm-nccl-cu12<2.19,>=2.18 (from vllm)
    Git版本控制管理——版本库管理
    mac flutter 配置
    Pytorch深度学习——线性回归实现 04(未完)
    XPS测试加测轨道-科学指南针
    vLLM:由伯克利大学LMSYS组织开源的大语言模型高速推理框架-显著提高了大型语言模型(LLM)的服务效率
    CSS小技巧之单标签loader
    厉害了!GitHub 大神的 K8S+SpringCloud 笔记,从基础到进阶都很细致
    Python logging模块:别再用print来打印啦~
  • 原文地址:https://blog.csdn.net/liuyuncc/article/details/126142290