3、日志存储:高性能写入策略、日志轮转与压缩、冷热数据分层存储
日志存储这事儿,看着简单,其实坑特别多。我早年在一个高频团队里,就因为日志写入把交易引擎的延迟从5微秒拖到了50微秒——你想想看,这谁受得了?
今天咱们就聊聊,怎么把日志存得又快又省空间。
3.1 高性能写入策略
高频交易里,日志写入不能阻塞主交易路径。我个人的习惯是:能异步就异步,能批量就批量。
3.1.1 异步写入 vs 同步写入
同步写入简单,但每次写日志都要等磁盘IO。异步写入呢?先把日志扔到内存缓冲区,后台线程慢慢刷盘。
核心原则:交易线程只负责生产日志,不负责写磁盘。
代码示例(伪代码):
// 异步日志写入器
class AsyncLogger {
RingBuffer<LogEntry> buffer; // 无锁环形缓冲区
Thread flushThread; // 后台刷盘线程
void log(LogEntry entry) {
buffer.push(entry); // 微秒级操作
}
void flushLoop() {
while (true) {
batch = buffer.drain(); // 批量取出
writeToFile(batch); // 批量写入
}
}
}
我在项目中遇到过一个问题:缓冲区满了怎么办?丢日志还是阻塞?我的建议是:丢旧日志,保新日志。交易日志丢了旧的还能查,阻塞了交易引擎可就亏大了。
3.1.2 批量写入与缓冲
单条写日志,每次都要系统调用,开销巨大。批量写入能显著提升吞吐量。
| 写入方式 | 延迟(微秒) | 吞吐量(条/秒) |
|---|---|---|
| 单条同步写入 | 50-200 | 5,000 |
| 批量异步写入(64条一批) | 1-5 | 500,000+ |
你看,差距就是这么大。我建议缓冲区大小设为4KB或8KB,跟磁盘块大小对齐,性能最好。
3.1.3 文件映射(mmap)
嗯,这里要注意。mmap能把文件直接映射到内存,写日志就像写内存一样快。但有个坑:断电时可能丢数据。
警告:mmap写入的数据在断电时可能丢失。如果对数据完整性要求极高,建议配合fsync定期刷盘。
我曾经在一个策略回测系统里用了mmap,结果一次断电丢了半小时的日志。从那以后,我都是mmap + 每5秒一次fsync。
3.2 日志轮转与压缩
日志文件不能无限增长。你想想看,一个交易系统跑一天,日志可能就几十GB。不轮转?磁盘很快就满了。
3.2.1 基于时间的轮转
按小时或按天切分日志文件。我个人习惯按小时切,因为高频交易日志量太大,按天切一个文件可能上百GB,查起来也费劲。
// 日志轮转配置示例
log_rotation:
type: time_based
interval: 1h // 每小时切一次
max_files: 168 // 保留7天(24*7)
compress_after: 24h // 24小时后压缩
3.2.2 基于大小的轮转
有些场景更适合按大小轮转。比如每个日志文件不超过1GB,满了就切。
小技巧:我建议大小轮转和压缩配合使用。文件切分后立即压缩,能节省70%-90%的存储空间。
3.2.3 压缩策略
压缩算法怎么选?我做过对比测试:
| 算法 | 压缩比 | 压缩速度 | 解压速度 |
|---|---|---|---|
| gzip | 5:1 | 50 MB/s | 100 MB/s |
| lz4 | 3:1 | 500 MB/s | 1 GB/s |
| zstd | 6:1 | 200 MB/s | 400 MB/s |
高频交易日志,我推荐lz4。压缩比虽然一般,但速度快,不影响日志写入性能。归档日志可以用zstd,压缩比更高。
3.3 冷热数据分层存储
日志数据有冷热之分。热数据是最近几小时的,需要快速查询。冷数据是几天前的,查得少,可以存到便宜的地方。
3.3.1 分层架构
我设计的典型分层方案:
热层(SSD): 最近24小时日志,毫秒级查询
温层(HDD): 最近30天日志,秒级查询
冷层(对象存储): 30天以上日志,分钟级查询
说白了,就是用SSD保证性能,用HDD和对象存储降低成本。
3.3.2 数据迁移策略
数据怎么从热层迁移到冷层?我建议用后台定时任务,别影响主流程。
关键点:迁移时先复制再删除,保证数据不丢。万一迁移过程中系统挂了,还有原始数据在。
我曾经遇到过迁移时直接删源文件,结果迁移到一半磁盘满了,两边都没了。嗯,从那以后我再也不敢这么干了。
3.3.3 查询路由
查询日志时,系统要自动判断数据在哪一层。用户不需要关心底层存储。
class LogQueryRouter {
LogEntry query(timestamp, query) {
if (timestamp >= now - 24h) {
return queryHotLayer(query); // 查SSD
} else if (timestamp >= now - 30d) {
return queryWarmLayer(query); // 查HDD
} else {
return queryColdLayer(query); // 查对象存储
}
}
}
3.4 知识体系总览
下面这张图,把日志存储的核心逻辑串起来了:
我的经验:这套架构我用了三年,每天处理约500GB日志,从未丢过数据。关键就是异步写入不阻塞和分层存储降成本这两点。
好了,日志存储这块就聊到这儿。记住一句话:日志是系统的黑匣子,存得好不好,直接决定你出问题时能不能快速定位。