【服务器知识】nginx性能不够, 我们还有pingora

概述

Pingora是一个用Rust编写的软件库,旨在帮助开发者构建快速、可靠且易于迭代升级的网络服务。以下是对Pingora的详细介绍:

一、框架内容

Pingora 提供在 HTTP/1 和 HTTP/2、 TLS 或仅 TCP / UDP 之上构建服务的库和 API。作为代理,它支持 HTTP/1 和 HTTP/2 端到端、gRPC 和 websocket 代理。(HTTP/3 支持在路线图上)。它还提供可自定义的负载平衡和故障转移策略。在合规性和安全性方面,它同时支持常用的 OpenSSL 和 BoringSSL 库,两者均符合 FIPS 要求并支持后量子加密。

除了提供以上功能,Pingora 还提供过滤器和回调函数,让用户完全自定义服务应该如何处理、转换和转发请求。这些 API 对于 OpenResty 和 NGINX 用户来说应该很熟悉,因为许多 API 直观地映射到 OpenResty 的 “*_by_lua” 回调函数上。

在运行方面,Pingora 提供零停机优雅重启,以便在不丢失任何传入请求的情况下进行自身升级。Syslog、Prometheus、Sentry、OpenTelemetry 和其他必备的可观察性工具也可以轻松与 Pingora 集成。
pingora-req-flow

二、主要特性

  1. 高性能:Pingora充分利用了异步Rust的威力,保证了服务的速度和可靠性。它在Cloudflare的实际运营中经受了考验,每天处理超过4亿次互联网请求,展现了其稳定性和可靠性。
  2. 多协议支持:Pingora支持HTTP 1/2端到端代理,并能处理TLS连接(通过OpenSSL或BoringSSL)。此外,还提供gRPC和WebSocket的代理功能。
  3. 优雅重启机制:Pingora提供了优雅的重启机制,使得服务在升级或维护时能够无缝切换,不影响用户体验。
  4. 定制化负载均衡和故障切换策略:Pingora允许开发者根据需求定制负载均衡和故障切换策略,以满足不同场景下的需求。
  5. 高内存安全性:与C/C++相比,Pingora以更高的内存安全性保障服务运行,降低了内存泄漏和缓冲区溢出的风险。
  6. 高可编程性:Pingora的高可编程性让开发者可以随心所欲地调整和扩展,以满足特定需求。

三、主要组件

Pingora项目下包含了多个独立的crate,如:

  1. pingora:构建网络系统和代理的主要接口。
  2. pingora-core:定义协议、功能和基本特性。
  3. pingora-proxy:HTTP代理逻辑及相关API。
  4. pingora-error:全项目通用的错误类型。
  5. pingora-http:HTTP头定义及API。
  6. pingora-openssl和pingora-boringssl:SSL相关的扩展和API。
  7. pingora-ketama:实现一致性哈希算法Ketama。
  8. pingora-limits:高效的计数算法。
  9. pingora-load-balancing:pingora-proxy的负载均衡算法扩展。
  10. pingora-memory-cache:带有缓存锁的异步内存缓存,防止缓存踩踏问题。
  11. pingora-timeout:更高效的异步定时器系统。

四、应用场景

Pingora主要适用于需要高性能、高可靠性和高可编程性的网络服务场景,如云计算、边缘计算、物联网等。它可以帮助开发者快速构建和迭代网络服务,提高开发效率和服务质量。

