18. 从零用Rust编写正反向代理, 主动式健康检查源码实现

wmproxy

wmproxy是由Rust编写,已实现http/https代理,socks5代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket代理等,同时会将实现过程分享出来, 感兴趣的可以一起造个轮子法

项目地址

gite: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

为什么我们需要主动

  主动可以让我们掌握好系统的稳定性,假设我们有一条连接不可达,连接超时的判定是5秒,需要检测失败3次才认定为失败,那么此时从我们开始检测,到判定失败需要耗时15秒。

  如果此时我们是个高并发的系统,每秒的QPS是1000,我们有三个地址判定,那么此时我们有1/3的失败概率。那么在15秒内,我们会收到15000个请求,会造成5000个请求失败,如果是重要的数据,我们会丢失很多重要数据。

  如果此时客户端拥有重试机制,那么客户端在失败的时候会发起重试,而且系统可能会反复的分配到那台不可达的系统,将会造成短时间内请求数激增,可能引发系统的雪崩。

  所以此时我们主动知道目标端的系统稳定性极其重要。

网络访问示意图

以下是没有主动健康检查

客户端 代理服务器 后端1 后端2 请求数据(0.5s) 连接并请求数据(5s)失败 机器宕机不可达 返回失败0.5s(总耗时6s) 重新请求数据(0.5s) 请求数据成功(0.2s) 返回数据成功(0.2s) 返回数据成功0.5s(总耗时1.4s) 客户端 代理服务器 后端1 后端2

如果出错的时候,一个请求的平均时长可能会达到(1.4s + 5s) / 2 = (3.2s),比正常访问多了(3.2 - 1.4) = 1.8s,节点的宕机会对系统的稳定性产生较大的影响

以下是主动健康检查,它保证了访问后端服务器组均是正常的状态

客户端 代理服务器 服务器组(只访问1) 请求数据(0.5s) 定时请求,保证存活,1检查成功,2检查失败 loop [健康检查] 处理客户端数据 请求数据(0.2s) 返回数据成功(0.2s) 返回数据成功(0.5s)(总耗时1.4s) 客户端 代理服务器 服务器组(只访问1)

服务器2出错的时候,主动检查已经检查出服务器2不可用,负载均衡的时候选择已经把服务器2摘除,所以系统的平均耗时1.4s,系统依然保持稳定

健康检查的种类

在目前的系统中有以下两分类:

  • HTTP 请求特定的方法及路径,判断返回是否得到预期的status或者body
  • TCP 仅只能测试连通性,如果能连接表示正常,会出现能连接但无服务的情况

健康检查的准备

我们需要从配置中读出所有的需要健康检查的类型,即需要去重,把同一个指向的地址过滤掉
配置有可能被重新加载,所以我们需要预留发送配置的方式(或者后续类似nginx用新开进程的方式则不需要),此处做一个预留。

  • 如何去重
    像这种简单级别的去重通常用HashSet复杂度为O(1)或者用简单的Vec复杂度为O(n),以SocketAddr的为键值,判断是否有重复的数据。

  • 如何保证不影响主线程
    把健康请求的方法移到异步函数,用tokio::spawn中处理,在健康检查的情况下保证不影响其它数据处理

  • 如果同时处理多个地址的健康检查
    每一次健康检查都会在一个异步函数中执行,在我们调用完请求后,我们会对当前该异步进行tokio::time::sleep以让出当前CPU。

  • 如何按指定间隔时间请求
    因为每一次健康请求都是在异步函数中,我们不确认之前的异步是否完成,所以我们在每次请求前都记录last_request,我们在请求前调用HealthCheck::check_can_request判断当前是否可以发送请求来保证间隔时间内不多次请求造成服务器的压力。

  • 超时连接判定处理
    利用tokio::time::timeoutfuture做组合,等超时的时候直接按错误处理

部分实现源码

主要源码定义在check/active.rs中,主要的定义两个类

/// 单项健康检查
#[derive(Debug, Clone)]
pub struct OneHealth {/// 主动检查地址pub addr: SocketAddr,/// 主动检查方法, 有http/https/tcp等pub method: String,/// 每次检查间隔pub interval: Duration,/// 最后一次记录时间pub last_record: Instant,
}
/// 主动式健康检查
pub struct ActiveHealth {/// 所有的健康列表pub healths: Vec<OneHealth>,/// 接收健康列表,当配置变更时重新载入pub receiver: Receiver<Vec<OneHealth>>,
}

我们在配置的时候获取所有需要主动检查的数据

