18. 從零開始編寫一個類nginx工具, 主動式健康檢查源碼實現

来源:https://www.cnblogs.com/wmproxy/archive/2023/10/26/wmproxy18.html
-Advertisement-
Play Games

wmproxy將用Rust實現http/https代理, socks5代理, 反向代理, 靜態文件服務,講述的是主動式健康檢查可帶來的好處 ...


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個請求失敗,如果是重要的數據,我們會丟失很多重要數據。
  如果此時客戶端擁有重試機制,那麼客戶端在失敗的時候會發起重試,而且系統可能會反覆的分配到那台不可達的系統,將會造成短時間內請求數激增,可能引發系統的雪崩。
  所以此時我們主動知道目標端的系統穩定性極其重要。

網路訪問示意圖

以下是沒有主動健康檢查

sequenceDiagram 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,節點的宕機會對系統的穩定性產生較大的影響

以下是主動健康檢查,它保證了訪問後端伺服器組均是正常的狀態

sequenceDiagram 客戶端->>代理伺服器: 請求數據(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::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(())
}

結語

主動檢查可以及時的更早的發現系統中不穩定的因素,是系統穩定性的基石,也可以通過更早的發現因素來通知運維介入,我們的目的是使系統更穩定,更健壯,處理延時更少。

點擊 [關註][在看][點贊] 是對作者最大的支持


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. maven打包方式 maven打包有三種方式 pom、jar、war。在pom.xml 文件中聲明的方式分別如下: <!-- 1. pom方式 --> <packaging>pom</packaging>` <!-- 2. jar方式 --> <packaging>jar</packaging ...
  • 不,代碼是值錢的! 前幾天我們一直服務的一個客戶覺得自己用了兩三年的UI太醜,乞求我們換一套。集團領導討論後一口報價30w,牛逼哄哄說:很麻煩的啊,要先設計UI庫,然後把所有頁面都換個樣,又要測試這玩意(內行人都明白前端能測出啥bug,也就可能要考慮優化),大概要6個人做一個月。 然後我這架構大頭兵 ...
  • 說明 介紹 該腳本使用Selenium庫來實現自動登錄併在指定的時間購買商品。 運行前準備 mac 的safari瀏覽器本身已經集成了safaridriver,只要啟用並開啟即可,步驟如下: 終端啟用safaridriver: sudo safaridriver --enable 嘗試運行safra ...
  • 在查找二叉樹某個節點時,如果把二叉樹所有節點理理解為解空間,待找到那個節點理解為滿足特定條件的解,對此解答可以抽象描述為: _在解空間中搜索滿足特定條件的解_,這其實就是搜索演算法(Search)的一種描述。當然也有其他描述,比如是“指一類用於在數據集合中查找特定項或解決問題的演算法”,又或者是“指通過... ...
  • 相信大家對python-docx這個常用的操作docx文檔的庫都不陌生,它支持以內聯形狀(Inline Shape)的形式插入圖片,即圖片和文本之間沒有重疊,遵循流動版式(flow layout)。但是,截至最新的0.8.10版本,python-docx尚不支持插入浮動圖片(floating pic ...
  • Bean在Spring中的定義是_org.springframework.beans.factory.config.BeanDefinition_介面,BeanDefinition裡面存儲的就是我們編寫的Java類在Spring中的元數據 ...
  • 前言 最近博主在位元組面試中遇到這樣一個面試題,這個問題也是前端面試的高頻問題,因為在前端開發的日常開發中我們總是會與post請求打交道,一個小小的post請求也是牽扯到很多知識點的,博主在這給大家細細道來。 同源策略 在瀏覽器中,內容是很開放的,任何資源都可以接入其中,如 JavaScript 文件 ...
  • NPCAP 庫是一種用於在`Windows`平臺上進行網路數據包捕獲和分析的庫。它是`WinPcap`庫的一個分支,由`Nmap`開發團隊開發,併在`Nmap`軟體中使用。與`WinPcap`一樣,NPCAP庫提供了一些`API`,使開發人員可以輕鬆地在其應用程式中捕獲和處理網路數據包。NPCAP庫... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...