主頁 > 軟體設計 > 一言不合就重構

一言不合就重構

2022-12-01 07:30:30 軟體設計

hello,大家好呀,我是小樓,

前段時間不是在忙么,忙的內容之一就是花了點時間重構了一個服務的健康檢查組件,目前已經慢慢在灰度線上,本文就來分享下這次重構之旅,也算作個總結吧,

背景

服務健康檢查簡介

服務健康檢查是應對分布式應用下某些服務節點不健康問題的一種解法,如下圖,消費者呼叫提供方集群,通常通過注冊中心獲取提供方的地址,根據負載均衡演算法選取某臺具體機器發起呼叫,

image

假設某臺機器意外宕機,服務消費方不能感知,就會導致流量有損,如果此時有一種檢測服務節點健康狀態并及時剔除的機制,就能大大增加線上服務的穩定性,

原服務健康檢查實作原理

我們是自研的注冊中心,健康檢查也算注冊中心的一部分,其原理很簡單,可分為三個階段:

  • 從注冊中心獲取需要檢查的實體(即地址,由ip、port組成)
  • 對每個地址發起 TCP 建鏈請求,建鏈成功視為健康
  • 對判定為不健康的實體進行摘除,對原不健康現在健康的實體進行恢復,摘除恢復通過呼叫注冊中心提供的介面實作

image

當然這是大致流程,還有不少細節,例如獲取探活實體時一些不需要探活的服務會被排除(如一些基礎服務如MySQL、Redis);為了防止網路抖動導致健康狀態判定有誤,會增加一些判定策略,如連續 N 次建連失敗視為不健康;對不健康實體摘除時也計算了摘除閾值,如一個集群的機器都被判定為不健康,那也不能把它們全摘了,因為此時全摘和不摘差別不大(請求都會報錯),甚至全摘還要承擔風險,考慮集群容量問題,可以設個閾值,如最多只能摘三分之一的機器,

原服務健康檢查存在的問題

1. 容量問題

原組件是物理機時代的產物,當時實體數量并不多,所以最初是單機設計,只部署在一臺物理機上,隨著公司業務發展,實體數量增多,單機達到瓶頸,于是做了一次升級,通過組態檔來指定每個節點的健康檢查任務分片,

image

2. 容災問題

單機就必然存在宕機風險,即使檢查任務已經做了分片,但是寫在配置中,無法動態調配,當某個節點宕機,則它負責的實體健康檢查就會失效,

3.部署效率問題

部署在物理機且分片是寫在配置中,無論是擴容還是機器過保置換,都要修改配置,人為操作效率太低,而且容易出錯,

4. 新需求支持效率問題

隨著云原生時代的邁進,對健康檢查提出了一些新的需求,例如只探埠的聯通性可能不能代表服務的健康程度,甚至公司內還有一些其他不在注冊中心上的服務也想復用這個健康檢查組件的能力,日益增長的需求同原組件沉重的歷史包袱之間存在著不可調和的矛盾,

5. 迭代程序中的穩定性問題

原組件沒有灰度機制,開發了新功能上線是一把梭,如果出問題,就是個大故障,影響面非常廣,

需要解決這么多問題,如果在原基礎上改,穩定性和效率都非常令人頭疼,于是一個念頭油然而生:重構!

技術方案調研

業界常見服務健康檢查方案

在設計新方案前,我們看看業界對于健康檢查都是怎么做的,從兩個角度展開調研,注冊中心的健康檢查和非注冊中心的健康檢查

注冊中心健康檢查

方案 代表產品 優點 缺點
SDK 心跳上報 Nacos 1.x 臨時實體 處理心跳消耗資源過多
SDK 長連接 + 心跳保持 Nacox 2.x 臨時實體、SofaRegistry、Zookeeper 感知快 SDK 實作復雜
集中式主動健康檢查 Nacos 永久實體 無需SDK參與,可實作語意級探活 集中式壓力大時,時延增大

非注冊中心健康檢查

K8S 健康檢查 — LivenessProbe

與集中式健康檢查做對比

LivenessProbe 原健康檢查組件
實作方式 k8s原生,分布式(sidecar模式) 自研,集中式
檢查發起者 kubelet,與業務容器在同一物理機 集中部署的服務
適用范圍 k8s容器(彈性云) 容器、物理機、虛擬機等
支持的檢查方式 tcp、http、exec、grpc tcp、http
健康檢查基本配置 容器啟動延時探活時間、檢查間隔時間、檢查超時時間、最小連續成功數、最小連續失敗數 探活超時時間、連續失敗次數、最大摘除比例
檢測不健康時動作 殺死容器,容器再根據重啟策略決定是否重啟 從注冊中心上摘除
兜底 有,可配摘除比例

