Web3 现货撮合引擎中的跳表原理与实践

本文深入解析跳表在Web3现货撮合引擎订单簿中的核心应用。跳表以O(log n)复杂度支持高效的价格档位有序插入、删除与查询,兼顾缓存友好性与并发简易性,优于红黑树和普通链表。订单簿将买盘/卖盘分别建模为降序/升序跳表,价格为键,订单队列为值;辅以哈希索引实现按ID撤单。工程实践强调定点整数定价、单线程撮合、空档位清理及内存优化。

1. 引言:为什么撮合引擎需要跳表

在 Web3 现货交易所里,撮合引擎是整个系统的性能心脏。它需要在毫秒甚至微秒级别完成「价格优先、时间优先」的订单匹配,同时支撑每秒数万到数十万笔的订单插入、撤销与查询。

传统实现里,订单簿(Order Book)通常有两种组织方式:

  • 数组 / 有序列表:查询快,但插入和删除是 O(n),在深度大的交易对(如 BTC/USDT 动辄上万档)上会迅速成为瓶颈。
  • 红黑树 / 平衡二叉树:插入、删除、查询都是 O(log n),但节点分散、指针跳转多,缓存不友好,且并发实现复杂。

跳表(Skip List) 恰好在这两者之间取得了很好的平衡:它用多层链表实现近似 O(log n) 的查找、插入、删除,结构简单、无旋转、易并发、缓存局部性优于树,因此被大量撮合引擎与内存数据库采用(如 Redis 的有序集合底层就是跳表)。

本文从撮合引擎的实际需求出发,讲清楚跳表的原理、在现货订单簿中的落地方式,以及工程实现中的关键细节。

2. 现货订单簿的数据模型

在讲跳表之前,先明确订单簿要存什么。

一个现货交易对(如 BTC/USDT)的订单簿由两侧组成:

  • 买盘(Bids):按价格从高到低排序,价格越高越优先成交。
  • 卖盘(Asks):按价格从低到高排序,价格越低越优先成交。

每一侧本质上是一个「价格档位」的有序集合,每个价格档位下挂着一个 FIFO 的订单队列(同一价格按时间优先)。

Bids(价格降序)              Asks(价格升序)
10050 -> [订单A, 订单B]       10060 -> [订单C]
10040 -> [订单D]              10070 -> [订单E, 订单F]
10030 -> [订单G]              10080 -> [订单H]

撮合时,只要「最高买价 >= 最低卖价」,就说明存在可成交机会,引擎从两侧取出队首订单进行撮合。

因此订单簿的核心操作可以归纳为:

操作 说明 频率
插入订单 新订单进入对应价格档位 极高
删除订单 撤单或成交后移除 极高
查询最优价 取买一 / 卖一 极高
按价格区间遍历 深度行情推送、市价单扫单 高
按订单 ID 定位 撤单时找到订单所在档位 高

这些操作正是跳表擅长的场景。

3. 跳表的核心原理

3.1 从链表到多层索引

普通有序链表查找需要从头遍历,复杂度 O(n)。跳表的思路是:在链表之上建立多层「快速通道」,上层节点稀疏、下层节点稠密,查找时从最高层开始,能往右就往右,不能往右就下降一层。

Level 3:  1 -------------------------> 9
Level 2:  1 --------> 5 -------------> 9
Level 1:  1 --> 3 --> 5 --> 7 -------> 9
Level 0:  1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9

查找 7 的过程:

  1. 从 Level 3 头节点出发,下一个是 9,比 7 大,下降。
  2. Level 2 到 5,5 < 7,继续往右到 9,9 > 7,下降。
  3. Level 1 到 5,往右到 7,命中。

整个过程跳过了大量节点,平均查找长度为 O(log n)。

3.2 层高如何决定

每个节点在插入时随机决定自己的层高,通常采用几何分布:

P(level >= k) = p^k

常见取 p = 0.25 或 p = 0.5。p = 0.25 时,每 4 个节点期望有 1 个进入上一层,空间开销约为 1/(1-p) = 1.33 倍,非常划算。

