据 Cloudflare 官博介绍,他们基于 Rust 构建的异步 HTTP 代理 Pingora 正式开源,采用 Apache License 2.0 协议。Pingora 是一个 Rust 异步多线程框架,用于构建可编程网络服务。Pingora 长期以来一直在 Cloudflare 内部使用,能够维持大量流量,而现在 Pingora 正在开源,以帮助在 Cloudflare 之外构建基础设施。
在这里插入图片描述

早在 2022 年,Cloudflare 就宣布放弃 Nginx 转向内部编写的 Pingora。目前,Pingora 提高性能的同时,每天处理超过 1 万亿条互联网请求,并为 Cloudflare 客户带来了许多新功能,同时只需要以前代理基础架构的三分之一的 CPU 和内存资源。

Pingora 不仅为代理提供了构建模块,还为客户端和服务器提供了构建模块。除了这些组件,还提供了一些实用库,实现了常见逻辑,如事件计数、错误处理和缓存。

Pingora 都提供了哪些功能?

Pingora 提供了构建基于 HTTP/1 和 HTTP/2、TLS 或仅 TCP/UDP 的服务库和 API。作为代理,它支持 HTTP/1 和 HTTP/2 端到端、gRPC 和 WebSocket 代理(HTTP/3支持正在路线图中)。它还具有可定制的负载均衡和故障转移策略。为了符合合规性和安全性,它支持常用的 OpenSSL 和 BoringSSL 库,这些库具备 FIPS 合规性和后量子加密。

除了提供这些功能,Pingora 还提供了过滤器和回调函数,允许用户完全自定义服务的处理、转换和转发请求的方式。对于 OpenResty 和 NGINX 用户来说,这些 API 将特别熟悉,因为它们在很多方面与 OpenResty 的“*_by_lua”回调直观地对应。

在运营方面,Pingora 提供了零停机优雅重启,以便在升级自身时不会丢失任何一个传入请求。Syslog、Prometheus、Sentry、OpenTelemetry和其他必备的可观测性工具也可以轻松地与Pingora集成。

哪些人适合使用 Pingora ?

如果满足以下条件,就应该考虑使用 Pingora:

把安全作为首要任务:对于使用 C/C++ 编写的服务,Pingora 是一种更安全的内存替代方案。尽管有人对编程语言的内存安全性存在争议,但根据 Cloudflare 的实际经验,他们发现自己很少犯下导致内存安全问题的编码错误。此外,随着他们在处理这些问题上花费的时间减少,在实现新功能方面更加高效。

你的服务对性能敏感:Pingora 快速高效。由于 Pingora 的多线程架构,他们节省了大量的 CPU 和内存资源。对于对系统的成本和/或速度敏感的工作负载来说,这种时间和资源的节省可能非常引人注目。

服务需要广泛的定制:Pingora 代理框架提供的 API 非常可编程。对于希望构建定制化和高级网关或负载均衡器的用户,Pingora 提供了强大而简单的实现方式。

构建一个简单的负载均衡器

让我们通过构建一个简单的负载均衡器来探索 Pingora 的可编程API。该负载均衡器将以轮询的方式在 https://1.1.1.1/ 和 https://1.0.0.1/ 之间选择上游。

首先,让我们创建一个空的 HTTP 代理。

pub struct LB();

#[async_trait]
impl ProxyHttp for LB {
    async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
        todo!()
    }
}

任何实现了 ProxyHttp trait 的对象(类似于 C++ 或 Java 中的接口概念)都可以作为 HTTP 代理。在这里,唯一需要实现的方法是 upstream_peer(),它将在每个请求中被调用。这个函数应该返回一个包含要连接的上游服务器的原始 IP 和如何进行连接的 HttpPeer 对象。

接下来,让实现轮询选择。Pingora 框架已经提供了 LoadBalancer 加载平衡器,其中包含了常见的选择算法,比如轮询和哈希算法,所以大家可以直接使用。如果使用情况需要更复杂或自定义的服务器选择逻辑,用户可以在这个函数中自行实现。

pub struct LB(Arc<LoadBalancer<RoundRobin>>);

#[async_trait]
impl ProxyHttp for LB {
    async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
        let upstream = self.0
            .select(b"", 256) // hash doesn't matter for round robin
            .unwrap();

        // Set SNI to one.one.one.one
        let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
        Ok(peer)
    }
}

由于我们要连接的是 HTTPS 服务器,所以还需要设置 SNI(服务器名称指示)。如果需要的话,还可以在 HttpPeer 对象中设置证书、超时和其他连接选项。

最后,让我们来实际运行这项服务。在这个例子中,我们硬编码了源服务器的 IP 地址。在实际的工作负载中,可以在调用 upstream_peer() 时或后台动态地发现源服务器的IP地址。创建服务后,只需告诉 LB 服务监听 127.0.0.1:6188 即可。最终,我们创建了一个 Pingora 服务器,它将作为运行负载均衡服务的进程。

fn main() {
    let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();

    let mut lb = pingora_proxy::http_proxy_service(&my_server.configuration, LB(upstreams));
    lb.add_tcp("127.0.0.1:6188");

    let mut my_server = Server::new(None).unwrap();
    my_server.add_service(lb);
    my_server.run_forever();
}

让我们试一试:

curl 127.0.0.1:6188 -svo /dev/null
> GET / HTTP/1.1
> Host: 127.0.0.1:6188
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 403 Forbidden

我们可以看到代理正在工作,但是源服务器以 403 的错误拒绝了请求。这是因为我们的服务只是简单地代理了由 curl 设置的 Host 头部字段,即 127.0.0.1:6188,这让源服务器感到非常困惑。那么,我们该如何纠正代理的行为呢?我们可以通过添加另一个名为 upstream_request_filter 的过滤器来解决这个问题。该过滤器会在与源服务器建立连接后、发送任何 HTTP 请求之前运行。我们可以在这个过滤器中添加、删除或修改 HTTP 请求头部字段,从而改正代理的行为。

async fn upstream_request_filter(, upstream_request: &mut RequestHeader,) -> Result<()> {
    upstream_request.insert_header("Host", "one.one.one.one")
}

再试一次

curl 127.0.0.1:6188 -svo /dev/null
< HTTP/1.1 200 OK

可以在这里查看完整示例:https://github.com/cloudflare/pingora/blob/main/pingora-proxy/examples/load_balancer.rs

下面是一幅简单的请求处理示例图,展示了这个请求是如何通过我们在本例中使用的回调函数和过滤器流动的。Pingora 代理框架目前在请求的不同阶段提供了更多的过滤器和回调函数,以允许用户修改、拒绝、路由和/或记录请求(和响应)。
在这里插入图片描述
Pingora 代理框架负责连接池管理、TLS 握手、读取、写入、解析请求以及其他常见的代理任务,以便用户可以专注于对他们来说重要的逻辑。

有关 Pingora 的更多介绍,大家可以访问 Cloudflare 官博和开源主页

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