如果你正在運(yùn)行一個(gè)服務(wù)器,假設(shè)服務(wù)器需要從磁盤讀取一些文件,比如證書或密鑰。證書經(jīng)常會(huì)發(fā)生變化,因此你的服務(wù)器必須重新加載它們。如何告訴服務(wù)器重新加載這些文件?
傳統(tǒng)的方法是使用Unix信號(hào),你的服務(wù)器偵聽特定的信號(hào),如SIGUSR1(用戶定義的信號(hào)#1)或SIGHUP(掛起信號(hào)),并且可以在接收到信號(hào)時(shí)執(zhí)行你編寫的任何代碼。因此,你的服務(wù)器等待適當(dāng)?shù)男盘?hào),接收它,然后重新加載證書。
這種方法工作得很好,但是在實(shí)際應(yīng)用中出現(xiàn)了一些可用性的問題。使用單獨(dú)的一個(gè)http服務(wù)器來處理信號(hào)會(huì)更好。
下面我們先來看一下使用Unix信號(hào)的例子,然后我們使用服務(wù)器處理信號(hào)來改進(jìn)這個(gè)例子。
首先,我們先創(chuàng)建一個(gè)Rust項(xiàng)目:
cargo new signals-servers
在Cargo.toml文件中加入以下依賴項(xiàng):
[dependencies]axum = "0.7.2"tokio = { version = "1.25.0", features = ["macros", "rt-multi-thread", "signal"] }
在項(xiàng)目根目錄下創(chuàng)建一個(gè)cert.pem文件,內(nèi)容隨便寫,只是為了演示。
我們看一個(gè)完整的服務(wù)器偵聽信號(hào)的示例,當(dāng)你啟動(dòng)你的服務(wù)器時(shí),也啟動(dòng)一個(gè)異步任務(wù)(或進(jìn)程,或線程)來監(jiān)聽這個(gè)信號(hào),當(dāng)接收到信號(hào)時(shí),重新加載證書。
創(chuàng)建一個(gè)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!("已加載證書,正在啟動(dòng)web服務(wù)器"); println!("PID: {}", process::id()); tokio::select! { _ = start_normal_server(8080) => { println!("Web服務(wù)器關(guān)閉") } _ = listen_for_reload(SignalKind::hangup()) => { println!("信號(hào)監(jiān)聽器停止") } }}async fn start_normal_server(port: u32) { // 構(gòu)建我們的應(yīng)用程序 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> { // 監(jiān)聽信號(hào) let mut stream = signal(signal_kind)?; loop { stream.recv().await; match std::fs::read_to_string("cert.pem") { Ok(_) => eprintln!("重新加載證書成功"), Err(e) => eprintln!("無(wú)法重新加載證書: {e}"), } }}
運(yùn)行如下命令啟動(dòng)服務(wù)器:
cargo run --bin unix_signal已加載證書,正在啟動(dòng)web服務(wù)器PID: 41945
然后打開一個(gè)新的終端,輸入以下命令:
kill -s sighup 41945
這是服務(wù)器的日志如下:
已加載證書,正在啟動(dòng)web服務(wù)器PID: 41945重新加載證書成功
這是可行的,但對(duì)于發(fā)送信號(hào)的人來說,這不是一個(gè)很好的用戶體驗(yàn)。假設(shè)你是SRE或系統(tǒng)管理員,當(dāng)需要重新加載服務(wù)器證書時(shí),首先查找進(jìn)程的PID,并使用kill -s sighup pid發(fā)送信號(hào)。
服務(wù)器可能重新加載了,但也許它沒有,可能出現(xiàn)了錯(cuò)誤,例如新證書無(wú)效,或者服務(wù)器沒有讀取新證書的權(quán)限。系統(tǒng)管理員如何知道是否發(fā)生了這種情況?他們應(yīng)該檢查一下服務(wù)器的日志,但這需要切換窗口,或者打開一個(gè)不同的程序。
這不是一個(gè)很好的用戶體驗(yàn)。通常,當(dāng)你運(yùn)行命令時(shí),希望得到一些反饋。但是當(dāng)你發(fā)送Unix信號(hào)時(shí),終端不會(huì)給你任何響應(yīng)。你必須查找服務(wù)器的日志并檢查它們,以確保重新加載成功完成。閱讀一個(gè)不熟悉的程序日志是很困難的,特別是當(dāng)日志中有很多其他錯(cuò)誤時(shí)。
Unix信號(hào)的主要問題是,它們讓用戶向進(jìn)程發(fā)出信號(hào),但程序不向用戶發(fā)送響應(yīng)。
因此,我們希望進(jìn)程接受請(qǐng)求(“重新加載您的證書”),并響應(yīng)(“是的,它成功了”或“它失敗了,原因如下”)。這聽起來很熟悉——它只是一個(gè)普通的請(qǐng)求-響應(yīng)協(xié)議。沒有必要重新發(fā)明輪子——我們可以在這個(gè)過程中啟動(dòng)第二個(gè)小HTTP服務(wù)器。
創(chuàng)建一個(gè)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!("已加載證書,正在啟動(dòng)web服務(wù)器"); tokio::select! { _ = start_normal_server(8080) => { println!("Web服務(wù)器關(guān)閉") } _ = start_control_server(3000) => { println!("信號(hào)服務(wù)器關(guān)閉") } }}async fn start_normal_server(port: u32) { // 構(gòu)建我們的應(yīng)用程序 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) { // 構(gòu)建信號(hào)控制服務(wù)器 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!("無(wú)法重新加載證書: {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();}
對(duì)于SRE或系統(tǒng)管理員來說,這是一個(gè)更好的用戶體驗(yàn)。使用如下命令重新加載證書:
$ curl -X POST 0.0.0.0:3000/reload_certs重新加載證書成功%
如果沒有找到證書,會(huì)立即得到有關(guān)錯(cuò)誤的反饋:
$ curl -X POST 0.0.0.0:3000/reload_certs無(wú)法重新加載證書: No such file or directory (os error 2)
如果你的程序不需要HTTP或網(wǎng)絡(luò),那么引入一個(gè)完整的HTTP框架來監(jiān)聽信號(hào)可能有點(diǎn)多余。因此,根據(jù)程序的大小,以及系統(tǒng)管理員的需求或SRE團(tuán)隊(duì)的大小,來決定是否添加HTTP服務(wù)器,因?yàn)檫@對(duì)于管理流程的人員和軟件來說,它有更好的用戶體驗(yàn)。
本文鏈接:http://www.tebozhan.com/showinfo-26-80844-0.htmlRust中的信號(hào)處理:Unix信號(hào) vs 信號(hào)服務(wù)器
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com