Python
悠悠
2026年4月14日

Pingora实战:从零搭建比Nginx还快的反向代理,手把手教你玩转Cloudflare开源神器

前两天看到Cloudflare开源了Pingora这个项目,说是用Rust写的反向代理框架,性能比Nginx还要快,我就坐不住了。咱们做运维的,对新东西总是要保持好奇心,何况这玩意儿号称每秒能处理百万级请求,这不得赶紧试试?

说实话,刚开始看到这个消息的时候我还挺怀疑的。Nginx可是咱们运维圈的老伙计了,用了这么多年,稳定可靠,你说一个新出来的框架能比它快?不过转念一想,Cloudflare可是全球最大的CDN服务商之一,他们每天处理的流量那都是天文数字,既然敢开源出来,肯定是有两把刷子的。

抱着试试看的心态,我在测试环境折腾了几天,把整个安装配置流程都跑了一遍。不得不说,这东西确实有点东西,今天就把我的实践经验分享给大家,希望能帮到想尝鲜的朋友们。

Pingora是个啥?

咱们先聊聊Pingora到底是个什么东西。简单来说,它是Cloudflare用Rust语言开发的一个高性能反向代理框架[1]。Cloudflare大家应该都不陌生吧,全球最大的CDN服务商之一,每天处理的HTTP请求那是数以万亿计的。

以前Cloudflare也是用Nginx,但是随着业务规模越来越大,Nginx的一些局限性就开始显现出来了。比如说,Nginx的配置文件虽然灵活,但是要做一些复杂的逻辑处理就比较麻烦;还有就是性能瓶颈,在某些极端场景下已经满足不了需求了。

于是Cloudflare就开始自己造轮子,用Rust写了这个Pingora。根据官方的说法,Pingora在性能、安全性、可扩展性方面都有很大的提升[2]。特别是在高并发场景下,表现相当亮眼。

我看过一些性能测试的对比数据,Pingora在某些场景下确实比Nginx快不少[3]。当然,这个"快"也要看具体场景,不是说什么情况下都快的。咱们做技术的,不能盲目迷信,得自己动手测试才知道。

环境准备

好了,说了这么多,咱们开始动手吧。不过在安装Pingora之前,得先把环境准备好。

Pingora是用Rust写的,所以首先你得有Rust的环境。要是你系统里还没有Rust,那就得先装一下。我这边测试用的是debain13,其他Linux发行版应该也差不多。

安装Rust其实挺简单的,官方提供了一个脚本:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

运行这个脚本,一路回车就好。装完之后记得重启一下终端,或者手动source一下环境变量:

source $HOME/.cargo/env

然后验证一下安装是否成功:

rustc --version
cargo --version

看到版本号输出就说明OK了。

对了,还得提醒一下,Pingora对Rust的版本有要求,最好是1.70以上的版本。要是版本太老,可能编译的时候会报错。我一开始就是用的系统自带的旧版本,结果折腾半天编译不过,后来升级了Rust版本才解决。

除了Rust,你还得有一些基础的编译工具,比如gcc、make之类的。debain13的话可以这样装:

apt-get install -y gcc make openssl-devel pkg-config

这些工具在编译Pingora的时候会用到,少了哪个都会报错,到时候再装就麻烦了。

创建项目

环境准备好了,接下来就是创建项目。Pingora不是一个独立的可执行程序,而是一个框架,你需要基于它来开发自己的代理服务。这个跟Nginx不太一样,Nginx是直接装好就能用的,Pingora更像是一个开发框架。

用Cargo创建一个新项目:

cargo new my_pingora_proxy
cd my_pingora_proxy

创建后的目录结构:

然后编辑Cargo.toml文件,添加Pingora的依赖:

[package]
name = "my_pingora_proxy"
version = "0.1.0"
edition = "2024"

[dependencies]
pingora = { version = "0.1", features = ["proxy"] }
async-trait = "0.1"

这里要注意一下,Pingora的版本号可能会更新,具体可以去crates.io上看最新的版本。我写这篇文章的时候是0.1版本,现在可能已经更新了。

添加依赖的时候,那个features = ["proxy"]不能少,不然编译的时候会提示找不到某些模块。我第一次搞的时候就忘了这个,折腾了好久才发现。

