Pingora快速入门:负载均衡器

快速入门:负载均衡器

image.png

介绍

本快速入门展示了如何使用 pingora 和 pingora-proxy 构建一个简单的负载均衡器。

负载均衡器的目标是对于每个传入的 HTTP 请求,以循环方式选择两个后端之一: https://1.1.1.1https://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 服务器ServerServer是一个可以托管一个或多个服务的进程。Server负责配置和 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 的静态列表。pingora-load-balancingcrate已经提供了结构体LoadBalancer,并提供了常见的选择算法,例如循环法和散列法。所以我们就用它吧。如果用例更复杂或需要定制服务器选择逻辑,用户可以简单地在此方法中自行实现。

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

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

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

在upstream_peer()方法中,我们使用LoadBalancerselect()方法来轮询上游IP。在这个例子中,我们使用HTTPS连接到后端,所以我们还需要指定 use_tls,并在构建Peer对象时设置SNI。

#[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 服务

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

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

在下面的示例中,我们创建一个具有1.1.1.1:4431.0.0.1:443两个后端的LB实例。我们通过 调用http_proxy_service()方法将该LB实例放入代理Service,然后告诉我们 托管该代理 Service

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 个请求中就有 1 个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 秒内再次重新包含在循环中。

命令行选项

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

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

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

cargo run -- -h

我们现在应该能看到一个帮助菜单,列出了我们可以使用的参数列表。在接下来的部分中,我们将利用这些参数为我们的负载均衡器做更多事情。

在后台运行

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

cargo run -- -d

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

pkill -SIGTERM load_balancer

(SIGTERMpkill 的默认信号。)

配置

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

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

使用这个 conf 文件:

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

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

从客户端的角度来看,服务始终在运行,因为侦听套接字永远不会关闭。

完整示例

此示例的完整代码可在以下仓库中找到

pingora-proxy/examples/load_balancer.rs

您可能会发现有用的其他示例也可以在这里找到

pingora-proxy/examples/ pingora/examples

翻译自官方文档:https://github.com/cloudflare/pingora/blob/main/docs/quick_start.md

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容