用Rust和Pingora轻松构建超越Nginx的高效负载均衡器

目录

  1. 什么是Pingora?
  2. 实现过程
    • 初始化项目
    • 编写负载均衡器代码
    • 代码解析
    • 部署
  3. 总结

1. 什么是Pingora?

Pingora 是一个高性能的 Rust 库,用于构建可负载均衡器的代理服务器,它的诞生是为了弥补 Nginx 存在的缺陷。

Pingora 提供了丰富的功能和高度的扩展性,适用于各种网络应用场景。其高效的性能、易于扩展的设计以及 Rust 语言本身的安全性和速度。使得 Pingora 能够处理大量并发请求,确保高可靠性和稳定性。本文将带您一步步使用 Pingora 构建一个基础的负载均衡器。

如果你还不了解 Pingora 的相关背景, 建议先阅读:《一天为用户节省434年握手时间!Rust编写的Pingora凭什么力压Nginx?》

2. 实现过程

2.1 初始化项目

首先,我们需要一个 Rust 项目,并添加必要的依赖项。在项目根目录下的 Cargo.toml 文件中添加以下内容:

[package]
name = "load_balancer"
version = "0.1.0"
edition = "2021"[dependencies]
async-trait = "0.1"
pingora = { version = "0.1", features = ["lb"] }

2.2 编写负载均衡器代码

src/main.rs 中编写负载均衡器的实现代码。以下是完整的代码示例:

use async_trait::async_trait;
use pingora::{prelude::*, services::Service};
use std::sync::Arc;fn main() {// 创建一个服务器实例,传入Some(Opt::default())代表使用默认配置,程序执行时支持接收命令行参数let mut my_server = Server::new(Some(Opt::default())).unwrap();// 初始化服务器my_server.bootstrap();// 创建一个负载均衡器,包含多个上游服务器let mut upstreams = LoadBalancer::try_from_iter(["10.0.0.1:8080", "10.0.0.2:8080", "10.0.0.3:8080"]).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();// 创建一个HTTP代理服务,并传入服务器配置和负载均衡器let mut lb_service: pingora::services::listening::Service<pingora::proxy::HttpProxy<LB>> =http_proxy_service(&my_server.configuration, LB(upstreams));// 添加一个TCP监听地址,监听80端口lb_service.add_tcp("0.0.0.0:80");// 添加一个TLS监听地址,监听443端口println!("The cargo manifest dir is: {}", env!("CARGO_MANIFEST_DIR"));// 在项目目录下新增一个 keys 目录,对应证书文件放在该目录下let cert_path = format!("{}/keys/example.com.crt", env!("CARGO_MANIFEST_DIR"));let key_path = format!("{}/keys/example.com.key", env!("CARGO_MANIFEST_DIR"));let mut tls_settings =pingora::listeners::TlsSettings::intermediate(&cert_path, &key_path).unwrap();tls_settings.enable_h2();lb_service.add_tls_with_settings("0.0.0.0:443", None, tls_settings);// 定义服务列表,这个示例只有一个负载均衡服务,后续有需要可以添加更多,将服务列表添加到服务器中let services: Vec<Box<dyn Service>> = vec![Box::new(lb_service)];my_server.add_services(services);// 运行服务器,进入事件循环my_server.run_forever();
}// 定义一个包含负载均衡器的结构体LB,用于包装Arc指针以实现多线程共享
pub struct LB(Arc<LoadBalancer<RoundRobin>>);// 使用#[async_trait]宏,异步实现ProxyHttp trait。
#[async_trait]
impl ProxyHttp for LB {/// 定义上下文类型,这里使用空元组,对于这个小例子,我们不需要上下文存储type CTX = ();// 创建新的上下文实例,这里返回空元组fn new_ctx(&self) -> () {()}// 选择上游服务器并创建HTTP对等体async fn upstream_peer(&self, _session: &mut Session, _ctx: &mut ()) -> Result<Box<HttpPeer>> {// 使用轮询算法选择上游服务器let upstream = self.0.select(b"", 256) // 对于轮询,哈希不重要.unwrap();println!("上游对等体是:{upstream:?}");// 创建一个新的HTTP对等体,设置SNI为example.comlet peer: Box<HttpPeer> =Box::new(HttpPeer::new(upstream, false, "example.com".to_string()));Ok(peer)}// 在上游请求发送前,执行一些额外操作,例如将某些参数插入请求头,这里的示例是插入Host头部async fn upstream_request_filter(&self,_session: &mut Session,upstream_request: &mut RequestHeader,_ctx: &mut Self::CTX,) -> Result<()> {// 将Host头部设置为example.com,当然,在现实需求中,这一步可能是多余的upstream_request.insert_header("Host", "example.com").unwrap();Ok(())}
}

3. 代码解析

3.1 对等体健康检查

为了使我们的负载均衡器更可靠,我们添加了健康检查功能到我们的上游对等体。这样,如果有一个对等体已经出现异常,就可以快速停止将流量路由到该对等体。如下代码

