构建多线程的 Electron 应用和性能优化实践 仇浩俊

QCon大会

2019/06/25 发布于 技术 分类

QCon  QCon2019 

文字内容
1. 构建多线程的 Electron 应⽤用和性能 优化实践 仇浩俊 前端开发工程师
2. 在此键入姓名 在此键入tittle
3. ⾃自我介绍 毕业后一直从事前端开发的相关工作,参与过多种形态的 Web 产品研发,曾就职于唯 品会等互联网企业。2017年加入广发证券信息技术部,目前负责机构交易终端的业务 模块、基础架构工作。
4. ⽬目录 项目背景介绍 Main / Renderer Process: 多进程架构方案实践 Web Worker: 多线程架构方案实践 线程间通信的优化 对业务数据建立极速的索引机制
5. 项⽬目背景介绍 "广发投易通"是广发证券全自研的一款面向专业投资客户的极速交易终端产品,其基于 Electron技术。产品在运行过程中需要频繁处理海量的实时行情数据和实时交易数据,持续 完成数据渲染展示,同时保持对客户交易操作的快速响应。 海海量量数据 稳定 实时 计算 快速 响应
6. 项⽬目背景介绍 逻辑计算复杂,实时性强
7. 项⽬目背景介绍 Electron客户端 React + Typescript node.js websocket 代理理 C++ 后台服务
8. 项⽬目背景介绍 问题痛点 盘中的时候,客户下单回报时间非常慢(超过500ms),并且不稳定
9. 项⽬目背景介绍 优化成效 ⽤用户从界⾯面下单 数据组装转换 发送到后台 写⼊入流⽔水(硬盘) 格式化处理理 等待结果推送 优化目标:减少用户下单的回报时间(以写入流水为准)。
10. 项⽬目背景介绍 优化成效 版本 / 数量量 1笔 10笔 20笔 优化前(约) 500ms 1s 1.5s 优化后(约) 120ms 250ms 400ms 减少⽐比例例 76% 75% 73% 实际更更低
11. Main / Renderer Process: 多进程架构⽅方案实践 问题分析 webview ⽤用户从界⾯面下单 写⼊入流⽔水 数据组装转换 阻 塞 格式化处理理 阻 塞 发送到后台 阻 塞 等待结果推送 所有的操作都在同一进程中运行,不连续的动作都会马上被抢占CPU资源,导 致用户下单回报时间变长
12. Main / Renderer Process: 多进程架构⽅方案实践 多进程 / 多线程 ? 把交易接口数据和写入流水放到独立的进程中处理,不受渲染进程的计算干扰
13. Main / Renderer Process: 多进程架构⽅方案实践 electron 提供的API 图
14. Main / Renderer Process: 多进程架构⽅方案实践 渲染进程 (视图,运算,后 台收发) { ipcMain, ipcRenderer } 主进程 (运算)
15. Main / Renderer Process: 多进程架构⽅方案实践
16. Main / Renderer Process: 多进程架构⽅方案实践 全部都搞定啦? 实际上应用的速度没有变快,交互甚至出现了莫名其妙的卡顿。 主进程与渲染进程之间并非相互独立的关系 主进程的大量运算会引起渲染进程的卡顿 主进程发送大量的数据会引起渲染进程的卡顿
17. Main / Renderer Process: 多进程架构⽅方案实践 渲染进程 (渲染) 主进程 数据通信始终需要经过主进程进行转发 渲染进程 (运算)
18. Web Worker: 多线程架构⽅方案实践 Web Worker 为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分 配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。 渲染进程(主线程) Web Worker Web Worker
19. Web Worker: 多线程架构⽅方案实践 后台交易易数据处 理理 … ⾏行行情处理理 … 行情信息处理 :行情订阅推送序列 化,数据去重等; 后台交易处理:序列化转换、数据乱 渲染进程 序处理等; 写入流水处理:数据结构组织,写入 硬盘操作等; 写⼊入流⽔水处理理 本地⽇日志处理理 … …
20. 线程间通信的优化 深度克隆隆 线程间数据相互独立,避免数据污染 数据 线程A 线程B 数据 深度克隆隆 线程间值传递的⽅方式 存在数据传输性能耗损,传输大量数据会阻塞 发送方
21. 线程间通信的优化 优化思路路 1. 压缩网络请求大小 从 YUI14 得出的灵感 2. 加快网络请求速度 3. 减少网络请求数量
22. 线程间通信的优化 第⼀一步 - 压缩数据体积
23. 线程间通信的优化 数据压缩 优化数据结构 原始数据 缩短字段名
24. 线程间通信的优化 数据压缩 换一种数据格式? 常用于前后端的数据交换 Protocol-Buffers 传输通过二进制转换,安全性更好 压缩效率高,只传输数据内容不传输格式 JSON (1.6KB) 下单数据通过 protobuf 转换 减少 37.5% 的体积 ⼆二进制 (1KB)
25. 线程间通信的优化 第⼆二步 - 加快通信速度
26. 线程间通信的优化 postMessage postMessage 渲染进程 postMessage postMessage Worker Worker Worker
27. 线程间通信的优化 Hello Broadcast Channel Hello Hello BroadcastChann el Worker Worker 渲染 进程 The Broadcast Channel API allows simple communication between browsing contexts (that is windows, tabs, frames, or iframes) with the same origin (usually pages from the same site).
28. 线程间通信的优化 const channel = createMessageChannel(); // 两个Worker初始化时传⼊入MessageChannel Worker 渲染 进程 initWorkerA(channel.in); Worker initWorkerB(channel.out); // WokerA 发送 hello channel.in.post('hello'); MessageChannel (线程直通,单方向独立管道) // WokerB 收到 hello channel.out.listen(data => console.log(data));
29. 线程间通信的优化 项⽬目 / 类型 传输耗时 (系数,越⼤大越慢) 效率对⽐比 postMessage BroadcastChannel MessageChannel 1 4 1
30. 线程间通信的优化 后台交易易数据处 理理 通过 BroadcastChannel 初始化基础信息, 将大量数据一次传递到各线程中 写⼊入流⽔水处理理 … ⾏行行情数据处理理 … 渲染进程 本地⽇日志处理理 … …
31. 线程间通信的优化 后台交易易数据处 理理 建立 MessageChannel 直接通信, 减少数据传输节点,加快传输效率 写⼊入流⽔水处理理 … ⾏行行情数据处理理 … 渲染进程 本地⽇日志处理理 … …
32. 线程间通信的优化 频率控制 传输次数 \ 数据体 积 10倍 100倍 1000倍 10000倍 10次 - 12 99 906 100次 18 122 936 8501 1000次 239 1060 8845 - 10000次 2947 10518 - - 在传输相同数据量量⼤大⼩小(⼤大)的情况下,减少传输次数可以有效提⾼高传输效率
33. 线程间通信的优化 数据 数据 数据 数据 数据 线程数据 频率控制 缓冲区 放⼊入(throttle) 数据 数据 数 据 数据 依据策略略发送 数据 缓冲区⼤大⼩小限制,超时设置 频率控制会产生以下的问题: 1. 降低了一点数据实时性(业务数据) 2. 线程传输过程产生冗余信息(行情数据) 其他 线程
34. 线程间通信的优化 数据分级 场景(提高实时性): 用户下单会依次生成指令,委托,成交 三种类型的流水数据,这些数据会快速地从后台线程 推送回来,而对于指令数据的实时性要求最高,因为下单响应以收到指令数据为准。 指令 委托 成交 指令 委托 成交 指令 委托 成交 后台推送的数据序列列 …… 指令 委托 成交
35. 线程间通信的优化 a代表指令,b、c分别代表委托、成交 数据分级 数据根据优先级分组,分两次传输
36. 线程间通信的优化 第三步 - 减少通信数据量量
37. 线程间通信的优化 数据去重 场景(减低冗余): 交易数据的变化非常快,假如时间窗口内一只股票成交了N 笔,那么后台就会推送N条流水数据到前端,但对于UI只需要展示 5笔。 ⼴广发证券 ⼴广发证券 13.36:200 13.40:100 ⼴广发证券 13.39:150 …… 后台推送的数据序列列 ⼴广发证券 13.36:100 ⼴广发证券 13.40:500
38. 线程间通信的优化 数据去重 在缓冲区发送前,只选取最新的前5条流水 大量交易流水数据
39. 线程间通信的优化 序列列化转换 体积 / 类型 对象 字符串串传输 字符串串(反序列列化) 10KB 13.54ms 3.88ms 0.1ms 500KB 375.34ms 27.78ms 8ms 5MB 3590ms 255.02ms 75ms 字符串的传输速度比对象快很多
40. 线程间通信的优化 序列列化转换 UI进程 Worker JSON.stringify Object String 5MB JSON.parse 线程传输 String Object cost 75ms 字符串的传输速度比对象快很多,但是反序列化操作会占用接收的线程资源, 频繁发送到渲染进程会引起卡顿
41. 线程间通信的优化 序列列化转换 对需要传输的数据中,数据保持相同的引⽤用值越 多,实际体积在某种程度上越⼩小,传输的速度越 快。 以左图的数据结构为例例,⼤大量量引⽤用相同的数据结构 可以减少约15% ~ 20%的传输时间。 结构化克隆隆算法:https://developer.mozilla.org/zh-CN/docs/Web/Guide/API/DOM/The_structured_clone_algorithm
42. 建⽴立极速的索引机制 优化计算 ⼩小明:平安银⾏行行: ⼩小花:海海南航空: 600 1000 ⼩小明:⼴广发证券: 3000 ⼩小春:⾦金金字⽕火腿: 500 ⼩小张:东阳光: ⼩小春:三六零: 2000 800 持仓是键为uuid对象结构,每项值记录⽤用户的股票持仓数量量 1. 多账户(4)批量量卖出500只成分股 2. 交易易规则校验 3. 查找每个⽤用户的持仓信息 4. 计算可卖的股票数量量 5. 略略 假设单个⽤用户有2000条股票持仓,4个⽤用户共有8000条记录(没有区分⽤用 户),那么单次查找500只股票的信息耗时约为500ms,那么四次查找持仓信 息共耗时为2s。
43. 建⽴立极速的索引机制 uuid 0 持仓数据 1 持仓数据 2 持仓数据 3 … 持仓数据 持仓数据 n 持仓数据 证券代码 000776 持仓数据 600000 持仓数据 600010 持仓数据 602500 持仓数据 … 持仓数据 转换数据结构,将证券代码作为键值 000780 持仓数据
44. 对业务数据建⽴立极速的索引机制 证券类型 证券市场 持仓数据 上海海 深圳 沪股通 深股通 持仓数据 ⼩小明 … ⼩小花 持仓数据 600003 000676 期货 债券 … 期权 ⼩小新 … 持仓数据 持仓数据 根据业务需求的变化,建⽴立 不不同的外部索引,提⾼高各种 场景的检索速度 ⽤用户/代码,两级索 引 000676 000156 基⾦金金 郑商所 持仓数据 ⼩小⽶米 股票 600888
45. 建⽴立极速的索引机制 在⼤大量量数据的读写场景下,Map 的读取速度约为 Object 的8倍,但前者的写⼊入速度较后者减少了了20% https://jsperf.com/map-vs-object-property-access/14
46. 建⽴立极速的索引机制 Map a1 b1 c1 d1 … 索引数据通过 Map 结构存储,提 ⾼高查找数据的效率 z1 Object 持仓数据 持仓数据 持仓数据 持仓数据 持仓数据 业务数据通过 Object 结构存储,提⾼高修改数据的效率 持仓数据
47. 建⽴立极速的索引机制 优化成效 耗时 / 持仓数 100 500 2000 5000 优化前 8ms 22ms 130ms 350ms 优化后 - - 0.14ms 0.232ms 对500只股票查找⽤用户持仓数据的速度提升
48. 总结 Message Channel 渲染进程 (视图,运 算,后台收 发) 交易易数据 线程 计算 优化 消息 分级 消息 去重 频率 控制 写⼊入流⽔水 线程 消息 压缩 引⽤用 优化
49. 总结 版本 / 数量量 1笔 10笔 20笔 优化前(约) 500ms 1s 1.5s 优化后(约) 120ms 实际更更低 250ms 400ms 减少⽐比例例 76% 75% 73% ⽤用户下单到成功回报的耗时减少了了70%以上
50. 在此键入姓名 在此键入tittle