写第一个代理程序

依赖配好了,接下来就是写代码了。说实话,第一次看到Pingora的代码的时候,我还挺懵的。跟Nginx那种配置文件的方式完全不一样,Pingora是用代码来定义代理逻辑的。

不过仔细看看,其实也不难理解。咱们先写一个最简单的反向代理,把请求转发到后端服务器。

打开src/main.rs,把原来的内容删掉,换成下面的代码:

use pingora::prelude::*;
use std::sync::Arc;
use async_trait::async_trait; // 确保导入了这个 trait

pub struct MyProxy;

#[async_trait]
impl ProxyHttp for MyProxy {
    // 1. 必须定义上下文类型,通常用空元组
    type CTX = ();
    
    // 2. 必须实现初始化上下文的方法
    fn new_ctx(&self) -> Self::CTX {}

    async fn upstream_peer(
        &self,
        _session: &mut Session,
        _ctx: &mut Self::CTX, // 注意这里类型要与上面一致
    ) -> Result<Box<HttpPeer>> {
        // 3. 这里的地址只能是 "IP:端口",不能有 http://
        let peer = Box::new(HttpPeer::new(
            "127.0.0.1:8080", 
            false, 
            "localhost".to_string(), // 通常建议填入域名
        ));
        Ok(peer)
    }
}

fn main() {
    // 初始化服务器
    let mut server = Server::new(None).unwrap();
    server.bootstrap();
  
    // 创建代理服务
    let mut proxy = http_proxy_service(
        &server.configuration,
        MyProxy {},
    );
  
    // 监听端口
    proxy.add_tcp("0.0.0.0:6199");
  
    server.add_service(proxy);
    println!("Pingora Proxy 正在运行在端口 6199...");
    server.run_forever();
}

这段代码看起来有点长,但其实逻辑很简单。我来逐行解释一下。

开头那几行是引入依赖,这个不用多说。然后定义了一个MyProxy结构体,这个就是咱们的代理主体。

接下来是实现ProxyHttp trait,这个是Pingora的核心接口。里面的upstream_peer方法是用来指定后端服务器的,这里我把请求转发到了本地的8080端口。你可以根据实际情况改成你的后端地址。

main函数里就是创建服务器、配置监听端口这些操作。我这里监听的是6199端口,你可以改成其他端口,只要不冲突就行。

编译运行

代码写好了,接下来就是编译运行了。直接用cargo:

cargo build --release

出现报错,处理下,简单来说:你的 Linux 系统中没有安装 cmake,或者 cmake 不在系统的环境变量路径中。由于 libz-ng-sys 这个库(它是 Pingora 的依赖之一)需要通过 cmake 来编译 C 代码,所以报错了。(感谢ai)

第一次编译可能会比较慢,因为要下载和编译很多依赖包。我这边测试的时候花了大概五六分钟,具体时间要看你的网络情况和机器性能。

编译完成后,会在target/release目录下生成可执行文件。运行它:

./target/release/my_pingora_proxy

如果没什么报错,应该就能看到程序跑起来了。不过这个时候你可能会发现,程序启动之后没有任何输出,也不退出。这是正常的,它在等待连接呢。

测试一下

光跑起来还不行,咱们得测试一下能不能正常工作。先准备一个后端服务。我这边就用Python简单起一个HTTP服务器:

cd /tmp
echo "Hello from backend" > index.html
python3 -m http.server 8080

这样就在8080端口起了一个简单的HTTP服务器。

然后新开一个终端,用curl测试一下我们的代理:

curl http://127.0.0.1:6199/

如果一切正常,你应该能看到"Hello from backend"这个输出。这就说明我们的代理服务器工作正常,成功把请求转发到了后端。

我当时第一次测试成功的时候还挺激动的,虽然只是个简单的代理,但毕竟是自己用Rust写的第一个能用的程序。哈哈,成就感满满。

负载均衡配置

简单的代理跑通了,咱们来点进阶的。实际生产环境中,后端通常不会只有一台服务器,这时候就需要负载均衡了[6]。

Pingora做负载均衡也挺方便的,咱们稍微改一下代码:

