18. 从零开始编写一个类nginx工具, 主动式健康检查源码实现
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个请求失败,如果是重要的数据,我们会丢失很多重要数据。
如果此时客户端拥有重试机制,那么客户端在失败的时候会发起重试,而且系统可能会反复的分配到那台不可达的系统,将会造成短时间内请求数激增,可能引发系统的雪崩。
所以此时我们主动知道目标端的系统稳定性极其重要。
网络访问示意图
以下是没有主动健康检查
participant 客户端
participant 代理服务器
客户端->>代理服务器: 请求数据(0.5s)
代理服务器->>后端1: 连接并请求数据(5s)失败
Note right of 后端1: 机器宕机不可达
代理服务器-->>客户端: 返回失败0.5s(总耗时6s)
客户端->>代理服务器: 重新请求数据(0.5s)
代理服务器->>后端2: 请求数据成功(0.2s)
后端2-->>代理服务器: 返回数据成功(0.2s)
代理服务器-->> 客户端: 返回数据成功0.5s(总耗时1.4s)
如果出错的时候,一个请求的平均时长可能会达到(1.4s + 5s) / 2 = (3.2s),比正常访问多了(3.2 - 1.4) = 1.8s,节点的宕机会对系统的稳定性产生较大的影响
以下是主动健康检查,它保证了访问后端服务器组均是正常的状态
客户端->>代理服务器: 请求数据(0.5s)
loop 健康检查
代理服务器->>服务器组(只访问1): 定时请求,保证存活,1检查成功,2检查失败
end
Note right of 服务器组(只访问1): 处理客户端数据
代理服务器 -->> 服务器组(只访问1): 请求数据(0.2s)
服务器组(只访问1) -->> 代理服务器: 返回数据成功(0.2s)
代理服务器-->>客户端: 返回数据成功(0.5s)(总耗时1.4s)
服务器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::timeout和future做组合,等超时的时候直接按错误处理
部分实现源码
主要源码定义在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(())
}
结语
主动检查可以及时的更早的发现系统中不稳定的因素,是系统稳定性的基石,也可以通过更早的发现因素来通知运维介入,我们的目的是使系统更稳定,更健壮,处理延时更少。
点击 [关注],[在看],[点赞] 是对作者最大的支持
18. 从零开始编写一个类nginx工具, 主动式健康检查源码实现的更多相关文章
- 从零开始编写一个BitTorrent下载器
从零开始编写一个BitTorrent下载器 BT协议 简介 BT协议Bit Torrent(BT)是一种通信协议,又是一种应用程序,广泛用于对等网络通信(P2P).曾经风靡一时,由于它引起了巨大的流量 ...
- 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。
22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...
- 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n
35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...
- 编写一个类,其中包含一个排序的方法Sort(),当传入的是一串整数,就按照从小到大的顺序输出,如果传入的是一个字符串,就将字符串反序输出。
namespace test2 { class Program { /// <summary> /// 编写一个类,其中包含一个排序的方法Sort(),当传入的是一串整数,就按照从小到大的 ...
- 二、 编写一个类,用两个栈实现队列,支持队列的基本操作(add,poll,peek)
请指教交流! package com.it.hxs.c01; import java.util.Stack; /* 编写一个类,用两个栈实现队列,支持队列的基本操作(add,poll,peek) */ ...
- 如何编写一个SQL注入工具
0x01 前言 一直在思考如何编写一个自动化注入工具,这款工具不用太复杂,但是可以用最简单.最直接的方式来获取数据库信息,根据自定义构造的payload来绕过防护,这样子就可以. 0x02 SQL注 ...
- 从零开始编写一个vue插件
title: 从零开始编写一个vue插件 toc: true date: 2018-12-17 10:54:29 categories: Web tags: vue mathjax 写毕设的时候需要一 ...
- 题目一:编写一个类Computer,类中含有一个求n的阶乘的方法
作业:编写一个类Computer,类中含有一个求n的阶乘的方法.将该类打包,并在另一包中的Java文件App.java中引入包,在主类中定义Computer类的对象,调用求n的阶乘的方法(n值由参数决 ...
- 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】
目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...
- nginx限流&健康检查
Nginx原生限流模块: ngx_http_limit_conn_module模块 根据前端请求域名或ip生成一个key,对于每个key对应的网络连接数进行限制. 配置如下: http模块 ser ...
随机推荐
- 使用Stable Diffusion生成艺术二维码
在数字艺术的世界中,二维码已经从单纯的信息承载工具转变为可以展示艺术表达的媒介.这是通过使用Stable Diffusion的技术实现的,它可以将任何二维码转化为独特的艺术作品.接下来,我们将一步步教 ...
- Seal AppManager如何基于Terraform简化基础设施管理
作者简介 陈灿,数澈软件Seal 后端研发工程师,曾在腾讯负责敏捷研发体系建设以及 DevOps 解决方案的敏捷实践.在敏捷研发和产品效能提升有着丰富的经验,致力于构建一站式研发友好的平台工程解决方案 ...
- 2022-02-08 IValueConverter和StringFormat
主页 后台 stringFormat
- python连接数据库及查询包含中文错误解决方法
使用MySQLdb库来连接数据库 import MySQLdb conn = MySQLdb.connect(host='127.0.0.1', user='root', passwd='', por ...
- 根据图片搜索excel
问题描述:在excel使用中,当我们用大量的excel记录图文信息的时候,如果excel过多,比如成百上千个,里面都是包含大量的图片.这个时候如果想要根据图片快速找到这张图片可能被哪些excel包含, ...
- npm与package.json的联系
在nodejs编写的脚手架项目中,npm是不可缺少的包管理工具,当使用npm初始化时,会生成package.json文件来对项目进行整体的管理和描述 以下是新建的练习项目中package.json ...
- python将print的打印内容保存到日志
将python程序中的所有打印内容都输出到日志文件中,在程序执行完成后,方便查询程序运行过程是否出现异常. 1. 将打印内容输出到日志文件 1.1 代码实现: sys.stdout = open('s ...
- xss-labs靶场
在线XSS-labs靶场:https://xssaq.com/yx/ 靶场搭建 靶场是直接使用docker搭建的 docker pull vulfocus/xss-labs 启动靶场 docker r ...
- 关于 Llama 2 的一切资源,我们都帮你整理好了
Llama 2 是一个由 Meta 开发的大型语言模型,是 LLaMA 1 的继任者.Llama 2 可通过 AWS.Hugging Face 获取,并可以自由用于研究和商业用途.Llama 2 预训 ...
- 图解Spark Graphx实现顶点关联邻接顶点的函数原理
一.场景案例 在一张社区网络里,可能需要查询出各个顶点邻接关联的顶点集合,类似查询某个人关系比较近的都有哪些人的场景. 在用Spark graphx中,通过函数collectNeighbors便可以获 ...