33

前端性能:股票交易APP频繁更新怎么破

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI2NTk2NzUxNg%3D%3D&%3Bmid=2247486359&%3Bidx=1&%3Bsn=9111fad0780b96ff431f255d24a43758
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

写本文的原因

  • 有几位小伙伴最近又来问这个问题,之前帮人解答过一次,今天写下来

  • 以后有时间会多写一些解决方案,例如oom了,不用esbuild怎么解决之类的等..

正式开始

主题:股票交易APP(IM场景前端交互高频更新卡顿)

  • 一个正常的股票交易APP,是很复杂的,大都用原生写,但是有的公司没钱啊,只能做一套web app或者用RN这些写,也有用Flutter的(这就是没钱又要玩,那怎么办呢?那就玩 乞丐版 呀)
UJB3Yzz.png!web

一个股票交易APP的界面长这样

问题重现

  • 用户收藏了1000只自选股(国内国外+期货+指数等),技术栈是web app ,基于react或React-native,很卡顿

  • 由于是双工通讯,而且高频推送,触发更新,而且交易类APP对消息送达的效率/低延迟要求非常高,例如你准备买这只股票,此时大户砸盘,你还没收到更新的信息,下单,发现趋势已经走坏,然后接盘被套。

  • 还有一种情况,你买入的时候出了大利好,你下单价格是10块钱,但是此时已经涨到10.05,这个价格成成交不了,然后你错过了一波大涨。这时候客户就惨了

需求简单&技术的剖析

  • 理论上用户可以添加的自选股票,是无限的

  • 每个自选股/股票的都有对应的事件触发

AJrQNzA.png!web
  • 高频更新,此时要区分react/react-native环境,因为react-native组件在挂载后就不会卸载了,不像web app.

  • 性能优化最好是简单的手段

  • 所见即所得,简单高校,不触碰底层逻辑,例如网络层前后端可能都要做粘包的处理

  • ...不做可能诱发P0级别事故的技术方向选择

解决问题

  • react/react-native渲染上有区别,对于长列表,react-native是有组件对应只渲染可视区域,react则不会,需要虚拟列表,推荐 react-peter-window 这个库,而且可以支持自动高宽
源码demo地址:https://github.com/JinJieTan/react-keepAlive-dynamic
  • 这样react也可以跟react-native的组件一样,只渲染可视区域了

  • 长列表问题解决了,但是事件同时也很麻烦,理论上用户可以添加无限的自选股,这个列表可能就有无限长(不要说不可能,世界在发展,这就是高可用的APP),传统的事件需要每个item去绑定,然后切换组件时候再remove掉,但是频繁对事件挂载、移除其实也很损耗性能,这里换成事件冒泡,就可以了,把需要的数据挂载到dom的属性上获取即可~

  • 上面说的,不要小看,能解决相当一部分性能问题

最重要的高频更新的问题

  • 不同金融交易类公司,后端架构设计不一样,消息推送也是,例如大智慧的后端架构就比较特殊.

  • 前端网络层可能要处理粘包,后端的消息推送频率我们不管

  • 借鉴PReact、Redis、kafka的思想,自己在前端实现一个 消息队列 ,定期消费,更新界面.
  • 参考我之前手写React的代码:

https://github.com/JinJieTan/mini-react/tree/hooks

import { _render } from '../reactDom/index';

import { enqueueSetState } from './setState';

export class Component {

constuctor(props = {}) {
this.state = {};
this.props = props;
}

setState(stateChange) {
const newState = Object.assign(this.state || {}, stateChange);
console.log(newState,'newState')
this.newState = newState;
enqueueSetState(newState, this);
}

}

  • 当setState后,先进入队列中,首次进入,队列为空,进入判断,下一帧渲染前调用defer(flush)

export function enqueueSetState(stateChange, component) {

//第一次进来肯定会先调用defer函数
if (setStateQueue.length === 0) {
//清空队列的办法是异步执行,下面都是同步执行的一些计算
defer(flush);
}

//向队列中添加对象 key:stateChange value:component
setStateQueue.push({
stateChange,
component,
});

//如果渲染队列中没有这个组件 那么添加进去
if (!renderQueue.some((item) => item === component)) {
renderQueue.push(component);
}
}
  • defer函数

function defer(fn) {
//高优先级任务 异步的 先挂起
return requestAnimationFrame(fn);
}
  • 此时消息再次推送,再次触发enqueueSetState,数据此时被推送到队列中,一帧统一合并消费。

其实浏览器也是有渲染队列的,例如你在一个for循环里面频繁操作dom,并不会每次操作dom都会导致浏览器渲染,达到一个阀值,就会触发渲染,当然你也可以手动控制清空队列(这里不写太深,有兴趣的可以关注后面的文章)

写在最后

  • 我是Peter,架构设计过20万人端到端加密超级群功能的桌面IM软件,现在是一名前端架构师。

  • 如果你对性能优化有很深的研究,可以跟我一起交流交流,今天这里写得比较浅,但是大部分人都够用,之前问我的朋友,我让它写了一个定时器定时消费队列,最后也能用。哈哈

  • 另外欢迎收藏我的资料网站:前端生活社区: https://qianduan.life
  • 右下角
    在看
    [前端巅峰]
    

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK