记录–记录用前端代替后端生成zip的过程,速度快了 57 倍!!!

  • 记录–记录用前端代替后端生成zip的过程,速度快了 57 倍!!!已关闭评论
  • 141 次浏览
  • A+
所属分类:Web前端
摘要

业务场景:产品有个功能是设置主题。类似手机自动切换壁纸,以及其他功能颜色,icon,字体等。


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--记录用前端代替后端生成zip的过程,速度快了 57 倍!!!

业务场景:

产品有个功能是设置主题。类似手机自动切换壁纸,以及其他功能颜色,icon,字体等。

管理员需要在后端管理系统多次下载不同主题,(至于要干啥就不说了...),主题中可能有 30 ~ 100个高清壁纸, icon 等。现在每次下载主题(31张高清图片)至少需要 10s。有什么方法能够优化下。

因为代码不具备可复用性,因此部分代码直接省略,思路为主

原始逻辑

  public async getZip(themeId: string, res: any) {     const theme = await this.model.findById(themeId); // 从数据库      // 这里需要借用一个服务器上的主题模板文件夹 template/,          /*        theme = {               wallpapers: [                   { url: 'https://亚马逊云.com/1.jpg', ... },                   ...               ]        }     */          // for 循环遍历 theme.wallpapers , 并通过 fetch 请求 url,将其写进 template/static/wallpapers 文件夹中     theme.wallpapers.map((item) => {         const response = await fetch(item.url);         const buffer = new Uint8Array(await response.arrayBuffer());         await fs.writeFile(`template/wallpapers/${fileName}`, buffer);     })          // ... 还有其他一些处理          // 将 template 压缩成 zip 文件,发送给前端   }

思考 ing ...

1 利用图片可以被浏览器缓存

当一次下载主题从请求亚马逊云的图片数据,这步没有问题。 但是当重复下载的时候,之前下载过的图片又会再次下载,操作人员每次都需要等个十几秒,这就不太友好了。这部分时间花费还是挺多的。

可以利用下浏览器能够将图片缓存到 disk cache 中的特点,将这部分的代码逻辑放到前端完成,因为还需要对压缩包中的文件做一些处理,因此需要借助下 jszip 这个库。

看下改后的代码

onDownload () {     // 请求拿到 theme 数据     const theme = api.getTheme()      const template = api.getTemplate() // Blob          const zip = new JSZip()     await zip.loadAsync(getTmplResp) // 读取 template.zip 文件数据          console.time('handle images')     const wallpaperList = theme.wallpapers     for (const wallpaper of wallpaperList) {       const response = await fetch(wallpaper.url) // 请求图片数据       const buffer = new Uint8Array(await response.arrayBuffer())       const fileName = wallpaper.url.split('/').pop()       zip.file(`static/wallpapers/${fileName}`, buffer, { binary: true }) // 写进压缩包     }     console.timeEnd('handle images') // 统计用时          // 还需要读取 template.zip 中的 config.json, 然后修改,重新保存到 template.zip 中     ...          // 导出 template.zip     zip.generateAsync({ type: 'base64' }).then(       (base64) => {         const link = document.createElement('a')         link.href = 'data:application/zip;base64,' + base64         link.download = 'template.zip'         link.target = '_blank'         link.style.display = 'none'          document.body.appendChild(link)         link.click()         document.body.removeChild(link)       },       (err) => {         console.log('打包失败', err)       }     ) }

优化完成

当第一次下载时,handle images 步骤耗时 20 - 21 s,流程和后端差不多。

当第二次下载时,handle images 步骤耗时 0.35s - 0.45 s。会直接读取 disk cache 中的图片数据,50 ms 内就完成了。

速度快了 57 倍有余!!!, 你还能想到其他优化方式吗?继续往后看 ?

第一次请求各个图片耗时

记录--记录用前端代替后端生成zip的过程,速度快了 57 倍!!!

 第二次请求各个图片耗时

记录--记录用前端代替后端生成zip的过程,速度快了 57 倍!!!

2 并发请求

我们都知道,浏览器会为每个域名维持 6 个 TCP 链接(再拓展还有域名分片知识),我们是否可以利用这个特点做些什么?

答案是:并发上传

通过上面的代码,可以看到,每个图片请求都是串行的,一个图片请求完了再进行下一个图片请求。我们一次请求 4 个图片,这样就更快了。

首先写一个能够管理并发任务的类

export class TaskQueue {   public queue: {     task: <T>() => Promise<T>     resolve: (value: unknown) => void     reject: (reason?: any) => void   }[]   public runningCount: number  // 正在执行的任务数量   public tasksResloved?: (value: unknown) => void   public tasksRejected?: (reason?: any) => void    public constructor(public maxConcurrency: number = 4) { // 最多同时执行 4 个任务     this.queue = [] // 任务队列     this.runningCount = 0   }      // 添加任务   public addTask(task) {     return new Promise((resolve, reject) => {       this.queue.push({ task, resolve, reject })     })   }        // 执行   public run() {     return new Promise((resoved, rejected) => {       this.tasksResloved = resoved       this.tasksRejected = rejected       this.nextTask()     })   }    private nextTask() {     if (this.queue.length === 0 && this.runningCount === 0) {       this.tasksResloved?.('done')       return     }          // 如果任务队列中还有任务, 并且没有到最大执行任务数,就继续取出任务执行     while (this.queue.length > 0 && this.runningCount < this.maxConcurrency) {       const { task, resolve, reject } = this.queue.shift()       this.runningCount++       task()         .then((res) => {           this.runningCount--           resolve(res)           this.nextTask()         })         .catch((e) => {           this.runningCount--           reject(e)           this.nextTask()         })     }   } }

改造代码

onDownload () {     // 请求拿到 theme 数据     const theme = api.getTheme()      const template = api.getTemplate() // Blob          const zip = new JSZip()     await zip.loadAsync(getTmplResp) // 读取 template.zip 文件数据          console.time('handle images')     const wallpaperList = theme.wallpapers          // 注释之前的逻辑     // for (const wallpaper of wallpaperList) {     //   const response = await fetch(wallpaper.url)      //   const buffer = new Uint8Array(await response.arrayBuffer())     //   const fileName = wallpaper.url.split('/').pop()     //   zip.file(`static/wallpapers/${fileName}`, buffer, { binary: true })      // }          const taskQueue = new TaskQueue() // 新建任务队列,默认同时执行 4 个     for (const wallpaper of wallpaperList) {       taskQueue         .addTask(() => fetch(wallpaper.url)) // 添加任务         .then(async (res) => {  // 任务执行完后的回调           const buffer = new Uint8Array(await (res as Response).arrayBuffer())           const fileName = wallpaper.url.split('/').pop()           zip.file(`static/wallpapers/${fileName}`, buffer, { binary: true })         })         .catch((e) => console.log('壁纸获取失败', e))     }     await taskQueue.run()  // 等待所有图片都拿到     console.timeEnd('handle images') // 统计用时          // 还需要读取 template.zip 中的 config.json, 然后修改,重新保存到 template.zip 中     ...          // 导出 template.zip     zip.generateAsync({ type: 'base64' }).then(       (base64) => {         const link = document.createElement('a')         link.href = 'data:application/zip;base64,' + base64         link.download = 'template.zip'         link.target = '_blank'         link.style.display = 'none'          document.body.appendChild(link)         link.click()         document.body.removeChild(link)       },       (err) => {         console.log('打包失败', err)       }     ) }

大功告成!

当第一次下载时,handle images 步骤耗时 7 s,速度是之前的 3 倍。

当第二次下载时,handle images 步骤耗时 0.25s,速度是之前的 1.4 - 1.8

3 更多的可能

越来越感觉到计算机网络的重要性, 还有未实现的优化方式:

  1. 域名分片,更多的并发(也有劣势 ,比如 每个域都需要额外的 DNS 查找成本以及建立每个 TCP 连接的开销, TCP 慢启动带宽利用不足)
  2. 升级 HTTP2 这不是靠前端一人能够完成的

本文转载于:

https://juejin.cn/post/7267418197746270271

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--记录用前端代替后端生成zip的过程,速度快了 57 倍!!!