五、使用与社区

  1. 使用:要开始使用Pingora,首先需要安装Rust 1.72或更高版本,以及Clang和Perl 5用于特定库的构建。然后,可以从Pingora的官方项目地址(https://gitcode.com/gh_mirrors/pi/pingora)下载并编译源码。Pingora提供了详细的快速启动指南和用户指南,帮助开发者快速上手和深入配置。
  2. 社区:Pingora拥有一个活跃的社区,聚集了众多技术爱好者。开发者可以加入社区,与他人交流分享,共同学习进步。

综上所述,Pingora是一个功能强大、易于使用的网络服务构建工具,它以其高性能、多协议支持、优雅重启机制和高可编程性等特点,赢得了开发者的广泛赞誉。

快速构建一个负载均衡器

如何使用 pingora 和 pingora-proxy 构建一个简单的负载均衡器。

负载均衡器的目标是针对每个传入的 HTTP 请求,以循环方式选择两个后端之一:https://1.1.1.1和https://1.0.0.1 。

构建基本负载均衡器

为我们的负载均衡器创建一个新的 Cargo 项目。我们称之为load_balancer

cargo new load_balancer

包括 Pingora Crate 和基本依赖项
在您的项目cargo.toml文件中将以下内容添加到您的依赖项中

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

创建 pingora 服务器

首先,让我们创建一个 pingora 服务器。pingoraServer是一个可以托管一个或多个服务的进程。pingoraServer负责配置和 CLI 参数解析、守护进程、信号处理以及正常重启或关闭。

Server首选用法是在函数中初始化main()并用来run_forever()生成所有运行时线程并阻止主线程直到服务器准备退出。

use async_trait::async_trait;
use pingora::prelude::*;
use std::sync::Arc;

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

创建负载均衡器代理

接下来让我们创建一个负载均衡器。我们的负载均衡器包含一个静态上游 IP 列表。cratepingora-load-balancing已经为LoadBalancer结构提供了常见的选择算法,例如循环和哈希。所以我们就使用它吧。如果用例需要更复杂或定制的服务器选择逻辑,用户可以简单地在此函数中自己实现它。

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

为了使服务器成为代理,我们需要ProxyHttp为其实现特征。

任何实现该ProxyHttp特征的对象本质上都定义了如何在代理中处理请求。该ProxyHttp特征中唯一必需的方法是upstream_peer()返回应将请求代理到的地址。

在 的主体中upstream_peer(),我们使用 的select()方法LoadBalancer在上游 IP 之间进行循环。在此示例中,我们使用 HTTPS 连接到后端,因此在构造 ) 对象时,我们还需要指定use_tls并设置 SNI Peer。

#[async_trait]
impl ProxyHttp for LB {

    /// For this small example, we don't need context storage
    type CTX = ();
    fn new_ctx(&self) -> () {
        ()
    }

    async fn upstream_peer(&self, _session: &mut Session, _ctx: &mut ()) -> Result<Box<HttpPeer>> {
        let upstream = self.0
            .select(b"", 256) // hash doesn't matter for round robin
            .unwrap();

        println!("upstream peer is: {upstream:?}");

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

为了让 1.1.1.1 后端接受我们的请求,必须存在主机标头。可以通过回调添加此标头,upstream_request_filter()该回调会在与后端建立连接之后、发送请求标头之前修改请求标头。

impl ProxyHttp for LB {
    // ...
    async fn upstream_request_filter(
        &self,
        _session: &mut Session,
        upstream_request: &mut RequestHeader,
        _ctx: &mut Self::CTX,
    ) -> Result<()> {
        upstream_request.insert_header("Host", "one.one.one.one").unwrap();
        Ok(())
    }
}

创建 pingora-proxy 服务

接下来让我们创建一个遵循上述负载均衡器的指示的代理服务。

pingoraService监听一个或多个(TCP 或 Unix 域套接字)端点。建立新连接时,将Service连接移交给其“应用程序”。pingora-proxy就是这样一个应用程序,它将 HTTP 请求代理到上面配置的给定后端。

在下面的例子中,我们创建了一个LB有两个后端的实例1.1.1.1:443和1.0.0.1:443。我们通过调用 将该LB实例放到代理中,然后告诉我们 托管该代理。Servicehttp_proxy_service()ServerService

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

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

    let mut lb = http_proxy_service(&my_server.configuration, LB(Arc::new(upstreams)));
        lb.add_tcp("0.0.0.0:6188");

    my_server.add_service(lb);

    my_server.run_forever();
}

运行它

cargo run

为了测试它,只需使用以下命令向服务器发送一些请求:

curl 127.0.0.1:6188 -svo /dev/null

您也可以通过浏览器访问http://localhost:6188
以下输出显示负载均衡器正在执行其工作以在两个后端之间进行平衡:

upstream peer is: Backend { addr: Inet(1.0.0.1:443), weight: 1 }
upstream peer is: Backend { addr: Inet(1.1.1.1:443), weight: 1 }
upstream peer is: Backend { addr: Inet(1.0.0.1:443), weight: 1 }
upstream peer is: Backend { addr: Inet(1.1.1.1:443), weight: 1 }
upstream peer is: Backend { addr: Inet(1.0.0.1:443), weight: 1 }

到此为止, 已经创建好了一个功能齐全的负载均衡器。不过,这是一个非常 基本的负载均衡器,因此下一节将引导您了解如何使用一些内置的 pingora 工具使其更加强大。

添加功能

Pingora 提供了几个实用的功能,只需几行代码即可启用和配置。这些功能包括简单的对等健康检查,以及无缝更新正在运行的二进制文件且不会造成服务中断的能力。

同行健康检查

为了使我们的负载均衡器更加可靠,我们希望为上游对等点添加一些健康检查。这样,如果某个对等点发生故障,我们可以快速停止将流量路由到该对等点。

首先,让我们看看当其中一个对等点发生故障时,我们的简单负载均衡器会如何表现。为此,我们将更新对等点列表,以包含一个肯定会发生故障的对等点。

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

现在,如果我们再次使用运行负载均衡器cargo run,并使用

curl 127.0.0.1:6188 -svo /dev/null

我们可以看到,每 3 个请求中就有一个会失败502: Bad Gateway。这是因为我们的对等点选择严格遵循RoundRobin我们给出的选择模式,而没有考虑该对等点是否健康。我们可以通过添加基本健康检查服务来解决这个问题。

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

