百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

各个击破,超大文件分片上传Vue.js3.0+Tornado6异步IO高效写入

toyiye 2024-06-28 10:03 12 浏览 0 评论

分治算法是一种很古老但很务实的方法。本意即使将一个较大的整体打碎分成小的局部,这样每个小的局部都不足以对抗大的整体。战国时期,秦国破坏合纵的连横即是一种分而治之的手段;十九世纪,比利时殖民者占领卢旺达, 将卢旺达的种族分为胡图族与图西族,以图进行分裂控制,莫不如是。

21世纪,人们往往会在Leetcode平台上刷分治算法题,但事实上,从工业角度上来看,算法如果不和实际业务场景相结合,算法就永远是虚无缥缈的存在,它只会出现在开发者的某一次不经意的面试中,而真实的算法,并不是虚空的,它应该能帮助我们解决实际问题,是的,它应该落地成为实体。

大文件分片上传就是这样一个契合分治算法的场景,现而今,视频文件的体积越来越大,高清视频体积大概2-4g不等,但4K视频的分辨率是标准高清的四倍,需要四倍的存储空间——只需两到三分钟的未压缩4K 电影,或者电影预告片的长度,就可以达到500GB。 8K视频文件更是大得难以想象,而现在12K正在出现,如此巨大的文件,该怎样设计一套合理的数据传输方案?这里我们以前后端分离项目为例,前端使用Vue.js3.0配合ui库Ant-desgin,后端采用并发异步框架Tornado实现大文件的分片无阻塞传输与异步IO写入服务。

前端分片

首先,安装Vue3.0以上版本:

npm install -g @vue/cli

安装异步请求库axios:

npm install axios --save

随后,安装Ant-desgin:

npm i --save ant-design-vue@next -S

Ant-desgin虽然因为曾经的圣诞节“彩蛋门”事件而声名狼藉,但客观地说,它依然是业界不可多得的优秀UI框架之一。

接着在项目程序入口文件引入使用:

import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router/index'


import axios from 'axios'
import qs from 'qs'

import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';


const app = createApp(App)


app.config.globalProperties.axios = axios;
app.config.globalProperties.upload_dir = "https://localhost/static/";

app.config.globalProperties.weburl = "http://localhost:8000";

app.use(router);
app.use(Antd);

app.mount('#app')

随后,参照Ant-desgin官方文档:https://antdv.com/components/overview-cn 构建上传控件:

<a-upload

    @change="fileupload"
    :before-upload="beforeUpload"
  >
    <a-button>
      <upload-outlined></upload-outlined>
      上传文件
    </a-button>
  </a-upload>

注意这里需要将绑定的before-upload强制返回false,设置为手动上传:

beforeUpload:function(file){


      return false;

}

接着声明分片方法:

fileupload:function(file){


      var size = file.file.size;//总大小
 

      var shardSize = 200 * 1024; //分片大小
 
      this.shardCount = Math.ceil(size / shardSize); //总片数

      console.log(this.shardCount);
 
 
      for (var i = 0; i < this.shardCount; ++i) {
 
        //计算每一片的起始与结束位置
 
        var start = i * shardSize;
 
        var end = Math.min(size, start + shardSize);

        var tinyfile = file.file.slice(start, end);

        let data = new FormData();
        data.append('file', tinyfile);
        data.append('count',i);
        data.append('filename',file.file.name);

        const axiosInstance = this.axios.create({withCredentials: false});

        axiosInstance({
            method: 'POST',
            url:'http://localhost:8000/upload/',  //上传地址
            data:data
        }).then(data =>{

           this.finished += 1;

           console.log(this.finished);

           if(this.finished == this.shardCount){
            this.mergeupload(file.file.name);
           }

        }).catch(function(err) {
            //上传失败
        });



      }



    }

具体分片逻辑是,大文件总体积按照单片体积的大小做除法并向上取整,获取到文件的分片个数,这里为了测试方便,将单片体积设置为200kb,可以随时做修改。

随后,分片过程中使用Math.min方法计算每一片的起始和结束位置,再通过slice方法进行切片操作,最后将分片的下标、文件名、以及分片本体异步发送到后台。

当所有的分片请求都发送完毕后,封装分片合并方法,请求后端发起合并分片操作:

mergeupload:function(filename){


      this.myaxios(this.weburl+"/upload/","put",{"filename":filename}).then(data =>{

              console.log(data);

        });

}

至此,前端分片逻辑就完成了。

后端异步IO写入

为了避免同步写入引起的阻塞,安装aiofiles库:

pip3 install aiofiles

aiofiles用于处理asyncio应用程序中的本地磁盘文件,配合Tornado的异步非阻塞机制,可以有效的提升文件写入效率:

import aiofiles

