51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

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

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

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

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

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

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

构建自有代理的决策过程

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

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

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

Pingora项目的设计决策

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

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

Pingora在生产环境中的表现

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

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

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

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

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

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

更高效、更安全、更强大

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

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

实现简单负载均衡器的示例代码

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

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语言 等感兴趣,欢迎关注我的后续文章和技术分享!

赞(4)
未经允许不得转载:工具盒子 » 一天为用户节省434年握手时间!Rust编写的Pingora凭什么力压Nginx?