use async_trait::async_trait;
use pingora::lb::selection::round_robin::RoundRobin;
use pingora::lb::LoadBalancer;
use pingora::prelude::*;
use std::sync::Arc;

// 定义我们的代理结构体,现在它持有一个负载均衡器
pub struct MyProxy {
    pub lb: Arc<LoadBalancer<RoundRobin>>,
}

#[async_trait]
impl ProxyHttp for MyProxy {
    type CTX = ();
    fn new_ctx(&self) -> Self::CTX {}

    async fn upstream_peer(
        &self,
        _session: &mut Session,
        _ctx: &mut Self::CTX,
    ) -> Result<Box<HttpPeer>> {
        // --- 负载均衡核心逻辑 ---
        // 从负载均衡器中选择一个后端节点
        let upstream = self
            .lb
            .select(b"", 256) // 这里的 b"" 是用于一致性哈希的 key,轮询模式下不生效
            .ok_or_else(|| Error::explain(ErrorType::InternalError, "无可用后端节点"))?;

        println!("正在转发请求至: {:?}", upstream.addr);

        // 创建 HttpPeer,注意 upstream.addr 已经是 SocketAddr 类型
        let peer = Box::new(HttpPeer::new(upstream, false, upstream.host().to_string()));
        Ok(peer)
    }
}

fn main() {
    let mut server = Server::new(None).unwrap();
    server.bootstrap();

    // 1. 配置后端列表
    // 假设你本地运行了两个服务:8080 和 8081
    let mut upstreams = vec![
        "127.0.0.1:8080",
        "127.0.0.1:8081",
    ];
    
    // 2. 创建负载均衡器 (使用轮询算法)
    let lb = LoadBalancer::from_backends(upstreams.as_slice());
    let arc_lb = Arc::new(lb);

    // 3. 将负载均衡器注入代理服务
    let mut proxy = http_proxy_service(
        &server.configuration,
        MyProxy { lb: arc_lb },
    );

    proxy.add_tcp("0.0.0.0:6199");

    server.add_service(proxy);
    println!("Pingora 负载均衡代理已启动: 0.0.0.0:6199 -> [8080, 8081]");
    server.run_forever();
}

这段代码跟之前的区别在于,我们引入了RoundRobin,这是一个轮询的负载均衡算法。然后在upstream_peer方法里,通过lb.select()来选择一个后端服务器。

backends数组里配置了三个后端服务器地址,你可以根据实际情况修改。Pingora会按照轮询的方式,依次把请求分发给这三台服务器。

我测试的时候起了三个Python HTTP服务器,分别监听8080、8081、8082端口,然后用curl连续请求几次,确实能看到请求被分发到了不同的后端。效果还不错。

健康检查

光有负载均衡还不够,生产环境还得有健康检查。要是某台后端服务器挂了,负载均衡器得知道,不能再把请求往那台服务器上发了。

Pingora支持健康检查,不过需要额外配置一下:

use pingora::prelude::*;
use pingora::load_balancing::{RoundRobin, health_check};
use std::sync::Arc;
use std::time::Duration;

pub struct MyProxy {
    lb: Arc<RoundRobin>,
}

#[async_trait::async_trait]
impl ProxyHttp for MyProxy {
    async fn upstream_peer(
        &self,
        _session: &mut Session,
        _ctx: &mut Box<dyn Send + Sync>,
    ) -> Result<Box<HttpPeer>> {
        let peer = self.lb.select(b"").unwrap();
        Ok(Box::new(peer))
    }
}

fn main() {
    let mut server = Server::new(None).unwrap();
    server.bootstrap();
  
    let backends = vec![
        "127.0.0.1:8080".to_string(),
        "127.0.0.1:8081".to_string(),
        "127.0.0.1:8082".to_string(),
    ];
  
    let mut lb = RoundRobin::try_from(backends).unwrap();
  
    // 配置健康检查
    let health_check = health_check::TcpHealthCheck::new();
    lb.set_health_check(health_check);
    lb.health_check_interval = Duration::from_secs(5);
  
    let lb = Arc::new(lb);
  
    let mut proxy = http_proxy_service(
        &server.configuration,
        MyProxy { lb: lb.clone() },
    );
  
    proxy.add_tcp("0.0.0.0:6199");
  
    server.add_service(proxy);
  
    // 启动健康检查任务
    tokio::spawn(async move {
        lb.run_health_check().await;
    });
  
    server.run_forever();
}

