19. 从零用Rust编写正反向代理, 配置数据的热更新原理及实现

wmproxy

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

项目地址

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

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

配置数据

数据通常配置在配置文件中,如果需要变更配置,我们通常将配置文件进行更新,并通知程序重新加载配置以便生效。

nginx的变更方式

在nginx中,我们通常用

nginx -s reload

进行数据的安全无缝的重载。在nginx中,是多进程的模式,也就是在nginx -s reload信号发出后master进程通知之前的work进程停止接收新的流,也就是accpet暂停,但是会服务完当前的数据请求,并同时会启用新的work进程来接受新的请求
在这里插入图片描述

缺点:nginx只能整体的配置做全部重置,且无法查看当前的配置(除非看配置文件,配置可能被重新修改过和内存中的值可能不匹配)

当前选取的方式

当前选择的是用HTTP请求的方式,也就是对本地的端口进行监听(http://127.0.0.1:8837),对本地端口监听也不会造成对外暴露端口带来的安全问题,这样子可以高度的自定义。具有比较高的活跃性,也可以实时查询内存中的数据。

例如访问:

  • http://127.0.0.1:8837/reload即可通知目标进程重载当前的配置
  • http://127.0.0.1:8837/now即可以知道当前的所有的配置列表
  • http://127.0.0.1:8837/stop即可以关闭当前的进程,停止服务,类似于nginx中的nginx -s stop
  • http://127.0.0.1:8837/adapt加载当前配置,看是否错误,但是不进行应用。
    等功能。

功能实现的原理

  • 单进程
    单进程模式的缺点:如果存在内存泄漏之类的情况,无论如何重载进程都无法将内存恢复,会始终保持较高的内存值直到最终不可用的阶段。如果发生未正确处理的异常,可能会使该进程崩溃的风险,处于无服务状态。
    单进程模式的优点:在当前进程存储的一些有利于加速服务的将会很好的被保留下来(如健康检查的数据),异步进程里正在处理的数据等。无需进行进程间通讯,配合tokio的异步处理可以将单进程的优势完美发挥出来。

  • 端口复用
    无论哪种模式,都需要处理数据重载时,绑定对象的转移TcpListener或者重新绑定TcpListener,在Rust中转移绑定对象相对来说较麻烦后续如果拓展成多进程模式也无法进行转移,所以不考虑用转移所有权的问题。那么此时我们的解决方法就是set_reuse_addressset_reuse_port,不同平台该方法上有不同的表现,我们用的是socket2的封装,用该方法的注意事项:

  • 在windows平台上,不存在set_reuse_port方法,仅调用set_reuse_address即可实现一个地址多次绑定

  • 在linux上,不同的版本上,有些只需调用set_reuse_address即可端口复用,有些需要同时调用set_reuse_port

  • 在macos上,需要调用set_reuse_addressset_reuse_port函数才可实现端口复用

所以这里涉及一个分平台的编码,我们在此使用的是,这和C/C++中的#ifdef WINDOWS类似,但是只能在函数级的做调整,所以此处额外在封装了两个函数来做调用。

/// 非windows平台
#[cfg(not(target_os = "windows"))]
fn set_reuse_port(socket: &Socket, reuse: bool) -> io::Result<()> {
    socket.set_reuse_port(true)?;
    Ok(())
}

/// windows平台,空实现
#[cfg(target_os = "windows")]
fn set_reuse_port(_socket: &Socket, _sreuse: bool) -> io::Result<()> {
    Ok(())
}

然后将原来的TcpListener::bind(addr)函数改成Helper::bind即可无缝切换到支持端口复用的功能,针对代理端及反向代理端:

/// 可端口复用的绑定方式,该端口可能被多个进程同时使用
pub async fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener> {
    let addrs = addr.to_socket_addrs()?;
    let mut last_err = None;
    for addr in addrs {
        let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
        socket.set_nonblocking(true)?;
        let _ = socket.set_only_v6(false);
        socket.set_reuse_address(true)?;
        Self::set_reuse_port(&socket, true)?;
        socket.bind(&addr.into())?;
        match socket.listen(128) {
            Ok(_) => {
                let listener: std::net::TcpListener = socket.into();
                return TcpListener::from_std(listener);
            }
            Err(e) => {
                log::info!("绑定端口地址失败,原因: {:?}", addr);
                last_err = Some(e);
            }
        }
    }

    Err(last_err.unwrap_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            "could not resolve to any address",
        )
    }))
}

