1.4 技术栈对齐:tokio 与存储地图

选型与机器定了,要把技术栈摊开对齐:tokio 承担什么、RocksDB/Redis/COS/CLS 各在哪个位置、谁管热数据、谁管归档。本篇把这张地图画清楚,后面每篇的代码和配置都往这里挂。技术栈对齐的本质是把每类数据放到它该去的地方,而不是把所有东西都丢给一个组件。

组件分工总表

组件 角色 在管道里的职责
tokio 异步运行时 多路复用、任务调度、超时控制
mpsc 有界通道 进程内背压 满了让上游等着
RocksDB 本地持久化 分段落盘、WAL、快速检索
Redis 远端协调 采样窗口、计数器、全局状态
COS 对象存储 归档转储、低成本长期保存
CLS 日志服务 统一检索与分析

地图的主线很清晰:日志从左到右,越往右越冷。热数据在内存与 RocksDB,温数据在 Redis,冷数据在 COS 与 CLS。任何混放(比如把归档塞进 Redis)都会在成本或性能上翻车。

tokio 为什么合适

日志管道是典型的IO 密集 + 少量 CPU 尖刺负载:大部分时间在等读写,压缩时突然忙一阵。tokio 的多路复用让大量 IO 等待并行,工作窃取调度把 CPU 尖刺摊到多核,正好对上这个画像。关键用法是把每个慢操作(写盘、写网络)都丢进异步任务,绝不在主循环里阻塞,否则多路复用直接失效。

场景 tokio 做法 原因
大量慢 IO 异步任务 + select 多路复用
压缩 CPU 尖刺 spawn_blocking 不阻塞调度
超时控制 tokio::time 兜底卡死
通道背压 mpsc 有界通道 满了等待

这也解释了为什么通道要用有界:无界通道把背压信号吞掉,上游永远不知道下游满了,内存一路涨到 OOM。有界通道是 tokio 地图里最重要的一个阀门。

存储分层:热、温、冷

层 载体 停留时长 淘汰策略
热 内存攒批 秒级 刷盘即走
温 RocksDB 分段 分钟到小时 段合并淘汰
准冷 Redis 采样窗口 最近窗口 滚动删除
冷 COS + CLS 长期 按保留策略

分层的意义不只是性能,更是成本:热层贵但小,冷层便宜但大。把冷数据放错热层,账单会教你做人。RocksDB 在这里的定位值得多说一句:它不是数据库替代品,而是「本地持久化的快速暂存」——管道重启后能从本地恢复最近数据,不必回拉远端。RocksDB 分段落盘的细节见 4.1。

采样与全局状态为什么放 Redis

单机管道用内存计数器就行,多机管道必须共享计数器:比如「每秒全局只放行 N 条审计日志」,靠单机计数等于每台都放行 N 条。Redis 承担所有跨进程的协调状态:采样窗口、全局计数、过节去重。放 Redis 的另一好处是管道重启不丢采样基线,冷启动不会突然过量放行。

数据 结构 用途
采样计数器 INCR + TTL 限流采样
全局版本号 STRING 配置热更新版本
去重指纹 SET + TTL 节假日日志去重

Redis 在这张地图里体量很小(MB 级),但作用是让多机管道像一台机器一样协调。云数据库 Redis 的落地可参考 腾讯云 Redis 文档。

地图串起来

采集读文件 → 有界通道 → 汇聚攒批压缩 → RocksDB 分段落盘 → 定时刷 COS 归档 + 转发 CLS 检索;Redis 在旁边管采样与全局状态。任何一段满了,背压沿着通道反传,保护上游不崩。整张地图的数据流与 Node.js 云开发实战 的日志采集章节互通,存储分层的思路也可对照 MySQL 实战 的冷热分表。

通道拓扑:谁连谁

仅有组件还不够,还得定清通道怎么布。推荐的拓扑是两级通道:第一级从采集到汇聚(有界、较短),第二级从汇聚到落盘(有界、较长)。两级都设有界通道的好处是背压可以分段传递:落盘慢时先顶在第二级,汇聚采集层照常读书;等第二级也满了,反压才传到采集。多一级缓冲就多一级缓冲时间,重启落盘的窗口期完全 прозрачный。

通道 位置 容量建议 满了传导
一级 采集 → 汇聚 几千条 采集等待
二级 汇聚 → 落盘 几万条 汇聚等待
WAL 汇聚本地 分区 80% 告警 不丢兜底

注意拓扑里不要画环:任何「A 写 B、B 又回写 A」的环路都会在背压时死锁。数据单向流动,是这个地图的铁律。 通路纠错的时候画一张真实的通道图,比看代码直观十倍。

组件故障的降级行为

地图画完,给每个组件定降级行为:Redis 挂了采样退化为本地近似计数;COS 挂了归档先堆本地并告警;CLS 挂了检索停一段时间不影响归档;RocksDB 损坏从 WAL 重建。每个组件都有「挂了怎么办」的预案,地图才是活的。

组件 挂了之后 降级方式
Redis 采样失真 本地近似计数
COS 归档停滞 本地堆积 + 告警
CLS 检索停滞 归档不受影响
RocksDB 暂存损坏 WAL 重放重建

降级行为里有一个共同原则:降级不等于静默。任何进入降级模式的组件,必须同时打出一条告警,否则「看起来在跑、实际在糊弄」会成为常态,等用户发现检索少了三天日志才知道 CLS 早挂了。告警阈值建议在降级触发时就发,而不是降级持续一小时才发——早发现早修,日志管道的故障窗口越短越好。

升级路径:从小流量到大规模

地图不是一天建成的,给一条渐进路径。第一版只用「文件 → 攒批 → COS」三件套,跑通主流程;第二步加 Redis 做采样与全局状态,上多机;第三步引入 RocksDB 分段与 WAL,做崩溃恢复;最后接 CLS 做统一检索。每一步都能独立上线独立验收,比一口气铺全图稳得多。

版本 增量能力 价值
V1 文件 + 攒批 + COS 主流程跑通
V2 加 Redis 多机协调
V3 加 RocksDB + WAL 崩溃恢复
V4 接 CLS 统一检索

升级路径同时是一份新成员导读:按 V1 到 V4 读代码,能快速建立整张地图的全貌。

组件选型的替代方案

顺带给一份替代方案对照,避免「非 tokio 不可」的误解。异步运行时还有 async-std(接口更接近标准库),单线程场景用标准库线程加通道一样能跑。RocksDB 可用 sled 或简单的「WAL 加索引文件」替代,量小反而更轻。Redis 可用任何共享 KV。选这些替代不丢人,地图的逻辑比组件的牌子重要。

组件 主选 轻量替代 取舍
运行时 tokio async-std / 线程 生态与性能
暂存 RocksDB sled / WAL 文件 量小选轻
共享状态 Redis 任一共享 KV 已有基建

踩坑提示

最经典的坑是在异步任务里做阻塞 IO:有人把同步写盘丢进 spawn 的任务,结果整个运行时被一个慢盘卡住,所有通道一起积压。Rust 的异步是协作式,阻塞调用必须用 spawn_blocking 隔离。另一个坑是RocksDB 当数据库用:给它加复杂索引和事务,最后调参调到怀疑人生——它在本项目的角色只是分段暂存,简单追加加读取即可,别用出花。

小结

tokio 管 IO 多路复用与任务调度,有界通道管背压,RocksDB 管本地分段暂存,Redis 管跨机协调,COS 与 CLS 管冷存储与检索。把热温冷分层、阻塞隔离、背压传递三件事做对,这张地图就真正落地了。组件边界清楚,后面每篇的实现都有靶子。

延伸阅读