    // Note that upstreams needs to be declared as `mut` now
    let mut upstreams =
        LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443", "127.0.0.1:343"]).unwrap();

    let hc = TcpHealthCheck::new();
    upstreams.set_health_check(hc);
    upstreams.health_check_frequency = Some(std::time::Duration::from_secs(1));

    let background = background_service("health check", upstreams);
    let upstreams = background.task();

    // `upstreams` no longer need to be wrapped in an arc
    let mut lb = http_proxy_service(&my_server.configuration, LB(upstreams));
    lb.add_tcp("0.0.0.0:6188");

    my_server.add_service(background);

    my_server.add_service(lb);
    my_server.run_forever();
}

现在,如果我们再次运行并测试我们的负载均衡器,我们会看到所有请求都成功,并且从未使用过损坏的对等点。根据我们使用的配置,如果该对等点再次恢复正常,它将在 1 秒内再次重新纳入循环中。

命令行选项

pingoraServer类型提供了许多内置功能,我们可以通过单行更改来利用这些功能。

fn main() {
    let mut my_server = Server::new(Some(Opt::parse_args())).unwrap();
    ...
}

通过此更改,传递给我们的负载均衡器的命令行参数将被 Pingora 使用。我们可以通过运行以下命令进行测试:

cargo run -- -h

我们应该看到一个帮助菜单,其中列出了现在可用的参数。我们将在下一节中利用这些参数,免费使用我们的负载均衡器做更多事情

在后台运行

传递参数-d或–daemon将告诉程序在后台运行。

cargo run -- -d

要停止此服务,您可以SIGTERM向其发送信号以进行正常关闭,此时该服务将停止接受新请求,但会尝试在退出之前完成所有正在进行的请求。

pkill -SIGTERM load_balancer

(SIGTERM是 的默认信号pkill。)

配置

Pingora 配置文件有助于定义如何运行服务。下面是一个示例配置文件,它定义了服务可以有多少个线程、pid 文件的位置、错误日志文件和升级协调套接字(我们将在后面解释)。复制下面的内容并将其放入项目目录conf.yaml中名为的文件中load_balancer。

version: 1
threads: 2
pid_file: /tmp/load_balancer.pid
error_log: /tmp/load_balancer_err.log
upgrade_sock: /tmp/load_balancer.sock

要使用该配置文件:

RUST_LOG=INFO cargo run -- -c conf.yaml -d

RUST_LOG=INFO是为了让服务真正填充错误日志。

现在您可以找到服务的 pid。

 cat /tmp/load_balancer.pid

优雅地升级服务(仅限 Linux)

假设我们更改了负载均衡器的代码并重新编译了二进制文件。现在我们想将后台运行的服务升级到这个新版本。

如果我们只是停止旧服务,然后启动新服务,那么中间到达的一些请求可能会丢失。幸运的是,Pingora 提供了一种优雅的方式来升级服务。

完成此操作后,首先SIGQUIT向正在运行的服务器发送信号,然后使用参数-u\启动新服务器–upgrade。

pkill -SIGQUIT load_balancer &&\
RUST_LOG=INFO cargo run -- -c conf.yaml -d -u

在此过程中,旧服务器将等待并将其监听套接字移交给新服务器。然后旧服务器将运行,直到其所有正在进行的请求完成。

从客户端的角度来看,服务始终在运行,因为监听套接字从未关闭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问道飞鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值