一天为用户节省434年握手时间!Rust编写的Pingora凭什么力压Nginx?

2024-07-06 16:32:20 浏览数 (2)

一、Pingora简介

作为一个对 Rust 语言和新兴技术充满兴趣的开发者,我最近了解到一个令人振奋的项目——Pingora

这是 Cloudflare 使用 Rust 构建的全新 HTTP 代理,意在替代Nginx。Pingora 每天处理超过1万亿个请求,不仅大幅提升了性能,还为客户带来了许多新功能,同时只需以前基础架构的三分之一CPU和内存资源。本文将为大家介绍 Pingora 的设计理念以及它所带来的巨大优势。

二、为什么Cloudflare需要一个新的代理?

随着 Cloudflare 的快速扩展,现有的 Nginx 代理在处理能力和功能方面出现了局限性。虽然 Nginx 多年来运作良好,但在Cloudflare 的大规模应用中,仍存在一些难以解决的问题:

  1. 架构限制损害性能Nginxworker 架构导致 CPU 负载不平衡,进而影响整体性能。
  2. 糟糕的连接重用Nginx 的连接池与单个 worker 绑定,导致连接重用率低,影响请求首字节时间(TTFB)。
  3. 功能扩展困难Nginx 在添加复杂功能时存在局限性,且其用C语言编写存在内存安全问题。

三、构建自有代理的决策过程

面对这些挑战,Cloudflare 评估了三种解决方案:

  1. 继续投资 Nginx,并进行定制化改造。
  2. 迁移到其他第三方代理代码库,如 Envoy
  3. 从头开始构建一个内部平台和框架。

最终,Cloudflare 决定从头开始构建一个适合其需求的新代理系统——PingoraPingora 的设计不仅解决了 Nginx 的架构缺陷,还大大提升了性能和效率。

四、Pingora项目的设计决策

为了打造一个高效、安全且易于扩展的代理系统,Cloudflare 做出了一些关键设计决策:

  1. 选择Rust语言Rust 提供内存安全特性,同时具备 C 语言的高性能,是构建高效代理系统的理想选择。
  2. 自建HTTP库:为了最大化处理HTTP流量的灵活性, Cloudflare 选择构建自己的 HTTP 库,而不是使用现成的第三方库。
  3. 多线程架构:采用多线程而非多进程架构,以便更好地共享资源,尤其是连接池。
  4. 开发者友好的接口:设计类似 Nginx/OpenResty 的基于“请求生命周期”事件的可编程接口,使开发者可以轻松上手并快速开发新功能。

五、Pingora在生产环境中的表现

Pingora 上线以来,它处理了几乎所有需要与源服务器交互的 HTTP 请求,性能数据显著提升:

  1. TTFB显著降低Pingora 上流量的TTFB中位数减少了5毫秒,第95百分位数减少了80毫秒。
  2. 连接重用率大幅提升Pingora 的连接重用率提高,使每秒新连接数减少了三分之二。
  3. 资源消耗大幅降低:在相同流量负载下,Pingora的 CPU 和内存消耗减少了约70%和67%。

Cloudflare 上线 Pingora 以来,它处理了几乎所有需要与源服务器交互的 HTTP 请求,性能数据显著提升。

让我们看看 Cloudflare 使用 Pingora 如何加快客户的流量。

Pingora 的总体流量数据显示,TTFB 中位数减少了5毫秒,第95个百分位数减少了80毫秒。主要是得益于新架构可以跨所有线程共享连接,从而提高了连接重用率,减少了 TCPTLS 握手时间。

与旧服务相比,Pingora 每秒的新连接数减少到原来的三分之一。对于某个主要客户,连接重用率从87.1%提升到了99.92%,这将新连接减少了160倍。更直观地说,通过切换到 PingoraCloudflare 每天为客户和用户节省了434年的握手时间。

六、更高效、更安全、更强大

Pingora不仅性能出众,还在功能扩展和安全性方面表现优异:

  1. 功能扩展更灵活:例如,Cloudflare 能够轻松添加HTTP/2 上游支持,并向客户提供 gRPC 服务。
  2. 更高效的资源使用Rust 代码和多线程模型使得资源使用更高效,减少了连接创建和数据共享的成本。
  3. 内存安全Rust 的内存安全语义大大减少了内存错误的可能性,确保服务稳定运行。

七、单负载均衡器的示例代码

为了更好地展示 Pingora 的强大功能,下面提供一个使用Pingora实现简单负载均衡器的示例代码:

代码语言:rust复制
use async_trait::async_trait;
use pingora::prelude::*;
use std::sync::Arc;

fn main() {
    // 创建一个服务器实例,参数为None表示使用默认配置
    let mut my_server = Server::new(None).unwrap();
    // 初始化服务器
    my_server.bootstrap();
    // 创建一个负载均衡器,包含两个上游服务器
    let upstreams = LoadBalancer::try_from_iter(["192.168.9.34:80", "10.0.0.9:80"]).unwrap();
    // 创建一个HTTP代理服务,并传入服务器配置和负载均衡器
    let mut lb = http_proxy_service(&my_server.configuration, LB(Arc::new(upstreams)));
    // 添加一个TCP监听地址
    lb.add_tcp("0.0.0.0:6188");
    // 将服务添加到服务器中
    my_server.add_service(lb);
    // 运行服务器,进入事件循环
    my_server.run_forever();
}

// 定义一个包含负载均衡器的结构体LB,用于包装Arc指针以实现多线程共享。
pub struct LB(Arc<LoadBalancer<RoundRobin>>);

// 使用#[async_trait]宏,异步实现ProxyHttp trait。
#[async_trait]
impl ProxyHttp for LB {
    /// 定义上下文类型,这里使用空元组。对于这个小例子,我们不需要上下文存储
    type CTX = ();
    // 创建新的上下文实例,这里返回空元组
    fn new_ctx(&self) -> () {
        ()
    }
    // 选择上游服务器并创建HTTP对等体
    async fn upstream_peer(&self, _session: &mut Session, _ctx: &mut ()) -> Result<Box<HttpPeer>> {
        // 使用轮询算法选择上游服务器
        let upstream = self
            .0
            .select(b"", 256) // 对于轮询,哈希不重要
            .unwrap();
        println!("上游对等体是:{upstream:?}");
        // 创建一个新的HTTP对等体,设置SNI为example.com
        let peer: Box<HttpPeer> = Box::new(HttpPeer::new(upstream, false, "example.com".to_string()));
        Ok(peer)
    }

    // 在上游请求发送前,插入Host头部
    async fn upstream_request_filter(
        &self,
        _session: &mut Session,
        upstream_request: &mut RequestHeader,
        _ctx: &mut Self::CTX,
    ) -> Result<()> {
        // 将Host头部设置为example.com
        upstream_request
            .insert_header("Host", "example.com")
            .unwrap();
        Ok(())
    }
}

八、结语

Pingora的发布标志着代理技术领域的一次重大飞跃。我相信pingora也将成为rust一个有意义项目。如果你对互联网新兴技术、rust语言 等感兴趣,欢迎关注我的后续文章和技术分享!

本文示例代码:https://github.com/phyuany/simple-pingora-reverse-proxy/

本文原创首发自公众号:极客开发者,禁止转载!

0 人点赞