QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

没有 NGINX 和 OpenResty 的未来:Cloudflare 工程师正花费大量时间用 Rust 重构现有功能

  • 2023-02-27
    北京
  • 本文字数:3946 字

    阅读完需:约 13 分钟

没有 NGINX 和 OpenResty 的未来:Cloudflare 工程师正花费大量时间用 Rust 重构现有功能

在 Cloudflare 公司,工程师们正在花费大量时间重构或重写现有功能。


当每年处理的流量增长一倍时,原本最优雅的问题解决方案往往会随着工程约束条件的变化而迅速过时。不仅如此,面对每秒高达 4000 万的请求总量,即使流经 Cloudflare 网络的全部请求中有 0.001% 发生问题,代表的也是冲击数百万用户的大事件。或者从另一个角度讲,发生概率仅为万亿分之一的罕见事件在这样的运行规模下每天都会出现。这就是 Cloudflare 所面临的最大的问题。


长期以来,Cloudflare 一直依赖 Nginx 作为其 HTTP 代理堆栈的一部分。随着 Cloudflare 规模的扩大, NGINX 的处理能力已经不能满足业务需求了。


去年 9 月,他们宣布已将 Nginx 替换为其内部由 Rust 编写的 Pingora 软件,想以此建立一个更快、更高效、更通用的内部代理,作为他们当前和未来产品的平台。


但这还不是全部,上周 Cloudflare 又发布了一篇博客称,他们用 Rust 编写了 Cloudflare 基础设施中最古老和最不为人所知的部分 cf-html 的替代品。Cf-html 是一套用于在网站源到网站访问者之间解析并重写 HTML 的框架。从创立之初起,Cloudflare 就提供相关功能,可以为用户即时重写 Web 请求的响应正文。它位于 Cloudflare 核心反向 Web 代理 FL(Front Line)之内。FL 运行着 Cloudflare 应用程序的大部分逻辑,因此无疑这次替换更具有挑战性。


同时 FL 作为 OpenResty 的一部分运行在 NGINX 之上,“通过这样做,我们为完全摆脱 NGINX 铺平了道路。”“很明显,这套曾经代表着开发者易用性与速度性最佳组合的平台,已经开始显露出明确的时代局限性。”


“我们正在逐步替换掉用于运行 NGINX/OpenResty 代理的组件”,从而构建一个“没有 NGINX 的未来”。



“内存安全”也是一大原因


FL 主要由 Lua 脚本语言编写的代码组成,作为 OpenResty 的一部分运行在 NGINX 之上。为了直接与 NGINX 进行交互,其中某些部分(如 cf-html)是用 C 和 C++ 等低级语言编写的。


过去,Cloudflare 掌握着大量这样的 OpenResty 服务,但现在留下的就只有 FL 等为数不多的几种了,其他组件已经被转移到了 Workers 或者基于 Rust 的代理处


所有 cf-html 逻辑都是用 C 语言编写的,因此跟其他大型 C 代码库一样,它也容易受到内存损坏问题的困扰。2017 年,团队在尝试替换部分 cf-html 时就曾引发安全漏洞。FL 从内存中读取任意数据并将其附加至响应主体,而这可能包含同一时间通过 FL 的其他请求中的数据。这次安全事件,也就是后来广为人知的 Cloudbleed


自那次事件以来,Cloudflare 实施了式项政策和保障措施,以确保不再发生类似的问题。虽然多年来一直在 cf-html 上开展工作,但该框架几乎没有实现什么新功能。现在的业务情况导致大家对 FL 中的任何崩溃都非常敏感(当然,对网络上任何进程的崩溃都很敏感),特别是那些可能影响到响应性能的问题。


时间快进到 2022、2023 年,FL Platform 团队收到的请求越来越多,大家希望改用新的系统,从而轻松查看和重写响应主体数据。与此同时,Cloudflare 的另一支团队一直在为 Workers 开发新的响应主体解析和重写框架,名为 lol-html,即低输出延迟 HTML。Lol-html 不仅比 Laxy HTML 更快、更高效,而且目前已经在 Worker 接口中得到了全面的生产应用。另外,它是用 Rust 编写的,所以在内存处理方面比 C 语言安全得多。


“总而言之,Lol-html 正是我们用来替换 FL 中古老陈旧 HTML 解析器的理想选项。”Cloudflare 的工程师 Sam Howson 在博客中表示。



于是乎,该团队开始尝试用 Rust 编写新的框架,让该框架把 lol-html 合并进来。这样既能帮助其他团队编写响应主体解析功能,又不会造成大量安全问题。新系统被定名为 ROFL,即 FL 响应监工(Response Overseer for FL),这是一个完全用 Rust 编写的全新 NGINX 模块。


截至目前,ROFL 已经在生产环境中每秒处理数百万个响应,其性能可以与 cf-html 相媲美。“在构建 ROFL 的过程中,我们得以弃用 Cloudflare 整个代码库中最糟糕的部分,同时给 Cloudflare 各团队提供了一套强大系统,供他们以响应主体数据解析和重写为基础编写出更多功能。”