fn main() {// ...// 以下对等体中包含一个异常的对等体let upstreams =LoadBalancer::try_from_iter(["10.0.0.1:8080", "10.0.0.2:8080", "10.0.0.3:8080"]).unwrap();// ...
}

现在如果我们再次运行我们的负载均衡器 cargo run,并用以下命令测试它:

curl http://127.0.0.1 -svo /dev/null

如果去掉健康检查的代码片段,我们发现会出现 502: Bad Gateway 的失败情况,这是因为我们的对等体选择严格遵循我们给出的 RoundRobin 选择模式,而没有考虑该对等体是否健康。通过引入一个健康检查的功能来解决这个问题,进而排除掉不健康对等体。关键代码如下

fn main() {// ...// 健康检查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();// ...
}
3.2 接收命令行参数

在创建 pingora 服务时,传入了一个 Some(Opt::default())

参数,pingora 将会捕获我们运行的命令行参数,并使用这些参数来配置 pingora 服务。代码变更如下

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

我们可以通过以下命令来看 pingora 负载均衡器的参数说明

cargo run -- -h

这时我们可以了解到 pingora 相关参数提供的功能,后续可以为我们的服务器实现更多的功能。

4. 部署

4.1 后台运行

通过传递 -d 或者 --daemon 参数,可以将 pingora 运行在后台。如果要优雅的停止 pingora,可以使用 pkill 命令并且传递 SIGTERM 信号,那么在关闭的过程中,服务将停止接收新的请求,但是仍然会处理完当前请求再退出。命令如下

# 后台运行,我们使用release模式,因为debug模式下会生成调试信息,会影响性能
cargo run --release -- -d
# 优雅的停止
pkill -SIGTERM load_balancer
4.2 配置

Pingora 配置文件可以定义 Pingora 如何运行,以下定义了 Pingora 的版本、线程数、pid文件、错误日志文件、升级套接字文件的配置,文件名称命名为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

加载配置文件运行如下:

# 设置日志级别
RUST_LOG=INFO
# 启用
cargo run --release -- -c conf.yaml -d
4.3 优雅地升级

假设我们更改了负载均衡器的代码并重新编译了二进制文件,现在我们希望将正在后台运行的服务升级到这个新版本。如果我们简单地停止旧服务,然后启动新服务,那么在中间到达的一些请求可能会丢失。幸运的是,Pingora 提供了一种优雅的方式来升级服务。

首先,我们通过SIGQUIT停止正在运行的服务,然后使用-u或者--upgrade参数来启动全新的程序,如下命令

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

在升级过程中,Pingora 将会自动将请求路由到新的服务,而不会丢失任何请求。从客户端的角度来看,用户感觉不到任何变化。

5. 总结

到此为止,我们已经拥有了一个功能完备的负载均衡器。通过这个简单的示例,相信大家已经对 Pingora 有了一个初步的了解。不过,这是一个非常基础的负载均衡器。在实际应用中,负载均衡器的配置和功能可能会更加复杂,我们还需要根据实际需求来进行扩展和优化。

在后续,我也会分享一些关于 Pingora 以及新兴热门技术的更多内容,欢迎继续关注!

本文完整示例代码:https://github.com/phyuany/simple-pingora-reverse-proxy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/30614.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Scala学习笔记13: 集合

目录 第十三章 集合1- 列表 (List)2- 集 (Set)3- 映射 (Map)4- 数组 (Array)5- 元组 (Tuple)6- 可变和不可变集合7- 添加或者去除元素8- 化简、折叠和扫描9- 拉链操作10- 迭代器end 第十三章 集合 在Scala中, 集合 (Collections) 是一种重要的数据结构, 用于存储和操作一组元素…

PostgreSQL源码分析——psql

psql是一个PostgreSQL数据库自带的客户端工具&#xff0c;用来与数据库进行交互&#xff0c;当然&#xff0c;你也可以用其他工具。这里&#xff0c;我们简单分析一下psql工具的实现。 主流程如下 psql的核心功能&#xff0c;连接数据库&#xff0c;执行用户的命令&#xff0…

定制汽车霍尔传感器

磁电效应霍尔传感器、饱和霍尔传感器、非线性霍尔传感器 霍尔传感器原理 霍尔传感器的工作原理基于霍尔效应&#xff0c;即当一块通有电流的金属或半导体薄片垂直地放在磁场中时&#xff0c;薄片的两端会产生电位差。这种现象称为霍尔效应&#xff0c;两端具有的电位差值称为…

嵌入式实验---实验二 中断功能实验

一、实验目的 1、掌握STM32F103中断程序设计流程&#xff1b; 2、熟悉STM32固件库的基本使用。 二、实验原理 1、在上一章的实验基础上&#xff0c;添加一个按键和一个LED&#xff1b; 2、使用中断的方式实现以下两个功能&#xff1a; &#xff08;1&#xff09;KEY1按键…

【git】gitee仓库本地克隆失败可能的一种解决办法

出错点&#xff1a; 在 gitee 克隆远程仓库到 本地时&#xff0c;可能会出现以下报错情况&#xff0c;无法成功克隆 正常流程&#xff1a;&#xff08;熟悉正常克隆流程的可以直接跳到下面的【解决办法】&#xff09; 我们一般复制仓库地址是在下面红线框框的位置&#xff0c…

