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 管冷存储与检索。把热温冷分层、阻塞隔离、背压传递三件事做对,这张地图就真正落地了。组件边界清楚,后面每篇的实现都有靶子。
延伸阅读
- 分段落盘细节,见 4.1。
- 通道背压实现,见 2.3。
- 存储分层对照,见 MySQL 实战。
- 日志采集互通思路,见 Node.js 云开发实战。