个中挑战


给飞机更换引擎总会有一些挑战。


NGINX 模块系统也给各模块的工作方式提供了极大的灵活性,使其能高度匹配特定用例。当然,这种灵活性设计也有相应的问题。他们遇到的一大挑战,跟 Rust 和 FL 之间的响应数据处理方式有关。在 NGINX 当中,响应主体会被拆分成块,之后将这些块串连起来形成一个列表。另外,如果响应规模很大,则每条响应可能对应多个链接列表。


要想高效处理这些块,就需要加快对各个块的处理和传递速度。在编写用于操作响应的 Rust 模块时,大家往往会想到在链表中采用基于 Rust 的视图。但如果这样做,就必须确保在变更基于 Rust 的视图时,也一并更新底层 NGINX 数据结构,否则 Rust 与 NGINX 间的不同步会导致严重 bug。Cloudflare 工程师以 ROFL 早期版本中的一条令人头痛的小函数为例,讲解了他们曾经遇到过的挑战。


fn handle_chunk(&mut self, chunk: &[u8]) {    let mut free_chain = self.chains.free.borrow_mut();    let mut out_chain = self.chains.out.borrow_mut();    let mut data = chunk;
self.metrics.borrow_mut().bytes_out += data.len() as u64;
while !data.is_empty() { let free_link = self .pool .get_free_chain_link(free_chain.head, self.tag, &mut self.metrics.borrow_mut()) .expect("Could not get a free chain link.");
let mut link_buf = unsafe { TemporaryBuffer::from_ngx_buf(&mut *(*free_link).buf) }; data = link_buf.write_data(data).unwrap_or(b""); out_chain.append(free_link); }}
复制代码


这段代码的设计目标是获取 lol-html 的 HTMLRewriter 输出,并将其写入缓冲区的输出链。重要的是,输出可能大于单一缓冲区,所以需要在循环内从链中取出新的缓冲区,直到将所有输出均写入缓冲区。在这样的逻辑中,NGINX 应该负责从空闲链中取出缓冲区,再将新块附加到输出链上。它确实也是这么做的,但如果只考虑 NGINX 处理链表视图的方式,往往会忽视 Rust 不会更改其 free_chain.head 所指向的缓冲区。


这就导致逻辑永远循环,且 NGINX 工作进程完全锁定。这类问题可能需要很长时间才能发现,特别是在意识到其根源与响应主体的大小有关之前,他们甚至没法稳定地加以重现。


另外,使用 gdb 获取 coredump 来执行分析也很困难,因为当大家注意到内存占用过量而开始写入硬盘时,进程内存已经增长到了可能令服务器崩溃的程度,这时候做什么都太晚了。幸运的是,这段代码从没被投入生产环境。跟以往一样,虽然 Rust 编译器能帮助团队发现很多常见错误,但如果数据是通过 FFI 共享自另一个环境,那么即使不直接使用 unsafe 也会存在很多隐患。所以必须格外小心,特别是在 NGINX 的这种灵活性“优势”可能导致整台设备停止服务的情况下。


该团队面临的另一个重大挑战,跟传入响应正文块的背压有关。本质上,如果 ROFL 因必须向传输流中注入大量代码(例如用大量 JavaScript 替换电子邮件地址)而增加了响应的大小,则 NGINX 能以比单独推出更快的速度,将 ROFL 的输出提供给其他下游模块。这时如果下一模块的 EAGAIN 错误未得到处理,则可能导致数据被丢弃、HTTP 响应主体被截断。这也是个很难通过测试发现的问题,因为大多数时候响应的刷新速度是够的,背压并不会造成影响。为了将其解决,他们创建了一条特殊的链来存储这些被称为 saved-in 的块,再用一种特殊的方法完成附加。


#[derive(Debug)]pub struct Chains {    /// This saves buffers from the `in` chain that were not processed for any reason (most likely    /// backpressure for the next nginx module).    saved_in: RefCell<Chain>,    pub free: RefCell<Chain>,    pub busy: RefCell<Chain>,    pub out: RefCell<Chain>,    [...]}
复制代码


实际上,Cloudflare 工程师们决定在短时间内对数据进行“排队”,这样就不会因为提供速度超出处理速度而冲垮其他模块。NGINX 开发者指南中提供了很多重要的信息,但这里的案例显得比较孤立、不成体系,所以并没有包含他们遇到的问题。这类情况其实是 NGINX 复杂工作环境所催生出的结果,往往只有少数用户能够发现。


写在最后


Cloudflare 工程师对 Rust 表现出了极度的热爱,并在整个基础设施中使用它来获得内存安全优势、更现代的功能和其他优势。


该博客还指出,他们正在招聘更多的 Rust 工程师,并谈到了 Rust 对他们的好处:


“这里我们要特别感谢 Rust 的存在,只有这款语言能让我们在获得速度优势的同时实现高安全性,并获得 Bindgen 和 Serde 等高质量库的协助。


大多数人认为编程语言的安全性优势只体现在预防 bug 上,但事实不仅如此——作为一家规模化企业,我们发现语言的安全优势还能让某些极度困难、甚至原本认为与安全相悖的目标成为现实。