結合公司背景進行選型

我們的大背景是技術堆疊不統一,編程語言有 Java、Go、PHP、C++等,基于成本考慮,我們更傾向瘦SDK的方案,

于是注冊中心常見的 SDK 長連接+心跳保持方案被排除,SDK主動上報心跳也不考慮,

而 K8S 的健康檢查方案僅僅使用于 K8S 體系,我們還有物理機,而且 K8S 的 LivenessProbe 并不能做到開箱即用,至少我們不想讓節點不健康時被殺死,兜底策略也需要重新開發,

所以最終我們還是選擇了與原健康檢查組件相同的方案 — 集中式主動健康檢查,

理想態

基于原健康檢查組件在使用中的種種問題,我們總結出一個好的健康檢查組件該有的樣子:

  • 故障自動轉移
  • 可水平擴容
  • 快速支持豐富靈活的需求
  • 新需求迭代,本身的穩定性需要有保障

設計開發

總體設計

組件由四大模塊組成:

  • Dispatcher:負責從資料源獲取資料,生成并派發任務
  • Prober:負責健康檢查任務的執行
  • Decider:根據健康檢查結果決策是否需要變更健康狀態
  • Performer:根據決策結果執行相應動作

各模塊對外暴露介面,隱藏內部實作,資料源面向介面編程,可替換,

服務發現模型

在詳細介紹各個模塊的設計之前,先簡單介紹一下我們的服務發現模型,有助于后續的表述和理解,

一個服務名在公司內是唯一的,呼叫時需指定服務名,獲取對應的地址,

一個服務又可以包含多個集群,集群可以是物理上的隔離集群,也可以是邏輯上的隔離集群,集群下再包含地址,

image

協程模型設計

編程語言我們選擇的是 Go,原因有二:第一是健康檢查這種 IO 密集型任務與 Go 的協程調度比較契合,開發速度,資源占用都還可以;第二是我們組一直用 Go,經驗豐富,所以語言選擇我們沒有太多的考慮,

但在協程模型的設計上,我們做了一些思考,

資料源的獲取,由于服務、集群資訊不經常變化,所以快取在記憶體中,每分鐘進行一次同步,地址資料需要實時拉取,

Dispatcher 先獲取所有的服務,然后根據服務獲取集群,到這里都是在一個協程內完成,接下來獲取地址有網路開銷,所以開 N 個協程,每個協程負責一部分集群地址,每個地址都生成一個單獨的任務,派發給 Prober,

Prober 負責健康檢查,完全是 IO 操作,內部用一個佇列存放派發來的任務,然后開很多協程從佇列中取任務去做健康檢查,檢查完成后將結果交給 Decider 做決策,

Decider 決策時比較重要的是需要算出是否會被兜底,這里有兩點需要考慮:

一是最扯訓取的實體狀態可能不是最新了,需要重新獲取一次;

二是對于同一個集群不能并發地去決策,決策應該串行才不會導致決策混亂,舉個反例,如果一個集群3臺機器,最多摘除1臺,如果2臺同時掛掉,并發決策時,2個協程各自以為能摘,最后結果是摘除了2臺,和預期只摘1臺不符,這個如何解決?我們最后搞了 N 個佇列存放健康檢查結果,按服務+集群的哈希值路由到佇列,保證每個集群的檢測結果都路由到同一個佇列,再開 N 個協程,每個協程消費一個佇列,這樣就做到了順序執行,

決策之后的動作執行就是呼叫更新介面,所以直接共用決策的協程,用一張大圖來總結:

image

水平擴容 & 故障自動轉移

水平擴容與故障自動轉移只要能做到動態地資料分片即可,每個健康檢查組件在啟動時將自己注冊到一個中心的協調器(可以是 etcd),并且監聽其他節點的在線狀態,派發任務時,按服務名哈希,判斷該任務是否應該由自己調度,是則執行,否則丟棄,

image

當某個節點掛掉或者擴容時,每個節點都能感知到當前集群的變化,自動進行資料分片的重新劃分,

小流量機制

