有一类工程问题不是因为代码写错了而是因为系统架构在规模增长之后触碰到了物理极限。Cloudflare 的机器学习团队经历的正是这种情况。他们的 Bot 检测系统需要对每一个 HTTP 请求做实时机器学习推理而 Cloudflare 的流量峰值是每秒 6300 万次请求。在这个规模下任何看起来还好的延迟乘以请求量之后都会变成一个天文数字。这篇文章记录了他们如何把机器学习特征提取的 p99 延迟从9.5 毫秒降到18 微秒——降幅 99.8%速度提升 528 倍。原文链接https://blog.cloudflare.com/scalable-machine-learning-at-cloudflare/mmap-sync 开源地址https://github.com/cloudflare/mmap-sync先理解这套系统在做什么Cloudflare 的 Bot Management 系统需要对每一个 HTTP 请求打分判断它是真实用户还是机器人流量。这个评分在所有检测机制中覆盖面最广为超过72%的 HTTP 请求提供最终的 Bot Score 决策。核心推理引擎是CatBoost一个以低延迟推理著称的梯度提升框架。但推理本身只是延迟方程的一部分另一部分是特征提取和准备。这两件事合在一起才决定了每个请求实际感受到的延迟。特征的类型也在演进早期只用单请求特征比如某个 HTTP 头是否存在、值是什么。但这类特征太容易伪造攻击者稍作修改就能绕过检测。后来引入了跨请求聚合特征比如某个 IP 在过去一段时间内关联过多少个不同的 User-Agent这类特征更难伪造但也更难实时提取。旧系统Gagarin 的能与不能负责提供跨请求聚合特征的系统叫Gagarin用 Go 实现本质上是一个特征服务平台。工作流程大致如下HTTP 请求到达边缘节点从请求属性中提取维度键先查多层 Lua 缓存命中则直接用缓存未命中时通过 Unix Domain Socket 发 memcached 请求给 GagarinGagarin 返回对应的特征向量特征向量送入 CatBoost 模型产出 Bot Score早期这套系统运转良好p50 延迟约200 微秒。但随着特征数量增加、流量持续增长缓存命中率开始下降延迟随之恶化p50500 微秒p99 峰值10 毫秒团队对 Gagarin 做了大量的低层次调优最终触碰到了一个无法通过调参解决的边界。问题的根源Unix Socket 不够快Cloudflare 团队用第一性原理的方式重新审视了这个问题操作系统提供的最高效进程间通信方式是什么他们用 ipc-bench 这个开源工具测量了 Linux 上各种 IPC 机制的延迟条件百万次 1024 字节消息的双向通信IPC 方式平均延迟 (μs)平均吞吐 (msg/s)TCP socket8.74114,143Unix Domain Socket5.61177,573管道Pipe4.73210,369消息队列4.40226,421Unix 信号2.45404,844共享内存0.5981,616,014内存映射文件0.5031,908,613结论一目了然Unix socket 已经是相对高效的选项但和共享内存、内存映射文件相比还差了整整一个数量级。旧系统的延迟问题根源之一就在于必须经过 Unix socket 这条慢路。评估了六种方案最终选了内存映射文件在确定方向之前团队系统性地排除了其他选项继续优化 GagarinGo 的 GC 暂停、hashmap 查找性能、Unix socket 同步开销这些是语言和架构层面的固有限制调参无法根治。迁移到 QuicksilverCloudflare 内部的分布式 KV特征的更新频率太高会对 Quicksilver 的其他用途产生负面影响且底层仍然是 Unix socket。扩大多层缓存把几千万个维度键连同特征向量全部缓存在内存里会导致每个 worker 线程各维护一份副本内存消耗不可接受。对 Unix socket 做分片能部分缓解争用但引入了额外复杂度且治标不治本。换用 RPCRPC 仍然需要某种通信总线TCP/UDP/UDS性能不会有本质改变。最终选定内存映射文件mmap。三个关键技术决策选定内存映射文件之后还需要解决三个核心问题如何在文件里高效查找特征如何在高并发下安全更新如何消除反序列化开销无等待同步Wait-free普通的加锁mutex/spinlock在高并发下会引入争用正是 Unix socket 方案的痛点之一。Cloudflare 的设计受 Linux 内核RCURead-Copy-Update模式和Left-Right 并发控制技术启发采用了无等待同步维护两份数据副本存储在两个独立的内存映射文件中由单个 writer管理写入写入时更新非活跃副本完成后原子切换版本号多个 reader 可以并发读取活跃副本无需任何锁第三个文件专门存储同步状态当前活跃文件索引、数据大小、校验和、各副本活跃读者计数这套机制保证了读操作永远不会被阻塞且在有限步骤内必然完成——这是比 lock-free 更强的保证。零拷贝反序列化rkyv从文件里读数据通常需要把字节流反序列化成内存中的数据结构这个过程有拷贝和计算的开销。零拷贝反序列化的思路是让序列化后的字节布局和内存布局完全一致这样可以直接把文件内存映射后的字节当作 Rust 结构体来用不需要任何拷贝或转换。Cloudflare 选用了rkyv框架它是少数几个能对HashMap做零拷贝访问的 Rust 序列化库之一。读取特征时数据不会被复制也不会有额外的解析计算直接引用映射内存中的字节。mmap-sync把三个技术打包成一个 Rust crateCloudflare 把内存映射文件 无等待同步 零拷贝反序列化三者结合封装成了一个 Rust crate命名为mmap-sync并开源发布。核心是一个叫Synchronizer的结构体接口极其简洁implSynchronizer{// 写入任意可序列化的 Rust 结构体pubfnwriteT(mutself,entity:T,grace_duration:Duration)-Result(usize,bool),SynchronizerError{...}// 零拷贝读取返回带生命周期保护的引用pubfnreadT(mutself)-ResultReadResultT,SynchronizerError{...}}读操作返回一个 RAII guard持有期间自动增加活跃读者计数出作用域后自动减少——writer 会等到所有读者离开之后才回收旧副本整个过程对调用方完全透明。BLISS新系统的完整架构基于 mmap-syncCloudflare 重新设计了整个 Bot 检测的基础设施命名为BLISSBots Liquidation Intelligent Security System由两个组件构成bliss service用 Rust 实现的多线程 sidecar daemon负责数据的写入侧周期性地从上游拉取最新的机器学习特征和维度数据解析后通过 mmap-sync 写入内存映射文件基于 Tokio 异步运行时擅长大批量数据处理和 I/O 密集操作bliss library用 Rust 实现的单线程动态库负责读取和推理侧通过 FFI 嵌入每个 worker 线程用 Lua 模块调用从内存映射文件中零拷贝读取特征调用 CatBoost 模型完成推理产出 Bot Score零堆分配所有数据结构预先分配运行时只用栈内存用 dhat 堆分析器在集成测试中强制验证这一点SIMD 优化对某些请求属性的 hex 解码使用 AVX2/SSE4 指令集速度提升 10 倍编译配置也做了深度调优[profile.release] codegen-units 1 # 全程序优化不分片 lto fat # 跨 crate 链接时优化 opt-level 3 # 最高优化级别 debug true # 保留调试符号不影响性能上线数据延迟改善了多少个数量级延迟指标改造前 (μs)改造后 (μs)变化p505329降低 98.3%快 59 倍p999,51018降低 99.8%快 528 倍p99916,00029降低 99.8%快 551 倍在整体 HTTP 请求处理层面整体平均处理延迟降低12.5%Bot Management 模块延迟降低55.93%团队用了一个很直观的换算来说明这个改善的量级在 Cloudflare 每秒 4600 万请求的规模下每个请求节省 523 微秒等价于每天节省超过 65 年的处理时间。除延迟之外其他指标同样改善显著特征可用性从有损达到 100%消除了 Unix socket 超时误报和漏报率下降释放了等价于数千个 CPU 核心和数百 GB 内存的资源提升了整体服务器利用率清除了数千行 Lua 和 Go 代码降低了技术债务机器学习能力大幅扩展可以承载数百个特征、数十个维度和多个并行模型这套方案的本质这次重构表面上是一次延迟优化但背后的工程逻辑值得单独提炼。第一从性能测量开始而不是从直觉开始。用 ipc-bench 系统性地测量各种 IPC 方式的延迟数据驱动选型不靠猜测。第二找到真正的瓶颈而不是在边际上优化。Gagarin 经过大量调优之后p99 依然是 10ms。因为问题不在代码质量在于 Unix socket 这条通信路径本身的物理限制。换掉通信路径才能突破上限。第三把解决方案抽象成可复用的基础设施。mmap-sync 不只是 BLISS 的内部实现细节而是一个可以独立使用的 Rust crate开源之后整个社区都可以用同一套机制解决类似问题。在 Cloudflare 这个规模上每一微秒的节省都在被放大但解决问题的思路——测量、找根因、换架构、抽象复用——在任何规模的系统里都适用。