无论是用类似 Wireshark 的过滤语言来编写防火墙规则、允许数百万用户编写任意 JavaScript 代码并直接在我们平台上运行,还是即时重写 HTML 响应,Rust 都为我们的服务划定了严格的执行边界,让这种种不可能成为了可能。我们也欣慰地看到,Rust 的普及正逐步将那些曾经困扰开发行业的内存安全问题丢进历史的垃圾堆。”


声明:本文为 InfoQ 翻译整理,未经许可禁止转载。


今日好文推荐


开源意味着不问责,我们准备好应对比 Log4Shell 更大的安全危机了吗?|Log4j 一周年特别报道


阿里过去一年裁员达19000人;字节跳动布局中国版 ChatGPT;马斯克称下周将开源推特算法代码 | Q资讯


技术裁员正在助长新的创业潮:本来犹豫要不要创业,没想到公司替我做了决定


入行 14 年,我还是觉得编程很难:给大项目写代码没意思还危险


2023-02-27 19:388199

评论

发布
暂无评论
发现更多内容

新零售标杆 SKG 全面拥抱 Serverless,实现敏捷交付

阿里巴巴云原生

阿里云 Serverless 云原生 合作案例

历时4个月,4大板块,328页52188字,SpringMVC源码解析文档

冉然学Java

Java spring Spring MVC 构架 Spring Web MVC、

首发!清华大佬耗时几个月总结的这份Spring生态全家桶核心知识宝典助你剑指大厂offer

了不起的程序猿

Java spring 后端 java程序员 java面试

五、《图解HTTP》- RSS和网络攻击

懒时小窝

HTTP 图解https

Neo4j导入思知OwnThink开源的知识图谱

Joshua

nlp neo4j 知识图谱

室内led大屏幕可以用在室外吗?​

Dylan

LED显示屏 户外LED显示屏 户内led显示屏

N、《图解HTTP》读书笔记 - 附录

懒时小窝

资料 图解https 参考数据

软件交付周期缩短!且看精益思想如何加速全局价值流动

嘉为蓝鲸

DevOps 敏捷 精益

巨细靡遗流程控制,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang流程结构详解EP09

刘悦的技术博客

Go 教程 Go web go语言 Go 语言

Java将PDF拆分为多个 PDF 文件

在下毛毛雨

Java PDF 拆分PDF

共建共享数字世界的根:阿里云打造全面的云原生开源生态

阿里巴巴云原生

阿里云 开源 容器 RocketMQ 云原生

不要让CMDB沦为数据孤岛!运维高质量CMDB“修炼”之道

嘉为蓝鲸

运维 数据 配置 CMDB 配置管理

涛思数据加入龙蜥社区,携手共建时序数据库生态

OpenAnolis小助手

数据库 涛思数据 开源 龙蜥社区 CLA

四、《图解HTTP》- 状态码

懒时小窝

HTTP 状态码 图解https

开源一夏 | 使用 JavaScript 的响应式计数器动画

海拥(haiyong.site)

开源 8月月更

Spring Cache设计之美,你品,你细品...

华为云开发者联盟

后端 开发

干货合集 | 关于制品库,你了解多少?

嘉为蓝鲸

DevOps 研发 制品库

Python图像处理丨带你掌握图像几何变换

华为云开发者联盟

Python 人工智能

分布式系统大势所趋,银行运维如何与时俱进?

嘉为蓝鲸

架构 分布式 运维 金融 银行

注册配置、微服务治理、云原生网关三箭齐发,阿里云 MSE 持续升级

阿里巴巴云原生

阿里云 微服务 云原生 网关

六、《图解HTTP》- 用户身份认证

懒时小窝

HTTP 图解https

终、《图解HTTP》读书笔记 - 汇总篇(总结)

懒时小窝

读书笔记 读书 HTTP 图解https #读书

【SimpleFunction系列二.2】SpringBoot注解整合Redisson分布式锁

莫逸风

分布式锁 redisson 分布式锁 企业级应用

【计算讲谈社】第八讲:AI 技术的“纺织业”是什么?

大咖说

人工智能 商业化

场景品牌易观千帆,助力数智化需求持续升级

易观分析

数字经济 数智化

秒杀架构分析与实践

Bruce Duan

签约计划第三季

二、《图解HTTP》- HTTP协议历史发展(重点)

懒时小窝

HTTP 图解https

三、《图解HTTP》- 报文内的 HTTP信息

懒时小窝

HTTP 图解https

语音直播app——满足不同行业的业务需求

开源直播系统源码

软件开发 直播系统源码 语音直播系统 语音直播app

C#/VB.NET:在不同Excel工作簿之间复制单元格区域和工作表

Geek_249eec

C# Excel VB.NET 单元格区域 工作表

七、《图解HTTP》- HTTP首部和HTTP协作服务器

懒时小窝

HTTP 图解https

没有 NGINX 和 OpenResty 的未来:Cloudflare 工程师正花费大量时间用 Rust 重构现有功能_开源_Tina_InfoQ精选文章