分治算法 --- 详解

❤️❤️❤️感谢各位朋友接下来的阅读❤️❤️❤️ 

分治算法概念: 

分治算法,即分而治之

 在计算机科学中,分治法是一种很重要的算法。分治算法,字面上的解释是“分而治之”,分治算法主要是三点: 1.将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题----“分”
2.将最后子问题可以简单的直接求解----“治”
3.将所有子问题的解合并起来就是原问题打得解----“合”

分治法解析图:

 分治算法的基本思想:

            当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治策略的基本思想。

分治算法使用:

我做过的一个超大文件上传,为了提高效率,采用的是分治算法,现在的视频文件的体积越来愈大,传输速度慢,耗时时间太长,给用户带来不好的体验效果,我做的是使用前端Vue.js3.0然后配合ui库,阿里云的Ant-design,后端采用并发异步框架tornado,来实现大文件的分片无阻塞传输与异步io写入服务。

文件分片:

文件分片是在前端进行的,通过选择文件,来获取文件名,文件大小,自定义分片大小的阈值,
通过math函数ceil(总文件体积除以定义的单片阈值大小)进行向上取整来计算总片数 ,

然后通过slice方法来对文件进行切片,从0的地方开始切片,结束位置使用Math.min方法(开始位置加上定义分片大小),

最后将分片的下标,文件名,以及分片实体异步发送到后台。

 我前端使用的是Ant-design:组件总览 - Ant Design Vue (antdv.com)

<a-upload :before-upload="beforeUpload" @change="handleChange">
            <a-button>自动上传视频</a-button>
          </a-upload>

          <img :src="src" v-show="src" />
export default {
  data() {
    return {
      
      //分片个数
      shardCount: 0,
      finished: 0,
      src: "",
      websrc: "",
      size:0,


    };
  },

methods:

  methods: {
     //前端进行判断,如果errcode==0,则调用轮询接口,会将filename,分片数,以及尺寸大小,jobid
   //传送后端轮询中,进行分片判断,若不等就是分片传输未成功,判断文件大小删除分片,删除定时任务

    //发起定时任务
    task:function(filename){

      this.myaxios(this.taskurl + "scheduler/", "get", { "filename": filename,"count":this.shardCount,"size":this.size,"job_id":'1' }).then(
        (data) => {
          console.log(data);
        }
      );


    },
    //发送分片文件
    pushshard: function (file, filename, count) {
      const axiosupload = this.axios.create({ withCredentials: false });

      let data = new FormData();

      data.append("file", file);
      data.append("filename", filename);
      data.append("count", count);

      //发起请求
      axiosupload({
        method: "POST",
        url: this.baseurl + "upload/",
        data: data,
      }).then((data) => {
        console.log("111", data);

        if (data.data.errcode == 0) {
          this.finished += 1;
          if (this.finished == this.shardCount) {
            this.myaxios(this.baseurl + "upload/", "put", {
              filename: filename,size:this.size
            }).then((data) => {
              if (data.errcode == 0) {
                this.src = this.upload_dir + "/" + filename;

                this.websrc = filename;
                this.finished = 0;

                this.task(filename)
              }
            });
          }
        }
      });
    },

    handleChange(file) {
      //获取文件大小 1024字节=1kb
      var size = file.file.size
      this.size = size
      console.log('总大',size)
      //定义分片大小1G=1024MB,1MB=1024KB,1KB=1024字节
      var shardSize = 2 *1000 * 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)
        console.log("切片", start, end);

        //切片
        var shardfile = file.file.slice(start, end)

        this.pushshard(shardfile, file.file.name, i)
      }
    },

    // 上传图片
    beforeUpload: function () {
      return false;
    },
    //提交
    submit: function () {
      var temp = [];

      //取值
      for (var i = this.forms.length - 1; i >= 0; i--) {
        temp.push({
          label: this.forms[i]["label"],
          value: this.forms[i]["value"],
        });
      }

      temp.push({ "video": this.src });

      temp = JSON.stringify(temp);

      this.myaxios(this.baseurl + "merchantform/", "put", { form: temp }).then(
        (data) => {
          console.log(data);
        }
      );
    },
}

后端分片逻辑: 

后端接口使用post请求,获取到分片实体,下标和文件名,以及获取文件内容
使用await异步写入,分片成功,

然后再通过put请求,获取到filename和前端的文件大小,使用get.path.getsize方法获取文件大小,
如果前端传来的文件和读取的文件大小不相等,则异步打开文件句柄,使用open方法循环读取分片文件,将读取到的
分片文件异步写入,手动关闭句柄,停止循环,将文件数量+1,合并完成

后端异步io写入:

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

pip3 install aiofiles

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

# 上传文件
import aiofiles
import os
from base import BaseHandler
# 导入路由
from tornado.web import url

