Blog | Phodal - A Growth Engineerhttp://www.phodal.com/blog/2022-11-01T03:40:27.174847+00:00Blog高性能可视化架构:一个交互式实时数据引擎的架构设计2022-10-29T12:39:28+00:002022-10-29T12:42:12.556624+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/high-performance-frontend-component/在分析 SecDB、Athena、Quartz 几个实时金融与风险分析平台的时候,发现了 [Perspective](https://perspective.finos.org/) —— 一个 FinTech 开源基金会 FinOS 旗下开源的交互式分析和可视化组件库,由摩根大通(J.P. Morgan Chase)公司开源出去的流式数据可视化组件库。所以,从某种意义上来说也是《[金融 Python 即服务:业务自助的数据服务模式](https://www.phodal.com/blog/bank-python-as-a-service/)》 的后续展开,也可以算是[低延迟架构](https://www.phodal.com/blog/explore-low-latency-architecture/)的后续探索。
起初,我只是对其中使用的 ExprTk 感兴趣,后来发现这个库不简单:使用了 C++、Rust、Python、JavaScript、TypeScript 等语言。混合语言的项目都特别好玩,于是乎,我便开始探索它了。而我原先感兴趣的 `x <= 'abc123' and (y in 'AString') or ('1x2y3z' != z)` 的解析与实现,也就先放在一边了。
开始之前,先复制一下官方的介绍:
> [Perspective](https://github.com/finos/perspective) 是一个交互式分析和数据可视化组件,特别适合大型数据集或流数据。可以 使用它来创建用户可配置的报告、仪表板、Notebook 和应用程序,然后在浏览器中独立部署,或与 Python 和/或 Jupyterlab 协同部署。
简单来说,就是可以提供实时图形渲染,并支持 Jupyter 集成。如果是 Jupyter 的集成,那么从某种来说,它是一种金融工作台,类似于先前定义的[架构工作台](https://www.phodal.com/blog/architecture-workbench/)。
PS:写这样的工具太过复杂了,所以先写篇文章记录一下,等未来有空的时候,再写一个。
## 高性能可视化架构:Perspective 架构分析
初步绘制的 Perspective 架构图如下所示:
![高性能可视化架构][1]
在 JavaScript 侧,系统可以分为三层:
* 数据引擎。使用 C++ 与一系列的数据结构库等,进行封装,并提供数据操作 API。通过 [Emscripten](https://github.com/emscripten-core/emscripten) 构建和封装,以提供 WASM 接口。
* wrapper 层。提供对于数据引擎的再次封装,以使 API 更符合日常的编程习惯,诸如于 table、view 等,还提供 `worker`、`websocket` 等封装。
* UI 组件。viewer 分为 d3fc、datagrid、openlayers 等不同的组件,大部分使用纯 Rust 编写,提供 Web Component API 等。
在 Python 侧,除了相同的 UI 部分,还需要构建 Jupyter 插件:
* 数据引擎。结合 pybind11 来提供 FFI (Foreign Function Interface,外部函数接口)能力。
* wrapper 层。结合了 Python 数据科学生态中的 Pandas、Numpy 等工具,来进行数据转换。
* UI 层。结合 Lumino 对 UI 组件进行封装。
其中,比较有意思的是 Apache Arrow,提供了跨语言的数据支持。
## 密集计算下沉:C++ 与 WASM 应对挑战
对于将密集型计算下沉到 WASM 部分,相信大家都比较熟悉了。对于常规的 WASM 使用来说,需要平衡开发效率和运行效率,FFI 在调用的时候也存在性能损失。也因此,一种比较理想的方式是将数据操作,全部委托给 C++ 部分去实现。
如上面的架构图所示,Perspective 的计算部分,主要是 Table 对象实现的,它是 **Perspective** 中的基本数据容器。 Table 是有类型的 —— 它们有一组不可变的列名,每个都有一个已知的类型。每当有对数据的处理时,都会通过 WASM 来处理。**过滤与计算**,在这里也是一个非常有意思的问题,即上面说到的 ExptTk,便是用来做这部分计算用的。
值得注意的是,**Perspective** 之所以采用 C++ 来构建 WebAssembly 的方式,大概率是因为原有的一部分基础设施是基于 C++ 的。与此同时,原先采用的是 C++ 的 UI,以提供更好的性能。不过,Perspective 提供的 WASM 包,大概有 40M 左右,在初始化的时候相对慢了一点。
可是,又为什么是 Table 呢?这就得从 Apache Arrow 提供的能力说起。
## 无序列化与内存分析:Apache Arrow
对于序列化的性能优化,相信大家都比较熟悉了。通常来说一次数据传输操作包括:
1. 以某种格式序列化数据
2. 通过网络连接发送序列化数据
3. 在接收端反序列化数据
于是乎,在很多系统中(如 ArchGuard),序列化就是系统的瓶颈。**既然序列化会带来问题,那么就不应该有序列化**。于是乎,我们就可以在上面的架构图中,看到两个工具:
* Apache Arrow。一个直接针对数据分析需求的数据层,提供分析所需的数据类型的综合集合。除了语言无关的标准化列式存储格式之外,它还包含三个特性:零拷贝共享内存和基于 RPC 的数据移动、读取和写入文件格式(如 CSV、Apache ORC 和 Apache Parquet)、内存分析和查询处理。
* FlatBuffers。同样的,无需解析/解包即可访问序列化数据。
不过呢,FlatBuffers 只是 Arrow 用来序列化实现 Arrow 二进制 IPC 协议所需的模式和其他元数据。随后,我们就可以使用 Table 来调用 Arrow 的 API 来进行计算。
Apache Arrow 的相关介绍可以见官方文档:<https: arrow.apache.org=""></https:>
## 灵活的前端组件:无框架与渲染机制优化
简单来说,只要是以下的两个特点:
* **无框架**。对于一个以渲染为主的项目来说,Perspective 不采用任何框架。从某种意义上来说,更小的包大小,也带来了更好的性能。除此,作为一个纯粹的 web components 组件,它可以非常容易与几大主流框架结合到一起。
* **虚拟渲染的 Table**。在 Table 显示上, Perspective 采用的是 JMPC 的 [regular-table](https://github.com/jpmorganchase/regular-table),同样也是 Web Components 组件,可以直接引入项目使用。并且支持虚拟渲染,即仅显示可视区域的数据,减少 DOM 节点以带来更好的性能。
对于 Web Component 和 Custom Element 部分,相信大家都比较熟悉了。它们使用起来和正常的 HTML 区别不大,如下是一个不同 UI 组件之间的关系示例:
```javascript
<perspective-workspace>
<perspective-viewer>
<perspective-viewer-datagrid>
<regular-table></regular-table>
</perspective-viewer-datagrid>
<perspective-viewer-datagrid-toolbar></perspective-viewer-datagrid-toolbar>
</perspective-viewer>
<perspective-viewer ...=""></perspective-viewer>
</perspective-workspace>
```
每一个组件分别在不同的工程中,倒是挺 [componentless](https://componentless.com/) 的。一旦数据发生变化的时候,就会从 viewer 侧,调用 `update_and_render` 从而更新 UI 部分的 render。
## 其它
参考材料:
* 《[Apache Arrow 和 Java:大数据传输快如闪电](https://www.infoq.cn/article/bcayj0vv8f8wtcpzxm7h)》
* 《[Perspective](https://perspective.finos.org/).js》官网
[1]: /media/uploads/gallery/bank/low-latency-render-architecture.png低延迟架构体系初探2022-10-23T12:22:44+00:002022-11-01T03:40:27.174847+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/explore-low-latency-architecture/几年前,在通信领域的技术咨询经历,让我初步了解到**预分配内存管理**对于性能的改善是多么的明显。最近,也从点点滴滴的金融科技的技术里,看到了高频交易所需要的低延时架构技术(国内受限于特有国情),也有点如出一辙的味道。而在未来,“元宇宙” 可能会换个新的名词,但是呢,它依旧也需要一系列的低延迟架构设计。
过程中,我发现一系列**反直觉**的架构模式或设计模式。于是乎,作为**低延迟架构**领域的 “门外汉”,便结合着自己的理解,以及手头上的资料和书籍,做一个初步的整理和调研。
## 低延迟技术概览
![enter image description here][1]
低延迟(low latency)顾名思议是计算系统或网络以最小延迟提供响应的能力。从数据的角度来考虑,如果我们想最大程度降低延迟,那么就需要关注到每一个层次的数据处理。所以,它会涉及到一系列方方面面的领域,诸如于:
* 网络传输与硬件
* 数据传输。如微波、光纤、网线等
* 路由机制。
* 网络与硬件侧。网络协议、网卡等,专用处理器等,如 FPGA,
* 系统编程策略
* 操作系统。(从通用型操作系统到专用型操作系统) CPU 缓存、内核调度等。
* 语言侧。平衡高性能的语言与规模化。
* 其它。如内存管理策略等。
* 应用软件
* 架构侧。事件驱动型架构,如 LMAX
* 应用开发侧。高性能的数据结构(如集合)、通过设计模式提升性能等。
* 可视化侧。密集计算下沉 WASM 等。
* 设计模式。采用**无锁和无阻塞设计模式**,降低、消除操作系统信号延迟
当然了,受限于我在这个领域的了解有限,应该包含其它的内容,有待未来进一步探索。特别是,作为一个大学学习的是通信领域的工程师,对于网络传输与硬件侧,我还是相当有兴趣的。当然了,一些奇技淫巧并不在我们这里的讨论范围,诸如于数据包提前处理机制。
## 系统编程策略
**操作系统**
在操作系统侧,底层的模式是:**基于 Linux 内核的定制型操作系统搭配特定的硬件,以及特定私有的网络协议栈**。于 Linux 的网络性能不好,所以内核旁路技术的目的是:不再让内核(TCP/IP 协议栈)处理数据包。用自己实现的相同功能的代码来处理,从用户空间直接访问和控制设备内存,避免数据从设备拷贝到内核,再从内核拷贝到用户空间。所以在大量的高频交易 (HFT) 解决方案里,使用带有 OpenOnLoad 框架的 Solarflare 网卡成了一个非常好的选择。
再往上则是,同样也是围绕于 Linux 内核作其它的定制,诸如于内存分布管理、进程调度、**优化数据结构以利用处理器缓存**等。
**语言**
从现有的应用情况而言,C、C++ 基本上是在低延迟领域的代表性语言。当然,在不并需要那么严苛速度的场景下,诸如国内环境,那么 Java 也是一个非常不错的语言 —— 快速呼朋唤友。毕竟,资深的 C++ 程序员是稀缺的。
良好的设计模式 + 整洁的代码,配合上经过特制的 JVM 和架构,同样**工程造价**下**,**Java 程序的速度还是可以接近 C++。详细,在下一部分会展开介绍。
**内存管理策略**
在传统的通信领域里,一种常见管理内存的方式是:预分配内存,它可以减少分配内存所需的时间,进而提升性能。也因此,编写一个自定义的 malloc 便是这个领域的好选择,而诸如于采用 Google 的 [TCMalloc](https://github.com/google/tcmalloc),又或者是 [Jemalloc](https://github.com/jemalloc/jemalloc) 也是一个不错的选择。
既然,C/C++ 语言有内存分配的问题,那么自动 GC 的 Java 语言也有相似的问题。在 GC 的过程中,为了进行准确的计数,应用程序线程需要暂时“冻结”,也就是 GC 停顿。应对于此,通常有两个做法:无 GC 又或者是更好的 GC 算法。 而要对 JVM 进行优化并不是一件容易的事,所以直接采用诸如于 Azul Systems 的 Zing 引擎,又或者是 GraalVM,还能支持 AoT 编译的 VM —— 可以将字节码转换为机器码,以提升性能。这又造成了另外一个问题,AoT 意味着失去了自适应即时(JIT)编译,会影响到我们习惯的各种依赖注入等特性。简单来说,你可能用不了 Spring,或者你的 Spring 应用需要迁移。
这方面研究的路很长,都有待我在未来有空进一步展开研究。
## 应用软件
### 架构侧
从架构层面来考虑,基于事件驱动架构的无状态软件架构,诸如于 LMAX 架构这样的框架就能提供非常不错的参考。其主要由三部分组成:高并发框架(Disruptor)、事件溯源(Event Sourcing)、完全内存(in-memory)驻留风格等。其中,关于持续久化部分是相当反直觉的,既然事务性数据库是个瓶颈,那就不用数据库,将事件流记录到文件系统。**以内存为中心,持久化的事件作为辅助**,便是这种架构模式的一大特征。
### 应用开发
**Java 框架**
回到上面说的 AoT 和 JIT 的问题,如果我们想到一个启动更快的框架,那么我们就不能使用 Spring 框架。于是乎 现代化的微服务架构框架 Micronaut、Kubernetes 原生的 Java 框架 Quarkus,提供了两种不同的思路。Micronaut 在编译时依赖流入,可以加快应用程序的启动,降低内存的使用,进而提升响应速度,也可以利用 AoT 带来的性能加强。当然,这也意味着编程模式上会有一系列的变化。
**高性能数据结构**
在 Java 侧,为了构建高性能的 Java 应用,还需要在大数据结构上做一些优化,诸如于集合操作(List、Set、Bag),像 [Agrona](https://github.com/real-logic/agrona)、[fastutil](https://github.com/vigna/fastutil) 便是针对于 Java Collections Framework 做的扩展。同样,高盛的 JVM 架构组在多年前贡献给 Eclipse 基金会的:[Eclipse Collections](https://github.com/eclipse/eclipse-collections),便是一个旨在提升集合操作的 Java 库。
### 可视化侧
在可视化方面,如果有大量的图表需要展示,那么就需要考虑将密集计算下沉到 WASM 的方式。在这一点上 [Perspective](https://github.com/finos/perspective) 提供了一个非常不错的思路,使用 Rust 结合高性能的 C++ 数据结构设计,提供 GUI + WASM 的封装,以实现更快的计算速度。
## 其它
**数据处理**。理论上,我们还应该考虑对于数据的处理,诸如于不同存储介质应对实时数据、历史数据等等。
## 小结
在参考资料上,推荐一下《Developing High-Frequency Trading Systems》,它是我在编写此文时,尝试云覆盖更多维度时,所查找到的比较系统介绍的书籍。
* 《Developing High-Frequency Trading Systems》
* 《[Linux性能优化(九)——Kernel Bypass](https://blog.51cto.com/quantfabric/2594168)》
* 《[为什么协议栈在内核中实现](https://wendajiang.github.io/why-impl-network-stack-in-kernel/)》(中文翻译版)
* 《[为什么我们选择Java开发高频交易系统?](https://juejin.cn/post/6900145125427838983)》 (中文翻译版)
* 《[LMAX架构](https://www.jianshu.com/p/5e0c4481efb7)》(中文翻译版)
[1]: /media/uploads/gallery/bank/low-latency-architecture.png