🚀从病秧子到百万 TPS:一次用 Rust 攻克亿级 EVM 日志处理的极限性能实战

  • King
  • 发布于 3小时前
  • 阅读 56

关键词:Rust/区块链/EVM/并发编程/日志分析/tokio/governor/moka/高性能架构🧭导读这不是一篇单纯讲Rust的文章。而是一篇从一台机器、一个节点、到每天处理百日区块数据的性能奇迹诞生记。如果你:正在用RPC拉链上

关键词:Rust / 区块链 / EVM / 并发编程 / 日志分析 / tokio / governor / moka / 高性能架构


🧭 导读

这不是一篇单纯讲 Rust 的文章。

而是一篇从一台机器、一个节点、到每天处理百日区块数据的性能奇迹诞生记

如果你:

  • 正在用 RPC 拉链上日志,慢得想睡觉
  • 遇到限流 + OOM + 崩溃复跑 等地狱难题
  • 想通过 Rust 打造一套极致高性能的数据处理系统

那你一定不能错过这篇文章。
我将详细剖析从 性能病秧子到百万级吞吐系统的全过程


🗂️ 目录

  1. 项目背景:地狱级链上日志采集需求
  2. 初代版本:Rust 服务的无力挣扎
  3. 性能瓶颈全面诊断
  4. 重构之战:模块拆解与限流建模
  5. 架构细节深度剖析
  6. 断点续跑与容错机制
  7. 实测 Benchmark 对比:从 7 天都跑不完24小时处理 100 天
  8. 技术总结 & 可拓展方向

1. 项目背景:地狱级链上日志采集需求

需求看起来很“简单”:

  • 抽取某条 EVM 链上 数年历史区块日志
  • 每条日志需要绑定 区块时间戳
  • 需要进行基本的业务数据聚合处理
  • 日志有业务顺序依赖问题,无法做到完全并发,需要处理区块1的日志才能处理区块2日志
  • 最终导出为结构化数据供下游分析

实际运行中暴露出多个挑战:

困难项 描述
数据量巨大 总量达 数亿级区块,每个区块 平均约150条 交易日志记录
时间戳缺失 需要 额外调用 get_block 获取每条日志时间戳
API 限流 每节点每秒最多 10 req/s,超过容易封禁
节点资源有限 仅有 10 个节点可用,高并发成本受限
并发复杂度高 需要调度任务、缓存时间戳、并发写入、异常容错等

2. 初代版本:Rust 服务的无力挣扎

初始用一个基础 tokio 异步服务模型:

for block in block_range {
    let logs = get_logs(block).await?;
    let ts = get_block(block).await?.timestamp;
    write(logs, ts).await?;
}

结果:

  • 吞吐量低至 56 条/秒
  • 完全串行请求 + 写入
  • 没有缓存,每个区块都重复请求时间戳
  • 跑了一天,还没跑出 5% 数据…

简直就是一台 Rust 写的“病秧子爬虫”。


3. 性能瓶颈全面诊断

📌 问题归纳如下:

模块 问题描述
RPC 限流 每秒 10 请求,稍超就触发限制
请求串行 所有逻辑全在主线程执行,延迟极高
重复请求 get_block 每个区块都打一次
IO 阻塞 写入操作阻塞主线程,无法并发
错误处理 崩溃需手动重跑,无断点恢复机制

4. 重构之战:模块拆解与限流建模

我将系统重构为 4 层职责模型:

  • 任务调度器:负责按区块段派发任务
  • 并发请求池:并发请求 get_logsget_block
  • 缓存系统:moka + dashmap 管理时间戳与任务状态
  • 写入模块:异步聚合落盘,非阻塞

✅ 限流策略

每个 RPC 节点绑定 governor 限流器:

let limiter = RateLimiter::direct(Quota::per_second(nonzero!(10)));
limiter.until_ready().await;

配合轮询调度器,分摊 10 个节点的调用压力。


5. 架构细节深度剖析

✅ 系统架构图

任务流程:调度 → 请求 → 缓存 → 业务处理+落库

✅ 任务泛型调度

pub trait ProcessTask {
    type TaskInput;
    type TaskOutput;
    async fn process(&self, task: Self::TaskInput) -> anyhow::Result<Self::TaskOutput>;
}

结合通用 worker pool 可调度日志拉取器、时间戳补全器、写入器等。

✅ 缓存双模封装

// moka 时间戳缓存
let cache = moka::sync::Cache::new(10_000);
cache.insert(block_number, timestamp, Duration::from_secs(3600));

// dashmap 状态管理
let map: DashMap<u64, TaskStatus> = DashMap::new();

✅ 写入优化

  • 批量写入(如每 1000 条)
  • 支持 JSON Lines / Parquet / CSV
  • 异步 file writer + buffer channel

6. 断点续跑与容错机制

设计 checkpoint 模块保存任务状态:

{
  "123000": "Done",
  "123001": "Failed(RPC timeout)",
  "123002": "Pending"
}
  • 每 10K 区块持久一次状态
  • 程序重启自动加载
  • 崩溃后从失败段自动重跑

7. Benchmark 实测对比

阶段 吞吐量 日处理区块 时间戳支持 容错能力
初版(串行) 56 logs/sec < 5 天/天
多线程改版 3000 logs/sec ~40 天/天
缓存优化后 10000+ logs/s 100+ 天/天
高峰突破状态 4.X 秒/批 最快处理极限

8. 技术总结 & 可拓展方向

Rust 带来的能力

  • tokio 的并发调度模型适合精细任务管控
  • governor/moka 等生态极大简化限流与缓存设计
  • Rust 的无 GC & 高性能天生适合链上中间件

后续计划

  • 接入 ClickHouse/Chroma 进行在线日志分析
  • 增加 Web UI 展示任务状态与进度
  • 抽象出通用链上日志处理框架,支持多链切换
  • Prometheus + tracing 接入指标与可视化

🧵结尾彩蛋

曾经,我以为处理亿级链上数据必须靠 Spark、分布式、云服务……

现在我只用一台机器 + Rust 服务,就能每天处理百日区块。

Rust 改变了我处理数据的方式,也让我更加相信:架构与实现,决定你是否能把性能压榨到极致。


📣 欢迎留言讨论!

如果你:

  • 正在做链上数据服务
  • 想移植这套架构到你项目里
  • 对高性能系统的构建有兴趣

欢迎留言或私信我,我们一起在 Rust 的道路上,把“慢服务”干成“猛兽”!

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发