3. 内存模型设计:Level2数据存储、价格-数量映射、跳表与红黑树的应用

好,咱们直接进入正题。Level2数据,说白了就是逐笔委托的深度行情。每秒几十万笔的更新,你要是用数据库存,那系统早挂了。必须全部放在内存里。

我个人习惯把内存模型分成三层:存储层索引层查询层。存储层管原始数据,索引层管快速定位,查询层管聚合计算。今天重点讲前两层。

3.1 Level2数据存储结构

先看存储层。每个订单簿,其实就是两个价格-数量映射表:一个买盘(Bid),一个卖盘(Ask)。

我见过不少新手直接用Python的dict来存。嗯,小规模没问题,但到了实盘,你会发现dict的哈希冲突和内存碎片会让你头疼。我在项目中遇到过,一个订单簿存了20万档位,Python dict的内存占用直接飙到2GB,还频繁GC。

所以,我建议用数组+指针的方式。每个价格档位是一个固定大小的结构体:

struct PriceLevel {
    double price;      // 价格
    uint64_t volume;   // 总量
    uint32_t order_count; // 订单数
    PriceLevel* prev;  // 前驱指针
    PriceLevel* next;  // 后继指针
};

为什么用C风格的结构体?因为内存布局连续,CPU缓存友好。你想想看,遍历一个链表时,如果每个节点都在不同的内存页,那性能得多差。

核心原则:高频交易场景下,内存访问模式比算法复杂度更重要。O(log n)的算法如果导致大量缓存未命中,可能比O(n)的线性扫描还慢。

3.2 价格-数量映射的设计

价格到数量的映射,说白了就是:给定一个价格,快速找到对应的档位,然后更新数量。

最简单的做法是用哈希表。但哈希表有个问题:无法支持范围查询。比如你想查“价格在10.00到10.05之间的所有档位”,哈希表就抓瞎了。

那怎么办?用有序数据结构。这里有两个主流选择:跳表(Skip List)红黑树(Red-Black Tree)

特性 跳表 红黑树
查找复杂度 O(log n) 平均 O(log n) 严格
插入复杂度 O(log n) 平均 O(log n) 严格
内存占用 较高(多层指针) 较低(2个指针+颜色位)
实现难度 简单 复杂(旋转操作)
并发友好度 高(无锁实现容易) 低(需要精细锁)

我个人更倾向跳表。为什么?因为实现简单,不容易出bug。我曾经在红黑树上栽过跟头——一个旋转操作的边界条件没处理好,导致订单簿数据错乱,回测结果全废了。从那以后,我对于核心数据结构,能简单就简单。

避坑指南:如果你用C++实现跳表,记得用内存池来管理节点。我曾经直接用new/delete,结果内存碎片导致性能抖动。改用固定大小的内存池后,延迟稳定了很多。

3.3 跳表在订单簿中的应用

跳表的核心思想,就是给链表加索引。每一层都是下一层的“快速通道”。

在订单簿里,我们这样用:

  • 第0层:完整的价格链表,按价格排序
  • 第1层:每隔2个节点取一个,作为索引
  • 第2层:每隔4个节点取一个
  • 以此类推

查找时,从最高层开始,快速跳过大量节点。插入时,随机决定新节点的层数。

这里有个细节:随机层数的分布。我一般用1/2的概率递增层数,这样平均每个节点有2个指针,内存开销可控。

int randomLevel() {
    int level = 1;
    while (rand() % 2 == 0 && level < MAX_LEVEL) {
        level++;
    }
    return level;
}

嗯,这个随机性保证了跳表的平衡性。虽然不严格,但实际效果很好。

3.4 红黑树的适用场景

虽然我偏爱跳表,但红黑树也有它的用武之地。比如,当你需要严格的最坏情况性能保证时,红黑树更靠谱。

我记得有一次做期权做市商的系统,订单簿的深度要求极高,最坏情况下延迟不能超过10微秒。跳表的随机性导致偶尔出现长路径,虽然概率低,但无法接受。最后换成了红黑树,用内存池+预分配节点的方式,把延迟抖动降到了1微秒以内。

注意:红黑树的实现一定要用迭代器,不要用递归。递归会导致栈溢出,尤其是在深度很大的订单簿中。我曾经见过一个同事的代码,递归深度超过1000层,直接Segmentation Fault。

3.5 核心逻辑流程图

下面这张图展示了订单簿内存模型的核心逻辑:

订单簿内存模型核心逻辑 Level2 行情数据 存储层:数组 + PriceLevel 结构体 连续内存布局,CPU缓存友好 索引层:跳表 / 红黑树 价格排序 + 快速定位 + 范围查询 查询层:聚合计算 最优买卖价、深度汇总、价格区间统计 说明:数据从行情接口流入,先写入存储层(连续内存),再通过索引层(跳表/红黑树)建立有序索引,最后供查询层做聚合计算

3.6 实战中的选择建议

说了这么多,到底该用哪个?我总结几条经验:

  • 如果你做的是A股Level2行情:数据量不大(约5000档位),用跳表就够了。实现简单,维护方便。
  • 如果你做的是加密货币订单簿:数据量巨大(几万到几十万档位),建议用红黑树。性能更稳定,内存更紧凑。
  • 如果你做的是期权做市:需要同时管理多个到期日、多个行权价,建议用嵌套结构:外层用红黑树按到期日分组,内层用跳表按价格排序。

我的个人习惯:先用跳表快速实现原型,跑通业务逻辑。等性能瓶颈出现时,再用红黑树替换热点路径。这样既保证了开发效率,又能在关键时刻优化性能。

嗯,内存模型这块就讲这么多。记住一句话:没有银弹。跳表和红黑树各有优劣,关键看你的业务场景。多测试,多压测,用数据说话。


公众号:蓝海数据掘金营,微信deep3321