# 分片上传 post请求体传参,不会被url截断
class SliceUploadHandler(BaseHandler):

    async def post(self):

        # 获取分片实体
        file = self.request.files["file"][0]

        # 获取下标
        count = self.get_argument("count",None)

        # 文件名
        filename = self.get_argument("filename",None)

        # 获取文件内容
        content = file["body"]


        # 异步写入
        async with aiofiles.open("./static/uploads/{}_{}".format(filename,count),"wb") as file:

            # 异步
            await file.write(content)

        self.finish({"errcode":0,'msg':'分片上传成功'})

    async def put(self):

        filename = self.get_argument("filename",None)
        print('filename',filename)

        count = 0
        size = self.get_argument("size", None)
        print('size',size)

        try:
            filesize = os.path.getsize("./static/uploads/{}".format(filename))
        except Exception as e:
            print(str(e))
            filesize = 0

        if int(size) != filesize:

            #异步打开文件句柄
            async with aiofiles.open("./static/uploads/{}".format(filename),"ab") as file:

                while True:
                    try:
                        # 循环读取分片文件
                        shard_file = open("./static/uploads/{}_{}".format(filename,count),'rb')

                        # 异步写入
                        await file.write(shard_file.read())

                        # 手动关闭句柄
                        shard_file.close()

                    except Exception as e:
                        print(str(e))

                        break

                    count = count + 1

        self.finish({"errcode":0,"msg":'合并完毕'})





# 声明路由
urlpatterns = [
    
    url('/upload/',SliceUploadHandler),
    

]

效果展示:

我这里采用图片代用视频啦!

重要问题:

当然了,用户上传文件时候,也会出现各种问题,就比如因为网络或者其他的某些因素导致
分片的任务中断,一定会影响到后面的文件合成,所以这个时候需要添加一个轮询服务,来确保
出现异常能提示并且解决异常。这里我们使用基于tornado的Apscheduler库来调度分片任务。


import redis
import os
import json

from datetime import datetime
from tornado.ioloop import IOLoop, PeriodicCallback

from tornado.web import Application, RequestHandler, url

from apscheduler.schedulers.tornado import TornadoScheduler

from test_async import main

from base import BaseHandler

from apscheduler.jobstores.redis import RedisJobStore

from thread_lock import start_pool_thread

scheduler = None

job_ids = []


# 声明任务:检查分片

def task(job_id,filename,size,count):
    count = int(count)

    filelist = os.listdir("./static/uploads/")
    temp = []

    for i in range(count):
        for x in filelist:
            if x == "{}_{}".format(filename,i):

                temp.append(x)


    if len(temp) != count:
        print("分片未传输成功")
        return False

    try:
        filesize = os.path.getsize("./static/uploads/{}".format(filename))
    except Exception as e:
        print(str(e))
        filesize = 0

    if int(size) != filesize:
        print("分片合并未成功")
        return False

    # 删除分片
    for x in range(count):

        os.remove("./static/uploads/{}_{}".format(filename,x))

    # 删除定时任务
    scheduler.remove_job(job_id)

    jobs = Jobs()
    job_info = {"job_id": job_id, "filename": filename, "count": str(count), "size": size}
    job_info = json.dumps(job_info)

    print(job_info)

    jobs.remove(job_info)

    # job_ids.remove(job_id)
    print("分片执行完毕")


# 初始化
def init_scheduler():

    global scheduler

    jobstores = {"default": RedisJobStore(jobs_key="jobs", run_times_key="jobtimes", host="localhost", port=6379,password=123)}
    scheduler = TornadoScheduler(jobstores=jobstores)
    # scheduler = TornadoScheduler()
    scheduler.start()

    # jobs = Jobs()
    # job_list = jobs.get_job_list()
    # for x in job_list:
    #     x = json.loads(x)
    #
    #     scheduler.add_job(task, "interval", seconds=3, id=x["job_id"],
    #                       args=(x["job_id"], x["filename"], x["size"], x["count"]))

    print("定时任务启动")


# 声明服务控制器
class SchedulerHandler(BaseHandler):

    async def get(self):

        job_id = self.get_argument("job_id",None)

        filename = self.get_argument("filename",None)

        size = self.get_argument("size",None)

        count = self.get_argument("count", None)

        jobs = Jobs()
        job_list = jobs.get_job_list()
        job_info = {"job_id": job_id, "filename": filename, "count": count, "size": size}

        job_info = json.dumps(job_info)

        if job_info not in job_list:

            jobs.insert(job_id,filename,count,size)
            scheduler.add_job(task,"interval",seconds=3,id=job_id,args=(job_id,filename,size,count))

            print("定时任务入队")
            res = {"errcode": 0, "msg": "ok任务完成"}

        else:
            print("该任务已经存在")
            res = {"errcode": 1, "msg": 'fail失败'}

        self.finish(res)

        # global job_ids

        # if job_id not in job_ids:
        #
        #     job_ids.append(job_id)
        #
        #     scheduler.add_job(task,"interval",seconds=3,id=job_id,args=(job_id,filename,size,count))
        #
        #     print("定时任务入队")
        #     res = {"errcode":0,"msg":"ok任务完成"}
        #
        # else:
        #     print("该任务已经存在")
        #     res = {"errcode":1,"msg":'fail失败'}
        #
        #     self.finish(res)


if __name__ == '__main__':

    routes = [url(r"/scheduler/",SchedulerHandler)]

    # init_scheduler()

    # 声明tornado对象
    print('56789')
    application = Application(routes,debug=True)
    application.listen(8888)
    IOLoop.current().start()



总结 :分治法对超大文件进行分片切割,同时并发异步发送,可以提高传输效率,降低传输时间。

遵循三步:

(1)分解,将要解决的问题划分成若干规模较小的同类问题;

(2)求解,当子问题划分得足够小时,用较简单的方法解决;

(3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值