小流量的實作采取部署兩個集群的方式,一個正常集群,一個小流量集群,小流量集群負責部分不重要的服務,作為灰度,正常集群負責其他服務的健康檢查任務,

只需要共享一個小流量的配置即可,我們按組織、服務、集群、環境等維度去設計這個配置,基本可以任意粒度配置,

image

可擴展性

可擴展性也是設計里非常重要的一環,可從資料源、檢查方式擴展、過濾器等方面稍微一些,

資料源可插拔

面向介面編程,我們將資料源抽象為讀資料源與寫資料源,只要符合這兩個介面的資料源,就能無縫對接,

檢查方式易擴展

健康檢查其實就是給定一個地址,再加一堆配置去進行檢查,至于怎么檢查可以自己實作,目前已實作的有TCP、HTTP方式,未來還可能會實作諸如Dubbo、gRPC、thrift等的語意級別的檢查方式,

過濾器

在派發任務時,有一個可能會隨時修改的邏輯是過濾掉一些不需要探活的服務、集群、實體,這塊用責任鏈的模式就能很好地實作,后期想增刪就只需要插拔鏈中的一環即可,

可擴展性是代碼層面的內容,所以這里只列舉了部分比較典型的例子,

灰度上線

由于我們是重寫了一個組件來代替原組件,所以上線還挺麻煩,為此我們做了2方面的作業:

  • 設計了一個可按組織、服務、集群、環境等維度的降級開關,降級分為3檔,不降級、半降級、全降級,不降級很好理解,就是啥也不做,全降級就是不作業,相當于一鍵關停健康檢查組件,半降級是只恢復健康但不摘除的一個作業模式,試想如果健康檢查在上執行緒序中,誤摘除,此時降級,豈不是無法恢復健康?所以我們讓它保留恢復能力,
  • 我們利用上述的小流量設計來逐步將服務遷移到新組件上來,灰度的服務新組件負責,非灰度的服務老組件負責,等全部灰度完成,停掉老組件,新組件的灰度集群再切換為正常集群,

踩坑調優

在灰度程序中,我們發現了一個問題,有的一個集群機器非常多,超過了1000臺,而我們的決策是順序執行,而且決策時還會去實時查詢實體狀態,假設每次查詢10ms(已經很快了),1000臺順序決策完也得10s,我們期望每輪的檢測要在3秒左右完成,光這一個集群就得10秒,顯然不能接受,

為了我們做了第一次的優化:

我們當時在線上環境測驗,一個集群有2000多臺機器,但大部分機器是禁用的狀態,也就是這部分機器其實做健康檢查是個無用功,禁用的機器,無論是否健康都不會被消費,所以我們的第一個優化便是在派發任務時過濾掉禁用的機器,這樣就解決了線下環境的問題,

但我們上到生產環境時仍然發現決策很慢,線上一個集群只有少量的機器被禁用,第一次的優化基本就沒什么效果了,而且線上機器數量可能更多,任務堆積會很嚴重,我們發現其他的佇列可能比較空閑,只有大集群所在的佇列很忙,

所以我們進行了第二次優化:

從業務視角出發,其實需要順序決策的只有不健康的實體,對于健康的實體決策時不需要考慮兜底,所以我們將按檢查結果進行分類,健康的檢查結果隨機派發到任意佇列處理,不健康的檢查結果嚴格按服務+集群路由到特定佇列處理,這樣既保證了兜底決策時的順序,也解決了佇列負載不均衡的狀況,

總結

本文從健康檢查的背景,原組件存在的問題,以及我們的理想態出發,調研了業界的方案,結合實際情況,選擇了適合的方案,并總結之前系統的問題,設計一個更加合理的新系統,從開發倍訓到上線,

我覺得系統設計是一個取舍的程序,別人的方案不見得是最優的,適合的才是最好的,而且有時并不是純技術解決問題,可能從業務角度去思考,可能更加豁然開朗,

推薦閱讀

與本文相關的文章也順便推薦給你,如果覺得還不錯,記得關注點贊在看

  • 《如何給注冊中心錦上添花?》
  • 《如何組裝一個注冊中心?》
  • 《服務探活的五種方式》
  • 《4個實驗,徹底搞懂TCP連接的斷開》

  • 搜索關注微信公眾號"捉蟲大師",后端技術分享,架構設計、性能優化、原始碼閱讀、問題排查、踩坑實踐,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/538874.html

標籤:其他

上一篇:(Java)設計模式:創建型

下一篇:Java外包程式員的技術出路

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more