一、日志基础:高频交易日志的独特特征、日志级别与分类标准、日志采集架构设计

1.1 高频交易日志的独特特征

做高频交易系统这些年,我最大的感触就是——日志这东西,在别的系统里可能是「辅助工具」,但在高频交易里,它简直就是「黑匣子」。

为什么这么说?你想想看,高频交易系统一秒钟可能要处理上万笔订单,延迟以微秒计。一旦出问题,你根本没有机会现场调试。能依赖的,只有日志。

我个人习惯把高频交易日志的特征总结为四个字:快、准、全、小

  • :写入延迟必须极低。我曾经见过一个团队用 Java 的 log4j 默认配置,结果每次写日志都要做同步锁,直接把交易延迟从 5 微秒拖到了 50 微秒。嗯,这显然不行。
  • :时间戳精度要到纳秒级。普通系统用毫秒就够了,但高频交易里,两个订单谁先到,差一微秒可能就是几百万的盈亏。
  • :不能丢日志。我在项目中遇到过网络抖动导致日志采集端丢包,结果排查问题时发现关键日志缺失,那种感觉……就像破案时监控录像少了一帧。
  • :日志体积要控制。高频交易一天能产生几十 GB 的日志,如果不做压缩和裁剪,磁盘很快就爆了。

核心要点:高频交易日志不是给人看的,是给机器和事后分析用的。所以它的设计目标,首先是「不拖累性能」,其次是「不丢失数据」。

1.2 日志级别与分类标准

说到日志级别,很多人第一反应就是 DEBUG、INFO、WARN、ERROR 那套。但在高频交易里,这套标准其实不够用。

我建议把日志级别重新定义一下,更贴合交易场景:

级别 含义 典型场景 是否写入磁盘
TRACE 全量跟踪 每个订单的完整生命周期 按需开启,默认关闭
DEBUG 调试信息 策略计算中间结果 开发环境开启
INFO 常规事件 订单成交、撤单、行情更新 始终写入
WARN 异常但不致命 网络重连、行情延迟超标 始终写入
ERROR 严重错误 交易接口断开、数据库写入失败 始终写入,且触发告警
FATAL 致命错误 系统崩溃、内存溢出 写入独立文件,立即通知

除了级别,分类也很重要。我个人习惯把日志分成三类:

  • 交易日志:记录订单流、成交回报、撤单指令。这是最核心的日志,必须保证不丢、不乱序。
  • 系统日志:记录进程状态、内存使用、线程池情况。用来排查系统层面的问题。
  • 性能日志:记录每个环节的耗时,比如网络延迟、数据库写入耗时、策略计算耗时。这是性能诊断的关键。

小技巧:我曾经把交易日志和系统日志混在一起写,结果排查问题时,要在海量日志里翻来翻去。后来我强制把它们分到不同的文件,甚至不同的磁盘分区,效率提升了不少。

1.3 日志采集架构设计

日志采集架构,说白了就是「日志从产生到存储,中间走哪条路」。高频交易系统对这条路的要求很苛刻:延迟低、不丢数据、可扩展。

我画了一张图,帮你理解整个架构:

高频交易日志采集架构 交易引擎 产生交易日志 行情网关 产生行情日志 风控模块 产生风控日志 本地环形缓冲区(Ring Buffer) 无锁写入,单条日志写入耗时 < 100ns 异步写入线程(独立线程池) 批量写入,每批 1000 条或每 10ms 刷一次 本地磁盘(SSD) 远程采集中心 对象存储(归档)

这个架构的核心思路是:日志产生端只负责写入本地缓冲区,绝不阻塞交易主流程

具体来说,我推荐这么设计:

  1. 本地环形缓冲区:每个线程分配一个独立的 Ring Buffer,无锁写入。我见过有人用全局锁队列,结果日志写入成了性能瓶颈。嗯,这其实是个很常见的坑。
  2. 异步批量写入:一个独立的线程负责从缓冲区取数据,批量写入磁盘。批量大小可以动态调整,我一般设成 1000 条或 10ms 刷一次,哪个先到就触发。
  3. 双写策略:同时写入本地磁盘和远程采集中心。本地磁盘保证数据不丢,远程采集中心方便集中分析。

避坑指南:我曾经在项目中只依赖远程采集,结果网络抖动导致日志丢失了 3 秒钟的数据。那 3 秒里正好发生了交易异常,排查时完全找不到线索。从那以后,我坚持「本地磁盘 + 远程采集」双写,本地磁盘保留至少 7 天的日志。

1.4 日志格式规范

日志格式这件事,看似简单,但做不好会让人抓狂。我见过有人把日志写成「order received」「order sent」这种模糊的描述,事后根本没法分析。

我建议的日志格式如下:

[时间戳] [级别] [模块] [线程ID] [会话ID] [消息] [关键字段:值]

举个例子:

2025-01-15 14:30:22.123456789 | INFO | TRADE | tid=0x7f8a | session=abc123 | 订单已发送 | order_id=ORD202501150001 | symbol=BTCUSDT | price=45000.12 | qty=0.1 | latency=12.3us

这里有几个关键点:

  • 时间戳精度到纳秒:用 clock_gettime(CLOCK_REALTIME)System.nanoTime() 获取。
  • 会话 ID:每个交易会话分配唯一 ID,方便串联整个交易链路。
  • 关键字段:把订单 ID、价格、数量、延迟等直接打在日志里,避免事后去查数据库。

个人经验:我习惯在日志里加一个 latency 字段,记录从事件发生到日志写入的耗时。这样在分析时,可以快速判断是系统慢还是日志写入慢。

1.5 日志采集的常见陷阱

最后,分享几个我踩过的坑:

  • 日志写入阻塞交易线程:这是最致命的。解决办法就是前面说的——异步写入,绝不阻塞。
  • 日志文件轮转导致丢数据:我曾经用 logrotate 做日志轮转,结果在轮转瞬间,新日志写入了旧文件。后来我改用「按大小轮转 + 信号通知」的方式,才彻底解决。
  • 日志采集端成为单点:远程采集中心如果挂了,日志就丢了。解决办法是本地先缓存,等采集中心恢复后再补传。

好了,日志基础这部分就聊到这儿。记住一句话:日志不是写给别人看的,是写给未来的自己看的。设计得好,它能帮你省下无数排查问题的时间。


专注资料整理