3、日志机制原理:操作日志、状态变更日志、日志的持久化与回放
日志这东西,说白了就是系统的「黑匣子」。
我在做量化系统的时候,最怕的就是半夜被电话吵醒——交易所那边说你的订单丢了,或者成交回报对不上。那时候你怎么办?没有日志,你连死因都查不出来。
所以今天咱们聊聊日志机制。别小看它,订单簿的可靠性,一半靠日志撑着。
3.1 操作日志:记录「谁干了什么」
操作日志,记录的是每一次用户操作。比如:
- 用户A 下了个买单,价格 100.5,数量 200
- 用户B 撤单了,撤的是订单ID #12345
- 管理员修改了交易对的状态
这些操作日志的格式,我一般这么设计:
{
"timestamp": 1699000000123, // 操作发生时间(毫秒)
"user_id": "user_001",
"action": "place_order", // 操作类型
"params": { // 操作参数
"symbol": "BTC/USDT",
"side": "buy",
"price": "100.5",
"quantity": "200"
},
"result": "success", // 操作结果
"order_id": "ord_20231001_001" // 生成的订单ID
}
为什么要记录这么细?
我曾经遇到过一个场景:用户投诉说他的撤单没成功,但系统显示撤单成功了。查日志发现,用户发的撤单请求里,订单ID 写错了——多了一个空格。这种问题,没有操作日志根本说不清楚。
3.2 状态变更日志:追踪「状态怎么变的」
操作日志记录的是「人做了什么」,状态变更日志记录的是「系统状态怎么变的」。
举个例子,一个订单的生命周期可能是这样的:
NEW → PARTIALLY_FILLED → FILLED
NEW → PARTIALLY_FILLED → PARTIALLY_CANCELED → CANCELED
NEW → CANCELED
每一次状态变更,都要记录一条日志:
{
"order_id": "ord_20231001_001",
"from_status": "NEW",
"to_status": "PARTIALLY_FILLED",
"timestamp": 1699000000500,
"reason": "match_engine", // 变更原因
"detail": { // 变更详情
"filled_qty": "50",
"filled_price": "100.5",
"remaining_qty": "150"
}
}
这里有个坑,我踩过:
状态变更必须是「有向无环图」。什么意思?就是你不能从 FILLED 再变回 NEW。我见过一个系统,因为状态机设计不严谨,订单在 FILLED 之后又被撮合引擎更新了一次,结果变成了「已成交+未成交」的诡异状态。嗯,那天的对账报表全乱了。
3.3 日志的持久化:写磁盘还是写数据库?
日志存哪儿?这是个经典问题。
我个人的经验是:操作日志写数据库,状态变更日志写文件。
为什么这么分?
- 操作日志需要频繁查询(查用户、查时间范围),数据库的索引能力更合适。
- 状态变更日志是顺序写入的,文件系统比数据库快 3-5 倍。而且回放的时候,顺序读文件比随机读数据库快得多。
文件日志的格式,我推荐用 WAL(Write-Ahead Logging) 的思路:
// 每条日志一行,用分隔符隔开
1699000000123|ord_001|NEW|PARTIALLY_FILLED|50|100.5
1699000000150|ord_001|PARTIALLY_FILLED|FILLED|150|100.5
1699000000200|ord_002|NEW|CANCELED|0|0
这种格式的好处是:
- 写入快,直接 append 到文件末尾
- 解析简单,按行读就行
- 压缩率高,文本格式可以压到原始大小的 20%
3.4 日志回放:从零重建订单簿
日志回放,就是把历史日志重新执行一遍,恢复出当时的系统状态。
什么时候需要回放?
- 系统重启后,从日志恢复订单簿
- 数据不一致时,用日志做「对账」
- 测试环境需要模拟生产数据
回放的流程,我画了张图:
回放的时候,有几个要点:
- 按时间戳排序:日志文件可能被切分过,多个文件之间要保证全局有序
- 幂等执行:同一个日志条目执行两次,结果应该一样
- 快照加速:如果日志太多(比如几亿条),从头回放太慢。我一般每 10 万条日志打一个快照,回放时从最近的快照开始
3.5 日志与快照的配合
日志和快照是「搭档」关系。
快照是「某一时刻的完整状态」,日志是「快照之后的变化记录」。
恢复的时候:
- 先加载最近的快照
- 再回放快照之后的所有日志
- 得到最终状态
这个机制的好处是:
- 恢复速度快(不用从头回放)
- 日志可以定期清理(快照之前的日志可以删掉)
- 快照本身也可以做校验(和日志回放结果对比)
好了,日志机制就聊到这儿。说白了,日志就是系统的「后悔药」。写得好,出问题能救你一命;写得不好,出问题你连怎么死的都不知道。
公众号:蓝海资料掘金营,微信deep3321