4、数据采集架构设计:采集层、处理层、存储层三层架构、数据流管道设计、高可用与容错机制

聊到高频行情数据采集,很多人第一反应就是“快”。没错,延迟确实重要。但我在这个行业摸爬滚打这么多年,发现真正让系统稳定跑下去的,其实是架构。

你想想看,行情数据每秒几万笔甚至几十万笔涌进来。如果架构设计得不好,哪怕单机性能再强,早晚也会出问题。我个人习惯把整个采集系统拆成三层:采集层、处理层、存储层。每一层各司其职,互不干扰。

4.1 三层架构:各层职责与边界

先说说这三层到底干什么。

  • 采集层:负责从交易所网关接收原始行情。说白了就是“接数据”。这一层只做一件事——把网络上的二进制流收下来,丢进内存队列。不做任何加工。
  • 处理层:负责清洗、校验、重组、计算。比如把乱序的tick排好序,把缺失的字段补上,或者算个中间价。这一层是“加工车间”。
  • 存储层:负责把处理好的数据持久化。可以是时序数据库,也可以是文件系统。这一层只关心“怎么写最快、怎么读最方便”。

我在项目中遇到过一种情况:有人把数据清洗逻辑直接写在采集层里。结果行情爆发时,采集线程卡在清洗上,导致丢包。嗯,这里要注意——每一层只做自己的事,千万别越界。

核心原则:采集层不处理,处理层不存储,存储层不采集。各层之间通过队列或管道解耦。

4.2 数据流管道设计

三层之间怎么传数据?这就是管道设计要解决的问题。

我个人比较喜欢用无锁环形队列做层间通信。为什么?因为高频场景下,加锁的成本太高了。你想想看,每秒几十万次入队出队,如果每次都要抢锁,CPU时间全耗在等待上了。

下面是一个简化版的管道设计图,我用SVG画了一下核心流程:

采集层 原始行情接收 无锁队列 处理层 清洗·校验·计算 批量写入 存储层 时序数据库/文件 数据流方向:从左到右,逐层传递 容错机制 • 采集层:双链路热备 • 处理层:进程级主备切换 • 存储层:多副本 + 异步复制 • 全链路:心跳检测 + 自动重连

实际代码中,管道设计大概长这样:

// 伪代码:采集层 -> 处理层 的无锁队列传递
struct TickData {
    uint64_t timestamp;
    double   price;
    uint64_t volume;
    char     symbol[8];
};

// 无锁环形队列(简化版)
LockFreeQueue<TickData, 65536> raw_queue;

// 采集线程
void collector_thread() {
    while (running) {
        TickData tick = recv_from_exchange();
        raw_queue.push(tick);  // 入队,不阻塞
    }
}

// 处理线程
void processor_thread() {
    while (running) {
        TickData tick;
        if (raw_queue.pop(tick)) {  // 出队
            process_tick(tick);     // 清洗、计算
            send_to_storage(tick);
        }
    }
}

经验之谈:队列大小要设得够大。我曾经因为队列设小了,行情爆发时直接写爆,数据全丢了。建议至少设到65536个槽位,每个槽位存一个tick。

4.3 高可用与容错机制

做高频交易最怕什么?怕系统挂了,行情断了,然后你还在傻傻地等数据。容错不是锦上添花,是保命用的。

我总结了几条必须做的容错策略:

  • 采集层双链路热备:同时连接两个交易所网关。主链路断了,备用链路秒级接管。注意,两个链路的数据要能去重,不然同一笔tick处理两次就麻烦了。
  • 处理层进程级主备:主进程挂了,备用进程立刻顶上。状态怎么同步?通过共享内存或者Redis保存最近1000笔tick的校验和。备机启动时先校验,再继续处理。
  • 存储层多副本:数据同时写到两个不同的磁盘,或者两个不同的节点。我用的是Raft协议做副本同步,强一致性,但代价是写入延迟会高一点点。如果对延迟极度敏感,可以改成异步复制。
  • 全链路心跳检测:每层之间每秒发一次心跳。连续3次没收到,就触发告警并自动切换。

避坑指南:我曾经遇到过一种情况——主备切换时,备用进程启动后发现自己落后了500毫秒的数据。这500毫秒的行情如果丢了,策略可能做出错误决策。后来我加了一个“追赶模式”:备用进程先快速回放最近1秒的日志,追上进度后再切为主。

下面是一个容错切换的伪代码示例:

// 主备切换逻辑
void health_check() {
    if (missed_heartbeat_count >= 3) {
        // 主节点挂了,触发切换
        if (role == BACKUP) {
            // 先追赶数据
            catch_up_from_log();
            // 再切换角色
            role = PRIMARY;
            start_processing();
        }
    }
}

// 数据追赶:从本地日志回放
void catch_up_from_log() {
    uint64_t last_seq = get_last_processed_seq();
    LogEntry entry;
    while (log_reader.read_next(&entry)) {
        if (entry.seq > last_seq) {
            process_tick(entry.tick);
        }
    }
}

4.4 架构总结

说了这么多,其实核心就几句话:

层级 核心职责 容错方式 性能关键点
采集层 接收原始行情 双链路热备 零拷贝、低延迟网络
处理层 清洗、校验、计算 进程级主备切换 无锁队列、批量处理
存储层 持久化存储 多副本 + 异步复制 顺序写入、列式存储

说白了,架构设计就是“把复杂问题拆简单”。每一层只做一件事,层与层之间用管道解耦。容错不是事后补救,而是设计时就考虑进去。嗯,这套架构我在多个项目里验证过,每秒处理50万笔tick没什么压力。

一句话总结:采集层接得住,处理层算得快,存储层存得稳,容错机制兜得住底。这就是一套靠谱的高频行情采集架构。


专注资料整理