/// 获取所有待健康检查的列表
pub fn get_health_check(&self) -> Vec<OneHealth> {let mut result = vec![];let mut already: HashSet<SocketAddr> = HashSet::new();if let Some(proxy) = &self.proxy {// ...}if let Some(http) = &self.http {// ...}result
}

主要的检查源码,所有的最终信息都落在HealthCheck中的静态变量里:

pub async fn do_check(&self) -> ProxyResult<()> {// 防止短时间内健康检查的连接过多, 做一定的超时处理, 或者等上一条消息处理完毕if !HealthCheck::check_can_request(&self.addr, self.interval) {return Ok(())}if self.method.eq_ignore_ascii_case("http") {match tokio::time::timeout(self.interval + Duration::from_secs(1), self.connect_http()).await {Ok(r) => match r {Ok(r) => {if r.status().is_server_error() {log::trace!("主动健康检查:HTTP:{}, 返回失败:{}", self.addr, r.status());HealthCheck::add_fall_down(self.addr);} else {HealthCheck::add_rise_up(self.addr);}}Err(e) => {log::trace!("主动健康检查:HTTP:{}, 发生错误:{:?}", self.addr, e);HealthCheck::add_fall_down(self.addr);}},Err(e) => {log::trace!("主动健康检查:HTTP:{}, 发生超时:{:?}", self.addr, e);HealthCheck::add_fall_down(self.addr);},}} else {match tokio::time::timeout(Duration::from_secs(3), self.connect_http()).await {Ok(r) => {match r {Ok(_) => {HealthCheck::add_rise_up(self.addr);}Err(e) => {log::trace!("主动健康检查:TCP:{}, 发生错误:{:?}", self.addr, e);HealthCheck::add_fall_down(self.addr);}}}Err(e) => {log::trace!("主动健康检查:TCP:{}, 发生超时:{:?}", self.addr, e);HealthCheck::add_fall_down(self.addr);}}}Ok(())
}

结语

主动检查可以及时的更早的发现系统中不稳定的因素,是系统稳定性的基石,也可以通过更早的发现因素来通知运维介入,我们的目的是使系统更稳定,更健壮,处理延时更少。

点击 [关注][在看][点赞] 是对作者最大的支持

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

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

相关文章

Python陷阱-如何安全地删除列表元素?

一个常见的任务是在一个列表上迭代&#xff0c;并根据条件删除一些元素。本文将展示如何完成该任务的不同方法&#xff0c;同时展示一些需要避免的陷阱。 假设我们需要修改列表a&#xff0c;并且必须删除所有不是偶数的项。首先实现辅助函数even(x)来确定一个数字x是否是偶数: …

LT8911EX LVDS 转 eDP

概述 Lontium LT8911EX 是 LVDS 至 eDP 转换器&#xff0c;具有单端口或双端口可配置 LVDS 接收器&#xff0c;具有 1 个时钟通道和多达 8 个数据通道&#xff0c;每个数据通道的最大工作速率为 1.2Gbps&#xff0c;最大输入带宽为 9.6Gbps。该转换器对输入LVDS数据进行反串行…

商用净水器行业分析:到2025年市场零售规模将接近500亿元

商用净水器与家庭净水器相差无几&#xff0c;只是出水量大小不同。一般商业净水器都采用中央净水器和集成净水器这两种&#xff0c;这样不仅可以解决工业用水&#xff0c;还可解决工人日常饮用水、沐浴用水和洗涤用水等生活用水问题。 目前我国中央水处理整机依然处于市场发展的…

【小黑嵌入式系统第十四课】μC/OS-III程序设计基础(三)——信号量(任务同步资源同步)、事件标记组(与或多个任务)

上一课&#xff1a; 【小黑嵌入式系统第十三课】PSoC 5LP第二个实验——中断控制实验 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff1a;人工智能 文章目录 1 信号量1.1 简介1.2…

DNS:解析互联网的“导航系统”

引言&#xff1a; 在互联网时代&#xff0c;我们每天都在使用各种网站和服务&#xff0c;但很少有人真正了解这些网站和服务是如何被找到和访问的。这背后有一个被称为DNS&#xff08;域名系统&#xff09;的“导航系统”&#xff0c;它负责将人类可读的域名转换为计算机可识别…

学习笔记之——3D Gaussian Splatting源码解读

之前博客对3DGS进行了学习与调研 学习笔记之——3D Gaussian Splatting及其在SLAM与自动驾驶上的应用调研-CSDN博客文章浏览阅读450次。论文主页3D Gaussian Splatting是最近NeRF方面的突破性工作&#xff0c;它的特点在于重建质量高的情况下还能接入传统光栅化&#xff0c;优…

对比两个json对象有那几个字段被修改,并返回有改动的字段内容

如果您想比较两个 JSON 对象&#xff0c;找出哪些字段发生了变化&#xff0c;并返回发生变化的字段及其新的值&#xff0c;您可以编写一个函数来递归比较对象。以下是一个 TypeScript 示例&#xff1a; type JSONValue string | number | boolean | null | JsonObject | Json…

LNMP平台对接redis服务

目录 1、安装 LNMP 各个组件 2、安装 redis 服务 3、安装 redis 扩展 4、修改 php 配置文件 5、测试连接 1、安装 LNMP 各个组件 2、安装 redis 服务 3、安装 redis 扩展 官网&#xff1a;http://redis.io/ 下载包&#xff1a; https://codeload.github.com/phpredis/p…

C++:常量

const的最初动机 const的使用方法 使用const的好处是允许指定一种语义上的约束&#xff0c;即某种对象不能被修改&#xff0c;且由编译器具体实施这种约束。 const声明格式&#xff1a;const 类型名 对象名;修饰普通变量&#xff0c;时期不能被随意修改 【注意】1.C中的const…

探索大模型时代下的文档识别与分析【GPT4-V带来的挑战与机遇】

中国图象图形学学会青年科学家会议是由中国图象图形学学会青年工作委员会发起的学术会议。本会议面向国际学术前沿与国家战略需求&#xff0c;致力于支持图象图形领域的优秀青年学者&#xff0c;为青年学者们提供学术交流与研讨的平台&#xff0c;促进学者之间的交流与合作。会…

深入理解C#中的引用类型、引用赋值以及 `ref` 关键字

深入理解C#中的引用类型、引用赋值以及 ref 关键字 在C#编程中&#xff0c;理解引用类型、引用赋值以及 ref 关键字的使用对于编写高效、可靠的代码至关重要。本文将深入探讨这些概念&#xff0c;帮助您更好地理解C#的工作原理。 引用类型简介 在C#中&#xff0c;所有的类型都…

MySQL的事务机制

一、事务机制简述 事务机制,避免写入直接操作数据文件&#xff1b;利用日志来实现间接写入&#xff0c;与事务有关的, redo日志与undo日志&#xff1b;sql语句操作记录复制到undo日志然后增删改查操作的结果会记录在redo日志&#xff0c;如果操作没有什么问题就把数据同步到数…

【MySQL】MySQL版本8+ 的 with recursive 递归语法初次使用

力扣题 1、题目地址 1613. 找到遗失的ID 2、模拟表 表&#xff1a;Customers Column NameTypecustomer_idintcustomer_namevarchar customer_id 是该表主键.该表第一行包含了顾客的名字和 id. 3、要求 编写一个解决方案, 找到所有遗失的顾客 id。遗失的顾客 id 是指那些…

代码随想录算法训练营第三十天 | 332.重新安排行程、51. N皇后、37. 解数独

332.重新安排行程 题目链接&#xff1a;332.重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的…

安卓fragment监听文本内容取值

首先需要自己定义一个最大的BaseFragment&#xff0c;继承这个BaseFragment并在骑宠填充你需要绑定的Fragment class LoginFragment : BaseFragment<FragmentLoginBinding>(R.layout.fragment_login) { }顶自己需要获取的值 private lateinit var account: EditTextpriv…

Prometheus C++使用教程

1 简介 Prometheus是一个包括时序数据库的工具&#xff0c;可以将指标Metric数据传入Prometheus&#xff0c;然后通过Grafana可视化出来。 Grafana是一个通用的数据看板&#xff0c;可以通过自定义的看板&#xff0c;实时的观察各类指标的变化。 2 安装Prometheus 2.1 安装d…

综合指南:理解气体检测仪的关键功能和单位换算

随着科技的飞速发展&#xff0c;气体检测仪在各行各业中的应用已十分普遍&#xff0c;其主要用途是检测环境中的气体浓度。 1、检测气体纯度 主要用于气体储罐、管道等储运设备中检测气体的纯度&#xff0c;一般都是专门针对高纯气体的浓度值进行检测&#xff0c;常见的如氩气…

C语言数组基础知识

目录 一维数组&#xff1a; 一维数组的创建&#xff1a; 一维数组的访问&#xff1a; 一维数组在内存中的存储&#xff1a; 二维数组&#xff1a; 二维数组的创建&#xff1a; 二维数组的初始化&#xff1a; 二维数组的使用&#xff1a; 二维数组在内存中的存储&#x…

部署Gitea服务的那些坑

目标&#xff1a;在win10系统上部署Gitea服务&#xff0c;以ssh协议的方式访问。 首先要在win10系统上安装ssh服务&#xff0c;这里安装OpenSSH即可&#xff0c;PowerShell脚本如下&#xff08;记住管理员运行&#xff09;&#xff1a; # 打开 PowerShell 以管理员身份运行 #…

为什么会有js?

JavaScript&#xff08;简称为JS&#xff09;是一种脚本语言&#xff0c;主要用于为网页添加交互性和动态效果。JS的出现主要有以下几个原因&#xff1a; 网页交互性的需求&#xff1a;早期的静态网页只能展示信息&#xff0c;无法与用户进行交互。随着互联网的发展&#xff0c…