# 分片上传
class SliceUploadHandler(BaseHandler):
    
    async def post(self):


        file = self.request.files["file"][0]
        filename = self.get_argument("filename")
        count = self.get_argument("count")

        filename = '%s_%s' % (filename,count) # 构成该分片唯一标识符

        contents = file['body'] #异步读取文件
        async with aiofiles.open('./static/uploads/%s' % filename, "wb") as f:
            await f.write(contents)

        return {"filename": file.filename,"errcode":0}

这里后端获取到分片实体、文件名、以及分片标识后,将分片文件以文件名_分片标识的格式异步写入到系统目录中,以一张378kb大小的png图片为例,分片文件应该顺序为200kb和178kb,如图所示:

当分片文件都写入成功后,触发分片合并接口:

import aiofiles

# 分片上传
class SliceUploadHandler(BaseHandler):
    
    async def post(self):


        file = self.request.files["file"][0]
        filename = self.get_argument("filename")
        count = self.get_argument("count")

        filename = '%s_%s' % (filename,count) # 构成该分片唯一标识符

        contents = file['body'] #异步读取文件
        async with aiofiles.open('./static/uploads/%s' % filename, "wb") as f:
            await f.write(contents)

        return {"filename": file.filename,"errcode":0}


    async def put(self):

        filename = self.get_argument("filename")
        chunk = 0

        async with aiofiles.open('./static/uploads/%s' % filename,'ab') as target_file:

            while True:
                try:
                    source_file = open('./static/uploads/%s_%s' % (filename,chunk), 'rb')
                    await target_file.write(source_file.read())
                    source_file.close()
                except Exception as e:
                    print(str(e))
                    break

                chunk = chunk + 1
        self.finish({"msg":"ok","errcode":0})

这里通过文件名进行寻址,随后遍历合并,注意句柄写入模式为增量字节码写入,否则会逐层将分片文件覆盖,同时也兼具了断点续写的功能。有些逻辑会将分片个数传入后端,让后端判断分片合并个数,其实并不需要,因为如果寻址失败,会自动抛出异常并且跳出循环,从而节约了一个参数的带宽占用。

轮询服务

在真实的超大文件传输场景中,由于网络或者其他因素,很可能导致分片任务中断,此时就需要通过降级快速响应,返回托底数据,避免用户的长时间等待,这里我们使用基于Tornado的Apscheduler库来调度分片任务:

pip install apscheduler

随后编写job.py轮询服务文件:

from datetime import datetime
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.web import RequestHandler, Application
from apscheduler.schedulers.tornado import TornadoScheduler


scheduler = None
job_ids   = []

# 初始化
def init_scheduler():
    global scheduler
    scheduler = TornadoScheduler()
    scheduler.start()
    print('[Scheduler Init]APScheduler has been started')

# 要执行的定时任务在这里
def task1(options):
    print('{} [APScheduler][Task]-{}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), options))


class MainHandler(RequestHandler):
    def get(self):
        self.write('<a href="/scheduler?job_id=1&action=add">add job</a><br><a href="/scheduler?job_id=1&action=remove">remove job</a>')


class SchedulerHandler(RequestHandler):
    def get(self):
        global job_ids
        job_id = self.get_query_argument('job_id', None)
        action = self.get_query_argument('action', None)
        if job_id:
            # add
            if 'add' == action:
                if job_id not in job_ids:
                    job_ids.append(job_id)
                    scheduler.add_job(task1, 'interval', seconds=3, id=job_id, args=(job_id,))
                    self.write('[TASK ADDED] - {}'.format(job_id))
                else:
                    self.write('[TASK EXISTS] - {}'.format(job_id))
            # remove
            elif 'remove' == action:
                if job_id in job_ids:
                    scheduler.remove_job(job_id)
                    job_ids.remove(job_id)
                    self.write('[TASK REMOVED] - {}'.format(job_id))
                else:
                    self.write('[TASK NOT FOUND] - {}'.format(job_id))
        else:
            self.write('[INVALID PARAMS] INVALID job_id or action')


if __name__ == "__main__":
    routes    = [
        (r"/", MainHandler),
        (r"/scheduler/?", SchedulerHandler),
    ]
    init_scheduler()
    app       = Application(routes, debug=True)
    app.listen(8888)
    IOLoop.current().start()

每一次分片接口被调用后,就建立定时任务对分片文件进行监测,如果分片成功就删除分片文件,同时删除任务,否则就启用降级预案。

结语

分治法对超大文件进行分片切割,同时并发异步发送,可以提高传输效率,降低传输时间,和之前的一篇:聚是一团火散作满天星,前端Vue.js+elementUI结合后端FastAPI实现大文件分片上传,逻辑上有异曲同工之妙,但手法上却略有不同,确是颇有相互借镜之处,最后代码开源于Github:https://github.com/zcxey2911/Tornado6_Vuejs3_Edu,与众亲同飨。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码