一、背景
目前公司的电子合同
采用表单设计器
+合同业务
配合实现,做了半年多后终于上线,但是下边员工普遍反映卡顿,甚至卡死,爆栈。尤其是新增和修改合同页面,因为这部分数据量大,逻辑复杂,很容易崩溃,所以决定进行性能优化。
二、业务场景介绍
先来了解一下我们是怎么实现:
1. 因为我们公司合同变换频繁,条款之间还有逻辑,所以做了个基础服务
(说白了就是组件库),为合同提供模板
2. 表单设计器作为基础服务,打包成了组件库,嵌入到合同项目,包括合同生成组件(拖拽生成合同模板)和合同预览组件(加载数据库中的合同模板数据)
3. 合同项目有一个模块管理页面,可以对多个模板进行维护,比如可以选择启用哪个模板。
4. 合同的管理员负责维护模板,可以用表单设计器拖拽生成合同模板,提交后落入数据库,每个合同类型可以同时启用一个模板。
5. 最终下边员工用的就是启用的模板(尤其是这部门卡顿)
三、页面介绍
好了,基本的业务逻辑和页面就介绍这么多,特别卡顿的页面就是第四个页面,下面我们分析一下卡顿的原因。
四、卡顿分析
1. 首先就是表单设计器的问题最严重,因为每一个组件需要很多配置项才能够支撑组件的渲染,而一个合同是由上千个组件组成,经过测试,一个合同模板需要5MB的存储空间(数据库用的是MongoDB,存储格式为字符串,几乎不影响),下面是一个输入框的配置
2. 表单设计器的实现用了大量的闭包管理业务,我们都知道,闭包是特别耗内存的。
3. 合同模板巨复杂,由上万个组件拼接而成,我把模板数据down下来看了一下,大约是16000多个组件,大小为3.4MB。
4. 因为表单设计器中包括id,model,事件id
都是前端随机生成的,采用随机字符串+时间戳
的形式,一共46位。
5. 合同项目属于大型项目,业务场景及其复杂,包括合同管理,附件管理,合同列表,新增页面,审批页面等等,我计算了一下,光路由页面就有三十多个,页面,组件,样式,业务
巨多,如果不做处理,不卡才怪
五、性能优化
1. 第一次尝试
说一下我的优化思路:首先,电子合同由表单设计器和合同业务两个项目共同完成,合同模板加载慢的原因是浏览器渲染了大量的模板数据,这些模板数据是由多个组组成的(大约12个),我第一想到的就是分组渲染
,先加载一个组,先让用户看到页面,然后在继续加载,一个一个,最终加载完成。这也是被大家认可的方案。
然后我就开始实现这个分组渲染,做了大概有二十多天吧,一点效果没出来。
先看一下渲染的代码:
上面就是所有组加载的代码,这是一个v-for
,做分组渲染,我想到使用vue的异步组件
实现,但是这是一个循环,所有的组件注册的都是同一个名字,这显然是不能用异步组件的,除非注册的是不同名字的组件,但是我想了很长时间都做出来效果,所以这二十多天,失败了。
2.第二次尝试
上边说了,模板加载慢是因为浏览器渲染了大量的数据,我们知道,js是单线程的,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。因此js处理数据的能力有限,所以在朋友的建议下调研了一把webworker
。
webworker的作用,就是为js创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行。在主线程运行的同时,Worker线程在后台运行,两者互不干扰。
看了一把文档我第一时间觉得这个方案不可行。说到底我们就是想要webworker为我们开辟县城用来处理大量数据,但是webworker处理的大数据,不是指数据量非常大,而是要从计算量来看,通常用时不能控制在毫秒级内的运算都可以考虑放在web worker中执行。而我们的合同模板数据恰恰是数据量大,并不需要做特别大的运算。
第二次尝试失败。
3.第三次尝试
后来在同事的建议下决定采用ssr
,也就是服务端预渲染
。我们平常写的vue项目打包后生成dist
,运维会把这个文件夹放在服务器中,我们看到的页面其实就是生成执行的render函数
,这是比较耗时的。
所谓的服务端渲染,就是在服务端
生成静态页面,然后交给客户端
渲染。
自己从零搭建一套服务端渲染的应用是相当复杂的,所以我最终选用了nuxt
框架。关于nuxt框架我不多做介绍,可以自己去看文档(传送门)。这个框架有自己的脚手架,也是vue官方推荐的。
经过了一周的时间,完成了从vue向nuxt的迁移,大部门页面速度有了明显的提升。
除了我们想优化的新增合同页面。
经过分析,合同项目用到的组件库有element-UI
和我问自己的表单设计器,element只有部门组件支持ssr,像是表格和树
是不支持ssr的,所以就不存在服务端渲染了。
我也曾尝试过弄一把表单设计器,让它支持ssr,但是并没有效果,如果有谁知道,可以联系我。
很显然,第三次也失败了。
4.第四次尝试
命运总是很捉弄人,优化了一个多月的合同,速度并没有显著的提升,领导很着急,我也很着急。
突然有一天,我在回家的途中,记得那天风雨交加,雷霆大作,一声巨雷轰天响,把我好的idea都劈出来了。我一下子想到了分组加载的实现。
先来看一把代码的实现(只展示了部分代码):