Rust Pingora框架实践:构建负载均衡器

442次阅读  |  发布于7月以前

Pingora框架概述

Pingora是一个Rust框架,用于构建快速、可靠和可编程的网络系统。Pingora是经过实战测试的,它在过去几年的时间里每秒服务超过4000万次互联网请求。

Pingora的功能特点

1,异步I/O:利用Rust的异步功能,Pingora可以进行非阻塞I/O操作,从而获得更好的性能和可伸缩性。

2,HTTP 1/2端到端代理

3,支持TLS,包括OpenSSL或BoringSSL

4,gRPC和websocket代理

5,优雅的重载

6,可定制的负载均衡和故障转移策略

7,支持各种可观察性工具

使用Pingora的原因

Pingora是用Rust构建的,这意味着它可以充分利用该语言的特性和库。它与其他Rust工具和crate无缝集成,提供一致且强大的开发体验。

构建负载均衡器

让我们通过构建一个简单的负载均衡器来探索Pingora的可编程API。这个负载平衡器的目标是,对于每个传入的HTTP请求,以轮询方式选择两个后端之一:https://1.1.1.1和https://1.0.0.1。

运行以下命令创建一个新的Rust项目:

cargo new load_balancer

在Cargo.toml文件中添加Pingora依赖项:

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

创建pingora服务器

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

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

在src/main.rs文件中写入以下代码:

use pingora::server::Server;

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

这段代码可以编译并运行,但它没有做任何事情。

创建负载均衡器代理

接下来,让我们创建一个负载均衡器。我们的负载均衡器持有一个静态的上游ip列表。pingora-load-balancing crate已经为LoadBalancer结构提供了常用的选择算法,如轮询和哈希。

为了使服务器成为代理,我们需要为它实现ProxyHttp特性。任何实现ProxyHttp特性的对象本质上都定义了在代理中如何处理请求。ProxyHttp特性中唯一需要实现的方法是upstream_peer(),它返回请求应该被代理到的地址。

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

use std::sync::Arc;

use async_trait::async_trait;
use pingora::{
    lb::{selection::RoundRobin, LoadBalancer},
    proxy::{ProxyHttp, Session},
    server::Server,
    upstreams::peer::HttpPeer,
    Result,
};

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

#[async_trait]
impl ProxyHttp for LB {
    /// 对于这个小示例,我们不需要上下文存储
    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)
            .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后端接受我们的请求,主机Header必须存在。添加此报头可以通过upstream_request_filter()回调来完成,该回调在与后端建立连接之后和发送请求报头之前修改请求报头。

#[async_trait]
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代理服务

接下来,让我们按照上面的负载平衡器创建一个代理服务。pingora服务监听一个或多个(TCP或Unix域套接字)端点。当新连接建立时,服务将连接移交给pingora-proxy,它将HTTP请求代理到配置好给定后端。

在我们的示例中,我们创建了一个具有两个后端1.1.1.1:443和1.0.0.1:443的LB实例。我们通过http_proxy_service()调用将LB实例放到代理服务中,然后告诉服务器托管该代理服务。

fn main() {
    let mut server = Server::new(None).unwrap();
    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(&server.configuration, LB(Arc::new(upstreams)));
    lb.add_tcp("0.0.0.0:6188");

    server.add_service(lb);

    server.run_forever();
}

执行cargo run,运行我们的负载均衡器。

要测试它,只需使用以下命令多次向服务器发送请求:

curl 127.0.0.1:6188 -svo /dev/null

负载均衡器日志如下:

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 }

总结

Pingora代理框架目前在请求的不同阶段提供了更多的过滤器和回调,以允许用户修改、拒绝、路由和记录请求(响应)。

在后台,Pingora代理框架负责连接池、TLS握手、读取、写入、解析请求和任何其他常见代理任务,以便用户可以专注于重要的业务逻辑。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8