随机化的好处是不需要任何重平衡操作,插入删除只改指针,天然适合高并发与低延迟场景。

3.3 复杂度分析

操作 平均复杂度 最坏复杂度
查找 O(log n) O(n)
插入 O(log n) O(n)
删除 O(log n) O(n)

最坏情况概率极低(随机层高退化),工程上可以认为稳定在 O(log n)。

4. 用跳表实现订单簿

4.1 价格档位作为跳表节点

订单簿的每一侧用一个跳表维护价格档位,key 为价格,value 为该价格下的订单队列。

  • 买盘跳表按价格降序比较(价格高者在前)。
  • 卖盘跳表按价格升序比较(价格低者在前)。

这样「买一 / 卖一」就是跳表的第一个有效节点,取最优价是 O(1)。

4.2 插入订单流程

1. 校验订单(价格、数量、交易对)
2. 在对应侧跳表中查找价格档位
   - 存在:追加到该档位的订单队列尾部(时间优先)
   - 不存在:新建档位节点,插入跳表
3. 触发撮合检查:比较买一与卖一
4. 若可成交,循环撮合直到价差恢复或订单耗尽

4.3 撤单流程

撤单需要「按订单 ID 快速定位」。常见做法是维护一个 orderId -> (side, price, orderNode) 的哈希索引:

1. 通过哈希表 O(1) 找到订单所在档位
2. 从档位的订单队列中移除该订单(双向链表 O(1))
3. 若档位订单队列为空,从跳表中删除该价格节点

这里跳表的删除同样是 O(log n),且只需修改前后指针。

4.4 撮合循环

while (bestBid.price >= bestAsk.price) {
    bidOrder = bestBid.queue.head
    askOrder = bestAsk.queue.head
    matchedQty = min(bidOrder.qty, askOrder.qty)
    // 生成成交记录,更新双方余额
    // 扣减数量,数量为 0 的一方出队
    // 档位为空则从跳表删除
}

由于最优价始终在跳表头部,撮合循环的每次迭代都是 O(1) 取档位 + O(log n) 删除空档位,整体效率很高。

5. 代码实现示例

下面用 Go 给出一个简化但可运行的跳表实现,聚焦订单簿需要的「有序插入、查找、删除、取首节点」能力。

package skiplist

import (
	"math/rand"
)

const maxLevel = 32
const p = 0.25

type node struct {
  
内容概要:本文围绕二级倒立摆系统的控制问题,开展LQR(线性二次型调节器)与传统PID控制方法的对比仿真研究。基于Matlab/Simulink平台,首先建立二级倒立摆的动力学模型,随后分别设计LQR状态反馈控制器与经典PID控制器,通过仿真实验对比两者在系统响应速度、稳定性、抗干扰能力及控制精度等方面的动态性能。LQR控制器依据最优控制理论,利用状态变量实现全局优化调控,具有较强的理论深度;而PID控制器则基于误差调节的经典思想,结构简单、工程实用性强。研究通过量化分析与曲线对比,直观展现两种控制策略的优劣,阐明现代控制方法与经典控制方法在复杂非线性系统中的适用性差异。文档配套提供参考文献与说明材料,有助于深入理解算法原理与仿真实现细节。; 适合人群:自动化、控制科学与工程及相关专业的本科生、研究生、科研人员及工程技术人员,尤其适合具备自动控制理论基础和Matlab/Simulink仿真经验的学习者。; 使用场景及目标:①掌握复杂机电系统的建模与仿真方法;②深入理解LQR与PID控制策略的设计原理与应用场景差异;③提升控制系统性能分析与参数调优能力;④为课程设计、毕业设计、科研项目或竞赛提供高质量的技术参考与实现范例。; 阅读建议:建议结合所提供的Simulink模型逐步实践,重点聚焦系统建模、控制器构建与参数整定过程,对照文档深入剖析算法逻辑,并可进一步拓展至模糊PID、智能优化算法等先进控制方法进行综合对比研究。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北冥you鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值