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 知识体系总览

下面这张图,把日志存储的核心逻辑串起来了:

日志存储核心架构 高性能写入策略 • 异步写入 • 批量缓冲 • mmap文件映射 • 无锁环形缓冲区 日志轮转与压缩 • 基于时间轮转 • 基于大小轮转 • lz4/zstd压缩 • 自动清理策略 冷热分层 • 热层:SSD • 温层:HDD • 冷层:对象存储 • 自动迁移 核心设计理念 1. 写入不阻塞:交易线程零等待,日志写入完全异步化 2. 空间可控:通过轮转+压缩,日志占用空间可预测 3. 成本优化:热数据用SSD保证性能,冷数据用廉价存储降低成本 4. 查询透明:用户无需关心数据在哪一层,系统自动路由

我的经验:这套架构我用了三年,每天处理约500GB日志,从未丢过数据。关键就是异步写入不阻塞分层存储降成本这两点。

好了,日志存储这块就聊到这儿。记住一句话:日志是系统的黑匣子,存得好不好,直接决定你出问题时能不能快速定位

专注资料整理