队列数据结构:FIFO队列原理、优先级队列原理、时间轮算法在撮合中的应用
聊到撮合引擎,队列这东西是绕不开的。说白了,整个撮合过程就是一场「排队游戏」。谁先来、谁单子大、谁优先级高,全得靠队列来管。我这些年调过不少撮合系统,踩过的坑多半都跟队列设计有关。今天咱们就把三种核心队列掰开揉碎了讲清楚。
一、FIFO队列:最朴素的公平
FIFO,先进先出。这个太直观了——先到的先服务。在撮合场景里,FIFO队列保证的是「时间公平性」。
举个例子。假设有两个买单,价格都是100元。A在10:00:00.001下单,B在10:00:00.002下单。那A就该排在B前面。这就是FIFO干的事。
我在项目中遇到过一个问题:用Java的ConcurrentLinkedQueue做FIFO队列,结果在高并发下偶尔出现顺序错乱。后来排查发现,是CAS操作在极端竞争下导致入队顺序和实际时间戳不一致。解决方案?嗯,用ReentrantLock加一个严格的时间戳校验。
// 一个简单的FIFO队列实现(伪代码)
class FIFOQueue {
Node head, tail;
void enqueue(Order order) {
Node node = new Node(order);
tail.next = node;
tail = node;
}
Order dequeue() {
if (head == null) return null;
Order order = head.order;
head = head.next;
return order;
}
}
二、优先级队列:价格优先的底气
FIFO只解决了「谁先来」的问题。但撮合的核心逻辑是「价格优先」。你出价100块,我出价99块,哪怕我先到,你也得排我前面。这就是优先级队列的用武之地。
优先级队列,本质上是一个堆结构。最大堆或最小堆,取决于你是买单还是卖单。买单用最大堆(价高者优先),卖单用最小堆(价低者优先)。
我记得有一次,团队里新来的同事直接用PriorityQueue做撮合队列。跑压测时发现性能上不去。为什么?因为PriorityQueue的remove()操作是O(n)的。你想想看,撮合引擎每秒要处理几万笔订单,每次撤销订单都要遍历整个队列,这谁顶得住?
// 优先级队列核心逻辑(伪代码)
class PriorityQueue {
// 买单用最大堆,卖单用最小堆
Heap buyHeap = new MaxHeap();
Heap sellHeap = new MinHeap();
void addBuyOrder(Order order) {
buyHeap.push(order); // 按价格排序
}
Order getBestBuy() {
return buyHeap.peek(); // 最高价买单
}
}
优先级队列的公平性问题,其实挺微妙的。同一价格下,谁先来?这时候就得结合FIFO了。所以实际系统中,往往是「价格优先 + 时间优先」两层结构:先按价格分桶,每个桶内再用FIFO队列。
三、时间轮算法:延迟撮合的利器
说到时间轮,很多人第一反应是Kafka里的延时消息。其实在撮合场景里,时间轮也有妙用。
什么场景需要时间轮?举个例子:冰山订单。一个大单不想暴露全部数量,只显示一部分。剩下的部分需要「延迟释放」。或者,条件订单——价格到了某个阈值才触发。这些都需要一个高效的定时器机制。
时间轮算法的核心思想,说白了就是「用空间换时间」。把时间分成一个个槽位,每个槽位对应一个时间间隔。指针每走一格,就处理当前槽位里的所有任务。
我参与过一个期货撮合系统的重构。原来的延迟订单用ScheduledExecutorService管理,结果在极端行情下,线程池积压了上万个定时任务,CPU直接飙到100%。后来改成时间轮,问题迎刃而解。
// 时间轮简化实现(伪代码)
class TimeWheel {
int tickMs; // 每个槽位的时间跨度
int wheelSize; // 槽位数量
List<Task>[] slots; // 槽位数组
int currentIndex; // 当前指针位置
void addTask(Task task, long delayMs) {
int ticks = delayMs / tickMs;
int index = (currentIndex + ticks) % wheelSize;
slots[index].add(task);
}
void advance() {
currentIndex = (currentIndex + 1) % wheelSize;
// 处理当前槽位的所有任务
for (Task task : slots[currentIndex]) {
task.execute();
}
}
}
四、三种队列在撮合中的协同
实际撮合引擎不会只用一种队列。它们是配合使用的。我画了一张图,帮你理清关系。
你看这张图,订单进来后,先按价格分桶——这是优先级队列干的活。每个桶内部,再用FIFO队列保证时间公平性。至于那些需要延迟处理的订单,比如冰山订单的剩余部分,就扔进时间轮里,等时间到了再放回对应的价格桶里。
三种队列各司其职,配合起来才能既保证公平性,又保证性能。我见过不少系统只用了优先级队列,结果同一价格下的订单顺序乱得一塌糊涂。也见过只用FIFO的,结果高价单被低价单堵在后面,用户体验极差。
好了,队列这块就聊到这儿。记住一句话:没有最好的队列,只有最合适的组合。具体怎么选,得看你的业务场景和性能要求。