测试功能

测试配置加载reload,一开始我们绑定81的端口
在这里插入图片描述

进程启动后改为绑定82的端口,然后调用reload(curl.exe http://127.0.0.1:8837/reload)
在这里插入图片描述

此时,再调用stop(curl.exe http://127.0.0.1:8837/stop),正确的预期应该显示关闭,且82端口不可再访问
在这里插入图片描述

符合功能预期,初步测试完毕

相关源码实现

以下是启动及发送重载配置的流程示意图

加载数据后绑定
(1)绑定端口后启动
(1)异步的方式启动
发送重载入命令
(3)发送关闭服务命令
(2)启动新的服务后关闭原服务
加载配置
绑定端口
控制端
服务1
服务2
控制窗户端

以下是中控的定义,消息的通知主要通过Sender/Receiver来进行数据的通知。

/// 控制端,可以对配置进行热更新
pub struct ControlServer {
    /// 控制端当前的配置文件,如果部分修改将直接修改数据进行重启
    option: ConfigOption,
    /// 通知服务进行关闭的Sender,服务相关如果收到该消息则停止Accept
    server_sender_close: Option<Sender<()>>,
    /// 通知中心服务的Sender,每个服务拥有一个该Sender,可反向通知中控关闭
    control_sender_close: Sender<()>,
    /// 通知中心服务的Receiver,收到一次则将当前的引用计数-1,如果为0则表示需要关闭服务器
    control_receiver_close: Option<Receiver<()>>,
    /// 服务的引用计数
    count: i32,
}

启动控制终端,接收HTTP的指令和关闭的指令,此时control已经变成了Arc<Mutex<ControlServer>>,方便在各各线程间传播,同步修改数据。

pub async fn start_control(control: Arc<Mutex<ControlServer>>) -> ProxyResult<()> {
    let listener = {
        let value = &control.lock().await.option;
        TcpListener::bind(format!("127.0.0.1:{}", value.control)).await?
    };

    loop {
        let mut receiver = {
            let value = &mut control.lock().await;
            value.control_receiver_close.take()
        };
        
        tokio::select! {
            Ok((conn, addr)) = listener.accept() => {
                let cc = control.clone();
                tokio::spawn(async move {
                    let mut server = Server::new_data(conn, Some(addr), cc);
                    if let Err(e) = server.incoming(Self::operate).await {
                        log::info!("反向代理:处理信息时发生错误:{:?}", e);
                    }
                });
                let value = &mut control.lock().await;
                value.control_receiver_close = receiver;
            }
            _ = Self::receiver_await(&mut receiver) => {
                let value = &mut control.lock().await;
                value.count -= 1;
                log::info!("反向代理:控制端收到关闭信号,当前:{}", value.count);
                if value.count <= 0 {
                    break;
                }
                value.control_receiver_close = receiver;
            }
        }
    }
    Ok(())
}

处理相关消息:

if req.path() == "/reload" {
    // 将重新启动服务器
    let _ = value.do_restart_serve().await;
    return Ok(Response::text()
    .body("重新加载配置成功")
    .unwrap()
    .into_type());
}

if req.path() == "/stop" {
    // 通知控制端关闭,控制端阻塞主线程,如果控制端退出后进程退出
    if let Some(sender) = &value.server_sender_close {
        let _ = sender.send(()).await;
    }
    return Ok(Response::text()
    .body("关闭进程成功")
    .unwrap()
    .into_type());
}

以下是主要的启动代码:

async fn inner_start_server(&mut self, option: ConfigOption) -> ProxyResult<()>  {
    let sender = self.control_sender_close.clone();
    let (sender_no_listen, receiver_no_listen) = channel::<()>(1);
    let sender_close = self.server_sender_close.take();
    // 每次启动的时候将让控制计数+1
    self.count += 1;
    tokio::spawn(async move {
        let mut proxy = Proxy::new(option);
        // 将上一个进程的关闭权限交由下一个服务,只有等下一个服务准备完毕的时候才能关闭上一个服务
        if let Err(e) = proxy.start_serve(receiver_no_listen, sender_close).await {
            log::info!("处理失败服务进程失败: {:?}", e);
        }
        // 每次退出的时候将让控制计数-1,减到0则退出
        let _ = sender.send(()).await;
    });
    self.server_sender_close = Some(sender_no_listen);
    Ok(())
}

结语

此时以不同于nginx的另一种配置的加载已经开发完毕,配置的热加载可以让您更从容的保护好您的系统。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3.2引用unplugin-vue-components插入,解放开发中import组件

目录 前言引用unplugin-vue-components插件的优缺点优点缺点 unplugin-vue-components插件引入安装插件配置vite配置更新TypeScript配置使用代码位置 总结Q&A 前言 unplugin-vue-components是一个用于Vue.js项目的插件&#xff0c;特别适用于Vite和Webpack构建工具。它的主…

区块链是怎么存储数据的?

每个块都是有大小限制的新的数据存储单元&#xff0c;当前数据不到上限&#xff0c;那么都可以添加进块。当前数据达到了上限&#xff0c;那么就得分表/分块&#xff0c;超限的那部分数据就需要等待下个区块存储 存储的数据&#xff1a;和mysql一样&#xff0c;文本数据直接存储…

【ceph】在虚拟环境中需要给osd所在的虚拟盘扩容操作

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

CAD点划线如何绘制?

CAD作为一款专业的制图软件&#xff0c;应用范围非常广泛&#xff0c;在所有需要精准数据的行业都会用到它。其中&#xff0c;公路交通工程等行业应用CAD软件是最多的&#xff0c;而且在这些行业中CAD软件中的线段应用也是最广泛的&#xff0c;其中的点划线在公路设计中用的是最…

MySQL一主一从读写分离

​ MySQL主从复制 一、主从复制概念 主从复制是指将主数据库的DDL和DML操作通过二进制日志传到从服务器中&#xff0c;然后在从服务器上对这些日志重新执行也叫重做&#xff0c;从而使得从数据库和主库的数据保持同步。 MySQL支持一台主库同时向多台从库进行赋值&#xff0c;从…

恒通未来-大数据传输中的WDM解决方案

DWDM的出现是光纤传输技术发展中最新的重要现象之一。本教程将介绍DWDM技术的基本原理&#xff0c;如组件、DWDM系统中使用的光放大器等。 组件和操作&#xff1a; DWDM是光传输网络中的一种核心技术。DWDM的基本组件可以根据其在系统中的位置进行分类。在发射方面&#xff0…

ChatGPT:人工智能与人类交流的桥梁

在人工智能的浪潮中&#xff0c;ChatGPT以其独特的交流能力成为了一个亮点。作为一个基于强大的GPT-4模型的聊天机器人&#xff0c;ChatGPT不仅仅是技术的展示&#xff0c;它更是人工智能与人类交流的桥梁。 人工智能的语言理解革命 ChatGPT的出现标志着人工智能在语言理解和…

ADI官方例程HDL和No-OS生成Vivado工程

源码下载 1、HDL(hdl-hdl_2019_r2.zip)&#xff1a;https://github.com/analogdevicesinc/hdl/tree/main 2、No-OS(no-OS-2019_R2.zip)&#xff1a;https://github.com/analogdevicesinc/no-OS/tree/main 软件工具 序号名称说明1Vivado 2019.12Cygwin Cygwin安装 1.安装步…

学生服务器是什么?推荐几款学生专享便宜服务器

随着科技的飞速发展&#xff0c;互联网已经成为我们生活中不可或缺的一部分。对于学生来说&#xff0c;拥有一个自己的服务器&#xff0c;不仅可以提供稳定、高效的网络服务&#xff0c;更可以成为探索和学习云计算、大数据等前沿技术的平台。本文将为学生朋友们推荐几款专享的…

好习惯修炼法则-1

2024年1月12日--读书笔记--好习惯修炼法则&#xff1a;《好习惯修炼法则&#xff1a;用3分钟改善你的生活习惯》([日]桦泽紫苑)【摘要 书评 试读】- 京东图书 (jd.com) 养成好的睡眠习惯&#xff0c;不仅可以提升免疫力&#xff0c;而且可以提高工作效率&#xff01;当有工作任…

vue前端开发自学,插槽练习第二次,name属性的使用

vue前端开发自学,插槽练习第二次,name属性的使用!可以使用name属性&#xff0c;来自定义一个名字&#xff0c;这样&#xff0c;就可以在一个组件内同时出现多个插槽的内容了。在子组件内接收的时候&#xff0c;很简答&#xff0c;只需要在slot标签里面加上name“mz”&#xff1…

云服务器十大服务商——云服务器哪家好用

云服务器哪家便宜&#xff1f;2024最新整理你要的都在这&#xff01;头部云厂商阿里云、腾讯云、华为云、京东云、UCloud等都在降价&#xff0c;阿腾云atengyun.com分享2024年云服务器租用价格给你惊喜&#xff01; 便宜云服务器阿里云腾讯云华为云 2024年便宜云服务器汇总&…

ssm基于Javaweb的网上花店系统的设计与实现论文

摘 要 进入21世纪网络和计算机得到了飞速发展&#xff0c;并和生活进行了紧密的结合。目前&#xff0c;网络的运行速度以达到了千兆&#xff0c;覆盖范围更是深入到生活中的角角落落。这就促使网上购物系统的发展。网上购物可以实现远程购物&#xff0c;远程选择喜欢的商品和随…

3万字数据结构与算法学习笔记+知识点总结

文章目录 数据结构与算法排序排序算法常见排序算法复杂度冒泡排序&#xff08;Bubble Sort&#xff09;选择排序&#xff08;Selection Sort&#xff09;插入排序&#xff08;Insertion Sort&#xff09;希尔排序&#xff08;Shell Sort&#xff09;堆排序&#xff08;Heap Sor…

协作共生:数字孪生与智慧城市的共赢之路

引言 随着科技的飞速发展&#xff0c;数字孪生和智慧城市的概念逐渐融入现代城市的规划和建设中。数字孪生技术为智慧城市的建设提供了强大的支持&#xff0c;而智慧城市则为数字孪生的应用提供了广阔的舞台。本文将深入探讨数字孪生与智慧城市之间的相互影响与协作&#xff0…

【QT】标准对话框

目录 1 概述 2 QFileDialog对话框 1.选择打开一个文件 2.选择打开多个文件 3&#xff0e;选择已有目录 4&#xff0e;选择保存文件名 3 QColorDialog对话框 4 QFontDialog对话框 5 QInputDialog标准输入对话框 1.输入文字 2&#xff0e;输入整数 3&#xff0e;输入…

Sublime Text 3配置 Python 开发环境

Sublime Text 3配置 Python 开发环境 一、引言二、主要内容1. 初识 Sublime Text 32. 初识 Python2. 接入 Python2.1 下载2.2 安装和使用 python2.2 环境变量配置 3. 配置 Python 开发环境4. 编写 Python 代码5. 运行 Python 代码 三、总结 一、引言 Python 是一种简洁但功能强…

用 docker 创建 jmeter 容器, 实现性能测试

前言 我们都知道&#xff0c;jmeter可以做接口测试&#xff0c;也可以用于性能测试&#xff0c;现在企业中性能测试也大多使用jmeter。docker是最近这些年流行起来的容器部署工具&#xff0c;可以创建一个容器&#xff0c;然后把项目放到容器中&#xff0c;就可以构建出一个独立…

element+vue 之图片放大器

1.安装插件 npm install vue-photo-zoom-pro2.main.js导入 // 放大镜 import VuePhotoZoomPro from vue-photo-zoom-pro Vue.use(VuePhotoZoomPro)3.页面使用 <vue-photo-zoom-pro:url"imgUrl":out-zoomer"true":scale"2"style"width:…

SQL-分页查询and语句执行顺序

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…
最新文章