华为云与AWS负载均衡服务深度对比:性能、成本与可用性

随着云计算的迅速发展&#xff0c;企业对于云服务提供商的选择变得越来越关键。在选择云服务提供商时&#xff0c;负载均衡服务是企业关注的重点之一。我们九河云将深入比较两大知名云服务提供商华为云和AWS的负载均衡服务&#xff0c;从性能、成本和可用性等方面进行对比。 AW…

知识库的创建(4) - KBServiceFactory:获取不同类型知识库服务的工厂类

文章目录 前言一、 方法详解1. get_service2. get_service_by_name3. get_default 二、 代码注释总结 前言 上一篇我们在update_docs里看到了 KBServiceFactory.get_service_by_name(knowledge_base_name)&#xff0c;这一篇我们一起来看看KBServiceFactory类 KBServiceFactor…

数据赋能(124)——体系:数据格式化——影响因素、直接作用、主要特征

影响因素 数据格式化过程中需要考虑的一些影响因素&#xff1a; 数据质量和准确性&#xff1a; 数据清洗&#xff1a;在格式化之前&#xff0c;应确保数据的质量和准确性。这包括去除重复数据、处理缺失值、纠正错误数据等。数据验证&#xff1a;在格式化过程中&#xff0c;应…

P1656 炸铁路

题目描述 A 国派出将军 uim&#xff0c;对 B 国进行战略性措施&#xff0c;以解救涂炭的生灵。 B 国有 n 个城市&#xff0c;这些城市以铁路相连。任意两个城市都可以通过铁路直接或者间接到达。 uim 发现有些铁路被毁坏之后&#xff0c;某两个城市无法互相通过铁路到达。这…

Vue--》从零开始打造交互体验一流的电商平台(三)

今天开始使用 vue3 + ts 搭建一个电商项目平台,因为文章会将项目的每处代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的github上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本专栏,学习更多…

leetcode刷题(46-50)

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可以啦…

【机器学习】 第1章 概述

一、概念 1.机器学习是一种通过先验信息来提升模型能力的方式。 即从数据中产生“模型”( model )的算法&#xff0c;然后对新的数据集进行预测。 2.数据集&#xff08;Dataset&#xff09;&#xff1a;所有数据的集合称为数据集。 训练集&#xff1a;用来训练出一个适合模…

TCP/UDP协议传输

TCP 客户端 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h>//宏定义错误输出格式>>>>类比封装函数#…

什么是无限铸币攻击?它是如何运作的?

一、无限铸币攻击解释 无限铸币攻击是指攻击者操纵合约代码不断铸造超出授权供应限制的新代币。 这种黑客行为在去中心化金融 (DeFi) 协议中最为常见。这种攻击通过创建无限数量的代币来损害加密货币或代币的完整性和价值。 例如&#xff0c;一名黑客利用了 Paid 网络的智能…

ansible 模块进阶及变量

yum 模块进阶 - name: install pkgs hosts: webservers tasks: - name: install web pkgs # 此任务通过yum安装三个包 yum: name: httpd,php,php-mysqlnd state: present # 根据功能等&#xff0c;可以将一系列软件放到一个组中&#xff0c;安装软件包组&#xff0c;将会把很…

shell脚本之数组及冒泡排序

1.数组定义&#xff1a;在集合当中指定多个元素&#xff0c;元素的类型可以是整数、字符串及浮点。 2.数组作用&#xff1a;一次性的定义多个元素&#xff0c;可以为变量赋值提供便利。 3.数组的定义方法&#xff1a; 数组名&#xff08;a b c d&#xff09; 数组名不能重复…

IPV6配置二

IV6 的单播路由协议-----在使用路由协议前一定需要开启 IPV6的单播路由功能&#xff0c;否则不转发IPV6的流量 【1】IPV6 静态路由协议&#xff1a; (1)普通静态路由 rl(config)#ipv6 route 2::/64 serial 1/1 rl(config)#ipv6 route 2::/64 12:2 &#xff1f; …

【速过】2024年9月三级数据库技术题库+知识点总结

24年3月已经考了一次数据库&#xff0c;实话&#xff0c;三级比二级简单一些&#xff0c;知识点都比较集中&#xff0c;50%-60%是题库里面的原题&#xff0c;考前只要好好的过一遍题库考到80以上完全没有问题&#xff0c;你实在不会答案背下来也是可以的&#xff0c;不过更多的…

kotlin函数

1、函数定义 // 下边定义了main函数 fun main() {} 2、函数的类型 // foo函数定义 fun foo () {} // 对应无参类型 () -> Unit fun foo (a: Int):String {} // 对应有参类型 (Int) -> String 3、函数的引用 函数的引用类似C语言中的函数指针&#xff0c;可用于函数传…

外包干了2年,彻底废了...

先说一下自己的情况。大专生&#xff0c;17年通过校招进入湖南某软件公司&#xff0c;干了接近2年的点点点&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了五年的功能测试…