二、系统总体架构:分层架构设计

做市商高频风控系统,说白了就是要在纳秒级别内做出决策。我见过太多团队一上来就堆技术栈,结果系统延迟高得离谱,风控反而成了拖后腿的环节。今天我就聊聊我个人习惯的分层架构设计思路。

先看一张整体架构图,后面我们一层层拆开讲。

做市商高频风控系统 — 分层架构总览 数据层 行情数据接入 · 订单簿快照 · 历史数据回放 · 多源数据对齐 延迟目标:< 1μs | 数据源:交易所UDP组播 / 二进制协议 计算层 实时指标计算 · 波动率模型 · 风险敞口监控 · 压力测试 计算引擎:C++核心 + GPU加速 | 延迟目标:< 5μs 决策层 规则引擎 · 阈值判断 · 熔断逻辑 · 多策略协调 决策模式:事件驱动 + 状态机 | 延迟目标:< 3μs 执行层 订单路由 · 撤单管理 · 仓位同步 · 异常处理 执行接口:FIX / 私有协议 | 延迟目标:< 2μs 数据流方向 总端到端延迟目标:< 15μs | 采用零拷贝 + 无锁队列 + 内存池技术

2.1 数据层:一切的基础

数据层是整个系统的地基。地基不稳,上面再花哨也没用。我早期参与过一个项目,数据层用了通用的消息队列,结果行情峰值时队列积压,风控信号晚了整整一个tick——那笔损失够买好几台服务器了。

数据层要解决三个核心问题:

  • 低延迟接入:交易所通常走UDP组播或私有二进制协议。我个人建议直接用DPDK收包,绕过内核协议栈。别用TCP,那东西在高频场景下就是灾难。
  • 数据对齐:多源行情的时间戳必须统一。我习惯用硬件时间戳,精度能到纳秒级。软件时间戳?嗯,误差太大,容易误判。
  • 历史回放:回测和复盘需要完整的数据快照。记得用内存映射文件,别一行行读磁盘。
💡 我的经验:数据层最容易被忽视的是「数据完整性校验」。我曾经遇到过交易所偶尔发重复包,如果不做去重,风控会重复计算,导致误触发熔断。加一个简单的序列号校验就能解决。

2.2 计算层:真正的硬仗

计算层是风控系统的核心。说白了,就是把原始行情变成可决策的指标。这里我踩过不少坑。

计算层主要做这几件事:

  1. 实时指标计算:比如波动率、买卖价差、订单簿深度。这些计算必须用C++,别想着用Python。Python的GIL在高频场景下就是枷锁。
  2. 风险敞口监控:净头寸、最大亏损、杠杆率。这些指标要实时更新,我习惯用增量计算,别每次都全量重算。
  3. 压力测试:模拟极端行情下的风险暴露。这个可以异步跑,别阻塞主流程。

计算层的延迟优化,我总结了几条铁律:

优化手段 说明 效果
无锁队列 用环形缓冲区替代互斥锁,避免线程切换 延迟降低 60-80%
内存池 预分配内存,避免运行时malloc/free 延迟降低 40-50%
CPU亲和性 绑定核心,避免缓存抖动 延迟稳定性提升
SIMD指令 批量处理向量化数据 吞吐量提升 2-4x
⚠️ 避坑指南:我曾经在计算层用了std::unordered_map做快速查找,结果发现哈希冲突导致延迟抖动。后来换成开放寻址法的自定义哈希表,延迟稳定多了。记住,高频场景下「确定性」比「平均性能」更重要。

2.3 决策层:风控的大脑

决策层负责「判断」。数据算好了,指标出来了,到底要不要熔断?要不要撤单?这就是决策层的事。

决策层我习惯用事件驱动架构:

  • 规则引擎:把风控规则写成可配置的表达式。比如「当净头寸超过阈值时触发熔断」。别硬编码,否则每次改规则都要重启系统。
  • 状态机:每个策略都有自己的状态。正常、预警、熔断、恢复。状态切换要清晰,别出现死循环。
  • 多策略协调:不同策略的风控信号可能冲突。比如A策略要撤单,B策略要加仓。决策层要有优先级仲裁机制。

这里有个细节:决策层不能「想太多」。你想想看,行情每微秒都在变,决策逻辑如果超过3微秒,那还不如不做风控。我见过有人把机器学习模型塞进决策层,结果推理延迟几十微秒——这在高频场景下就是自杀。

🔑 核心原则:决策层只做「是/否」判断。复杂的分析交给计算层异步处理。决策层要快,要简单,要可预测。

2.4 执行层:最后一公里

执行层是风控系统的「手」。决策层说「撤单」,执行层就得真的把单子撤掉。这里最怕什么?最怕执行失败。

执行层的关键设计:

  1. 订单路由:直接连接交易所的API,别经过中间件。每多一层转发,就多几微秒延迟。
  2. 撤单管理:撤单请求要带唯一标识,防止重复撤单。我遇到过撤单指令发了两遍,结果交易所返回「订单不存在」——尴尬。
  3. 仓位同步:本地仓位和交易所仓位要保持一致。用增量同步,别每次都全量拉取。
  4. 异常处理:网络闪断、交易所拒绝、超时重试。这些都要有预案。

执行层的代码,我建议用「防御性编程」。每个API调用都要检查返回值,每个超时都要有兜底逻辑。别觉得麻烦,线上出问题的时候,这些检查能救命。

2.5 模块化与微服务思想

说完分层,再聊聊模块化。很多人一听到微服务就兴奋,觉得要拆成几十个服务。但在高频场景下,微服务要慎用。

我的建议是:

  • 核心链路用单体:数据层→计算层→决策层→执行层,这条路径上的模块尽量在同一个进程内。用进程内通信,别走网络。
  • 辅助功能用微服务:比如日志收集、监控告警、历史回放。这些可以独立部署,不影响主链路延迟。
  • 接口要清晰:模块之间用接口隔离,别直接依赖实现。这样即使以后要拆分,改动成本也低。
💡 我的习惯:核心模块用C++写,辅助模块用Go或Java。C++负责性能,Go负责易用性。各取所长,别一刀切。

2.6 高可用与低延迟设计

高可用和低延迟,听起来矛盾,其实可以兼得。关键是要分场景处理。

高可用设计

  • 主备切换:核心模块做1主1备,备机实时同步状态。切换时间控制在1秒内。
  • 熔断降级:如果某个模块超时,自动降级。比如计算层挂了,决策层用上一帧的数据做判断。
  • 心跳检测:每个模块每秒上报心跳。连续3次心跳丢失,触发告警。

低延迟设计

  • 零拷贝:数据从网卡到应用层,全程零拷贝。用DPDK + 共享内存。
  • 无锁编程:核心路径上不要有锁。用CAS操作和内存屏障替代。
  • 预分配资源:连接池、内存池、线程池。运行时不要动态创建资源。

你可能会问:「高可用和低延迟冲突怎么办?」我的答案是:核心链路保延迟,辅助链路保可用。比如风控决策必须低延迟,但日志上报可以容忍几毫秒延迟。分而治之,别混为一谈。

📌 总结一句话:分层架构的核心是「各司其职」。数据层快,计算层准,决策层稳,执行层狠。每一层做好自己的事,别越界。

好了,这一章就聊到这里。下一章我们深入计算层,聊聊具体的指标计算和优化技巧。