页面参数加密卡顿怎么办?开启多线程吧!

643 阅读5分钟

页面参数加密卡顿怎么办?开启多线程吧!

场景

最近在做公司项目的时候遇到一个性能问题。因为某种原因,前端和后台进行数据交互时需要用RSA加解密参数,我们做的系统有个流程就是在登录后会加载3个字典表,达到大几千甚至上万条的数据量,在RSA进行解密时,页面直接卡死大约十几秒,这样显然不行!

分析原因

加密解密卡顿

在了解到了RSA加密是非对称加解密,在处理大量的数据的时候速度是相当的慢,想要优化加密其实是一种方法,但是由于一些其他因素,不得不使用RSA进行参数加密,所以这个方案pass。

线程堵塞

在此之前,先复习一下事件循环(EventLoop)和多线程:

js是一门单线程语言(可以理解为目的地A到目的地B只有一条马路,这条马路的宽度只能容纳下一种交通工具),浏览器执行是按照上下文的执行顺序依次执行(这就意味着交通工具通过马路只能一辆车一辆车的排队行驶)。 那js是如何实现页面不卡顿呢?(那么多车的情况下怎么会导致不拥堵呢?),开启一个消息队列(在马路旁边建造一个停车场吧),浏览器也分了3种任务,分别为交互任务(同步代码)、微任务、和延时任务,之前叫宏任务微任务(停车场按照车的类型分类,汽车、摩托车、自行车)。此时浏览器开始执行js,遇到了同步代码先执行,看到了微任务和延时任务就放入列队(绿灯开启时,交通工具通过马路,遇到了摩托车和自行车,先把这两类的车赶到停车场,汽车优通行),当同步代码执行完成之后,微任务先执行,最后是延时任务执行(汽车走完了,摩托车走,摩托车走完,最慢的自行车才开始通行),当所有任务都执行完毕后,将进入监听模式,等待事件的执行,以此循环以上步骤(当所有的车通过后,红灯开启,等待有车后,绿灯再开启,以此往复)

常用的延时任务:setTimeOutsetInterval请求IO,常用的微任务:Promise.thenPromise.catchPromise.finally

那么问题来了,如果其中一个交互任务执行时间比较慢,减慢了后面的的任务怎么办?(那么问题来了,马路上有一辆老爷车,正在以每小时20/km的速度在行驶,老爷车心想:我也是汽车,走这边应该没有问题)。这时候会用到一个js多线程方案:web Worker 相关的概念可以看看百度一下,这边不过多赘述。 他的作用是在使用js渲染主线程的前提下,再开启一个或者多个额外的线程,优点就是:速度快,即用即关。缺点:会消耗更多的资源,但是现在电脑配置都很高了,也不会在意那么点资源。(道路管理知道拥堵了,立马告诉老爷车,我给你走应急车道的权限,你走应急车道吧【现实中可不能】))那么同步任务和开启的线程任务将会继续同时执行(那么老爷车在应急车道慢慢的走,同时汽车也正常开始了通行)。

概念了解后,在到业务上来:在浏览器进行解析代码的时候,会按照一个任务一个任务的去执行,比如我点击登录时,会触发以下任务:发送登录认证请求(loginRequest),路由跳转逻辑判断任务(RouteAction),发送请求字典请求(DicRequest)。

分析三次改造

我对历史代码进行了2次改造:

原始代码逻辑:loginRequest(交互任务)—>DicRequest(交互任务)—>RouteAction(交互任务)

第一次改造逻辑:loginRequest(交互任务)—>RouteAction(交互任务) —>DicRequest(延时任务)

第二次改造逻辑:loginRequst (交互任务) —>RouteAction(交互任务)—>DicRequest(延时任务的同时开启线程任务)

原始代码一个字卡:所有任务都是同步代码,按照顺序依次执行,不是执行就是等待,所以要对此进行改造。第一次改造逻辑,做起来也简单,只要把代码不放在then里面执行即可,但是就会遇到我上面说的页面会卡死,任何交互都无效,原因是因为老爷车堵住了后面车的正常行驶。所以在这基础上应该再做一次改造:开启多线程

我这边一开始使用的原生web Worker,但是无奈原生api难用,所以使用第三方库:threads

使用方法:

```
1.npm install threads -S

2.根目录创建文件
    workers
        master.js
        worker.js
        
3. wokers.js写入     
    import { decrypt } from '../utils/crypto'
    import { expose } from 'threads/worker'
    expose({
        decode (data, url) {
            return decrypt(data, url)
        }
    })
    
4.master.js写入
    import { spawn, Thread, Worker } from 'threads'
    export async function decode (data, url) {
      const auth = await spawn(new Worker('./worker.js'))
      const hashed = await auth.decode(data, url) // 解密
      await Thread.terminate(auth) //关闭多线程
      return hashed
    }
    
5.响应拦截
import { decode } from '@/workers/master.js'
axios.interceptors.response.use(
    async res => {
    .....
    const response = JSON.parse(JSON.stringify(res))
    const config = response.config
    let res
    // 当大于20k就使用多线程解密
    if (response.data.encryptData && response.data.encryptData.length > 20480) {
        res = await decode(response.data.encryptData || response.data, config.url)
    } else { // 正常主线程解密
        res = decrypt(response.data.encryptData || response.data, config.url)
    }
    .....
    }

```

如果node版本<12,需要再装一个tiny-worker的依赖。在这里遇到过个坑,就是threads的导入需要使用插件来配合。不然会有报错.

npm install -D threads-plugin
//vue.config.js
const ThreadsPlugin = require('threads-plugin')
......
 plugins: [new ThreadsPlugin(), ......]
......

调试:在谷歌的调试工具的source栏可以看到自己开启的线程,以方便调试

image.png

总结:

合理使用异步来解决性能确实是个好方法,但是有些场景还是需要强大的多线程实现。总得来说速度还是挺高了很多,但是会留下一个问题,我们的字典做了缓存,也就是如果在第一次访问的时候会开启多线程加载,但是在此同时,用户点入到了需要用字典的页面,使用到了字典,那么解密还没有结束,这样怎么解决这个问题呢?如果各位大佬有合适的方案或者说以上做法有什么不好的地方,指出,定然虚心学习!