这段代码加了一个TCP健康检查,每5秒检查一次后端服务器的连通性。如果某台服务器连不上了,就会被标记为不健康,负载均衡器就不会再把请求发给它了。

不过说实话,Pingora的健康检查功能目前还比较基础,不像Nginx那样有丰富的配置选项。毕竟是新项目,功能还在不断完善中。相信随着版本的更新,这块会越来越完善的。

性能测试

说了这么多,大家肯定关心性能到底怎么样。我这边简单做了个测试,用的wrk这个压测工具。

测试环境:

  • 服务器:4核8G内存的云主机
  • 后端:简单的Nginx静态页面
  • 测试工具:wrk

先测试Nginx:

wrk -t4 -c100 -d30s http://127.0.0.1:80/

然后测试Pingora代理到同样的后端:

wrk -t4 -c100 -d30s http://127.0.0.1:6199/

测试结果我就不贴具体数字了,因为测试条件有限,数据可能不太准确。但是从趋势来看,Pingora的性能确实不错,跟Nginx基本持平,某些场景下还稍微快一点。

当然,这个测试比较简单,不能完全说明问题。实际生产环境的性能表现,还需要更全面的测试。不过从这个简单的测试来看,Pingora的性能确实有潜力。

实际应用场景

折腾了几天,我对Pingora有了更深的了解。那么问题来了,Pingora适合用在哪些场景呢?

我觉得有这么几种情况可以考虑:

  1. 需要高度定制化的代理逻辑。Nginx虽然功能强大,但是有些复杂的逻辑用配置文件很难实现,这时候用Pingora写代码就灵活多了。
  2. 对性能要求极高的场景。虽然Nginx已经很快了,但是在某些极端场景下,Pingora可能还能再榨出一些性能来。
  3. 想学习Rust网络编程的。Pingora的代码质量很高,是一个很好的学习案例。
  4. 需要更好的安全性。Rust的内存安全特性,让Pingora在安全性方面有天然的优势。

不过,Pingora目前还比较新,生态不如Nginx成熟,文档也不够完善。如果是关键业务,还是建议再观望一下,或者先在非核心业务上试试水。

踩过的坑

最后说说我在折腾过程中踩的一些坑,希望能帮大家少走弯路。

第一个坑就是Rust版本问题。我一开始用的系统自带的Rust版本比较老,编译的时候各种报错。后来升级到最新版本才解决。所以大家一定要注意Rust版本,最好用最新的稳定版。

第二个坑是依赖问题。Pingora依赖的一些库需要系统安装openssl-devel,不然编译会失败。CentOS用户记得yum install openssl-devel。

第三个坑是文档不够详细。Pingora毕竟是个新项目,文档还不完善,有些功能得去看源码才能搞清楚怎么用。建议多看看GitHub上的examples目录,里面有一些示例代码可以参考[7]。

第四个坑是性能调优。Pingora默认的配置可能不是最优的,需要根据实际情况调整。比如worker数量、连接池大小这些参数,都需要根据业务场景来优化。

总结

折腾了几天,总算把Pingora的基本功能都跑通了。整体感觉,Pingora确实是一个有潜力的项目,性能不错,代码质量也很高。不过目前还不够成熟,文档和生态都有待完善。

对于想尝鲜的朋友,我建议可以先在测试环境玩玩,熟悉一下Pingora的架构和用法。但是要上生产环境的话,还是得谨慎,毕竟这是个新项目,稳定性还需要时间验证。

我相信随着Pingora的不断完善,它会在反向代理领域占据一席之地。毕竟Cloudflare的技术实力摆在那里,而且他们自己就在大规模使用Pingora,这本身就是最好的证明。

好了,今天的分享就到这里。如果你对Pingora感兴趣,或者在使用过程中有什么问题,欢迎留言交流。咱们一起学习,一起进步!

如果觉得这篇文章对你有帮助,记得点赞、在看、转发三连哦!你的支持是我继续分享的动力!


公众号:运维躬行录

个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码