服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - Rust中的信号处理:Unix信号 vs 信号服务器

Rust中的信号处理:Unix信号 vs 信号服务器

2024-04-02 14:38coding到灯火阑珊 编程技术

传统的方法是使用Unix信号,你的服务器侦听特定的信号,如SIGUSR1(用户定义的信号#1)或SIGHUP(挂起信号),并且可以在接收到信号时执行你编写的任何代码。因此,你的服务器等待适当的信号,接收它,然后重新加载证书。

如果你正在运行一个服务器,假设服务器需要从磁盘读取一些文件,比如证书或密钥。证书经常会发生变化,因此你的服务器必须重新加载它们。如何告诉服务器重新加载这些文件?

传统的方法是使用Unix信号,你的服务器侦听特定的信号,如SIGUSR1(用户定义的信号#1)或SIGHUP(挂起信号),并且可以在接收到信号时执行你编写的任何代码。因此,你的服务器等待适当的信号,接收它,然后重新加载证书。

这种方法工作得很好,但是在实际应用中出现了一些可用性的问题。使用单独的一个http服务器来处理信号会更好。

下面我们先来看一下使用Unix信号的例子,然后我们使用服务器处理信号来改进这个例子。

首先,我们先创建一个Rust项目:

cargo new signals-servers

在Cargo.toml文件中加入以下依赖项:

[dependencies]
axum = "0.7.2"
tokio = { version = "1.25.0", features = ["macros", "rt-multi-thread", "signal"] }

在项目根目录下创建一个cert.pem文件,内容随便写,只是为了演示。

Unix信号处理

我们看一个完整的服务器侦听信号的示例,当你启动你的服务器时,也启动一个异步任务(或进程,或线程)来监听这个信号,当接收到信号时,重新加载证书。

创建一个src/bin/unix_signal.rs文件,代码如下:

use axum::{routing::get, Router};
use std::process;
use tokio::signal::unix::{signal, SignalKind};

#[tokio::main]
async fn main() {
    let _cert = std::fs::read_to_string("cert.pem");
    println!("已加载证书,正在启动web服务器");
    println!("PID: {}", process::id());
    tokio::select! {
        _ = start_normal_server(8080) => {
            println!("Web服务器关闭")
        }
        _ = listen_for_reload(SignalKind::hangup()) => {
            println!("信号监听器停止")
        }
    }
}

async fn start_normal_server(port: u32) {
    // 构建我们的应用程序
    let app = Router::new().route("/hello", get(|| async { "Hello, world!" }));

    let addr = format!("127.0.0.1:{port}");
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn listen_for_reload(signal_kind: SignalKind) -> Result<(), std::io::Error> {
    // 监听信号
    let mut stream = signal(signal_kind)?;

    loop {
        stream.recv().await;

        match std::fs::read_to_string("cert.pem") {
            Ok(_) => eprintln!("重新加载证书成功"),
            Err(e) => eprintln!("无法重新加载证书: {e}"),
        }
    }
}

运行如下命令启动服务器:

cargo run --bin unix_signal

已加载证书,正在启动web服务器
PID: 41945

然后打开一个新的终端,输入以下命令:

kill -s sighup 41945

这是服务器的日志如下:

已加载证书,正在启动web服务器
PID: 41945
重新加载证书成功

这是可行的,但对于发送信号的人来说,这不是一个很好的用户体验。假设你是SRE或系统管理员,当需要重新加载服务器证书时,首先查找进程的PID,并使用kill -s sighup pid发送信号。

服务器可能重新加载了,但也许它没有,可能出现了错误,例如新证书无效,或者服务器没有读取新证书的权限。系统管理员如何知道是否发生了这种情况?他们应该检查一下服务器的日志,但这需要切换窗口,或者打开一个不同的程序。

这不是一个很好的用户体验。通常,当你运行命令时,希望得到一些反馈。但是当你发送Unix信号时,终端不会给你任何响应。你必须查找服务器的日志并检查它们,以确保重新加载成功完成。阅读一个不熟悉的程序日志是很困难的,特别是当日志中有很多其他错误时。

Unix信号的主要问题是,它们让用户向进程发出信号,但程序不向用户发送响应。

更好的方法:信号服务器

因此,我们希望进程接受请求(“重新加载您的证书”),并响应(“是的,它成功了”或“它失败了,原因如下”)。这听起来很熟悉——它只是一个普通的请求-响应协议。没有必要重新发明轮子——我们可以在这个过程中启动第二个小HTTP服务器。

创建一个src/bin/http_signal.rs文件,代码如下:

use axum::{
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Router,
};

#[tokio::main]
async fn main() {
    let _cert = std::fs::read_to_string("cert.pem");
    println!("已加载证书,正在启动web服务器");

    tokio::select! {
        _ = start_normal_server(8080) => {
            println!("Web服务器关闭")
        }
        _ = start_control_server(3000) => {
            println!("信号服务器关闭")
        }
    }
}

async fn start_normal_server(port: u32) {
    // 构建我们的应用程序
    let app = Router::new().route("/hello", get(|| async { "Hello, world!" }));

    let addr = format!("127.0.0.1:{port}");
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn start_control_server(port: u32) {
    // 构建信号控制服务器
    let app: Router = Router::new().route(
        "/reload_certs",
        post(|| async {
            println!("重新加载证书");
            match std::fs::read_to_string("cert.pem") {
                Ok(_) => "重新加载证书成功".into_response(),
                Err(e) => {
                    let error = format!("无法重新加载证书: {e}");
                    eprintln!("{error}");
                    let resp = (StatusCode::INTERNAL_SERVER_ERROR, error);
                    resp.into_response()
                }
            }
        }),
    );

    let addr = format!("127.0.0.1:{port}");
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

对于SRE或系统管理员来说,这是一个更好的用户体验。使用如下命令重新加载证书:

$ curl -X POST 0.0.0.0:3000/reload_certs
重新加载证书成功%

如果没有找到证书,会立即得到有关错误的反馈:

$ curl -X POST 0.0.0.0:3000/reload_certs
无法重新加载证书: No such file or directory (os error 2)

总结

如果你的程序不需要HTTP或网络,那么引入一个完整的HTTP框架来监听信号可能有点多余。因此,根据程序的大小,以及系统管理员的需求或SRE团队的大小,来决定是否添加HTTP服务器,因为这对于管理流程的人员和软件来说,它有更好的用户体验。

原文地址:https://mp.weixin.qq.com/s/DMrs2b_Yfs9T9YivXP5cqg

延伸 · 阅读

精彩推荐
  • 编程技术Web 现代应用程序架构下的性能优化,渐进式的极致艺术

    Web 现代应用程序架构下的性能优化,渐进式的极致艺术

    本文是谷歌工程师带来的现代应用架构体系下的优化相关演讲的总结,演讲介绍了以下优化手段。...

    前端从进阶到入院11262021-02-02
  • 编程技术看一遍就理解:零拷贝详解

    看一遍就理解:零拷贝详解

    做服务端开发的小伙伴,文件下载功能应该实现过不少了吧。如果你实现的是一个web程序,前端请求过来,服务端的任务就是:将服务端主机磁盘中的文件...

    捡田螺的小男孩11332024-03-12
  • 编程技术搞懂二叉堆的那些事

    搞懂二叉堆的那些事

    我们在日常生活中,通常会说“一堆东西”或者“堆东西”,这里的“堆”,通常指重叠放置的许多东西。 ...

    二十二画程序员9852021-05-07
  • 编程技术Steam 增加了对 Mesa 着色器单文件缓存的支持

    Steam 增加了对 Mesa 着色器单文件缓存的支持

    上个月,Valve 开发者为 Mesa 增加了一个新的 "单文件 "缓存选项,以替代现有的多文件缓存。现在,最新的 Steam for Linux 测试版已经支持这个新的单文件缓存...

    开源中国11152021-03-19
  • 编程技术前端视角对Rust的浅析

    前端视角对Rust的浅析

    随着前端开发越来越卷,前端基建的效率也将是下一个值得投入的地方。并且随着 Rust 和 WebAssembly 发展,网页应用也有很大的发展空间。最后希望在深入学...

    大转转FE4422024-02-28
  • 编程技术六种常见负载均衡算法

    六种常见负载均衡算法

    今天我们来聊聊常见的负载均衡算法。负载均衡将网络流量或一组任务以某种算法合理分配给各个处理节点,使得节点得到平等的使用,并及时可靠地返回...

    ByteByteGo10812023-11-28
  • 编程技术VSCode常用知识小结

    VSCode常用知识小结

    本文给大家汇总介绍了vscode的下载,安装,常用插件以及常用的快捷键,非常的详细,希望对大家使用vscode能够有所帮助 ...

    Yaphets.Lee4392020-08-23
  • 编程技术一篇带给你 Serverless 云开发高阶应用

    一篇带给你 Serverless 云开发高阶应用

    在 FaaS 平台中,函数默认是不运行的,也不会分配任何资源,甚至 FaaS 中都不会保存函数代码。只有当 FaaS 接收到触发器的事件后,才会启动并运行函数。...

    勾勾的前端世界4792021-04-19