PowerJob 的自實現高可用方案,妙妙妙

本文適合有 Java 基礎知識的人羣

PowerJob 的自實現高可用方案,妙妙妙

作者:HelloGitHub-Salieri

HelloGitHub 推出的《講解開源項目》系列。

碎碎念

高可用放到今天已經不是一個新穎的詞彙了,怎麼實現高可用大家也已經瞭然於心。多實例部署 + 服務註冊 + 服務發現這一套組合拳打下來,實現高可用那還不是分分鐘的事情。所以很多人看到 PowerJob 的介紹頁面中寫了任意組件支持集羣部署以實現高可用,想當然的以為也是走了上述的那套流程。然後看到系統依賴組件時,發現......emmm...... zookeeper 呢?沒看着。那找找 Nacos ?emmm......也沒找着......不僅沒找着,還發現文檔中明明白白的寫着,最小依賴僅為關係型數據庫。許多用户看到這裏就有點百思不得其解了,正常來講都會有兩個疑惑。

PowerJob 的自實現高可用方案,妙妙妙

首先,為什麼不用註冊中心呢?

要做到分佈式環境下的高可用,肯定是需要服務註冊、服務發現這樣的概念的。沒有外部註冊中心,説白了就是自己去實現了一套類似的機制。那為什麼要怎麼做呢?

其實答案很簡單——成本。這個成本指的是用户的接入成本。對於一個需要部署的重型開源項目來説,每少一個外部依賴,就多一份潛在的用户。額外的系統依賴代表着額外的技術棧和額外的維護成本,如果企業本身沒有這一套技術體系(比如沒用到 zookeeper),而 PowerJob 又強依賴 zookeeper,那大概率只能説再見嘍~

第一個問題解決了,接下來進入第二個問題~

PowerJob 的自實現高可用方案,妙妙妙
簡單高“可用”

PowerJob 系統中的基礎組件為調度服務器 server 和執行器 worker,server 負責調度定時任務,並派發到 worker 執行,是一個典型的 C/S 架構。

C/S 架構下,如果目標是 server 和 client 可以相互聯通的“高可用”,那麼實現起來其實非常容易。

首先,啓動多個 server 應用實例,集羣部署。然後將多個 server 的 IP 地址統統填入 worker 的配置文件中,worker 啓動時,隨機找一個 IP 進行連接,失敗則重試。一旦成功連接到某一台 server,就開始上報自己的地址信息。server 通過持有這個信息也可以和 worker 進行通訊。如此一來,一個最簡單版本的“高可用”集羣就搭建完成了。但是......它真的可用嗎?

PowerJob 的自實現高可用方案,妙妙妙

答案顯然是否定的(否則也不會有這篇文章了是不是~)。以上方案主要存在兩個問題:

  1. 任務調度需要保證唯一性,即某個任務在某一個時刻只能被一台機器調度,否則就會導致重複執行。而前文提及的方案中,每一台 server 都是完全等價的,因此只能依靠分佈式鎖來保證唯一性,即搶到鎖的 server 執行調度,其他 server 只能充當戰地記者,默默地邊緣 OB。這種方案下,無論部署多少台 server,系統整體的調度性能其實是固定的,多實例部署只能做到高可用,而不能做到高性能。
  2. server 無法持有完整的 worker 集羣信息。PowerJob 的定位是任務調度中間件,旨在為企業下各部門各業務線提供精準的調度和分佈式計算能力。因此肯定會有集羣分組的概念,就像 RocketMQ 中存在 ProducerGroup 和 ConsumerGroup 一樣,PowerJob 有着 AppName 的概念。一個 AppName 邏輯上對應了某個應用下的一組任務,物理上對應了這個應用所部署的集羣。為了便於 server 統一管理以及一些額外功能的實現(分佈式計算),server 持有某一個 AppName 下完整的集羣信息是一個強訴求,而前文提及的“瞎貓撞上死耗子”式方案,顯然沒辦法做到這一點。

基於以上兩點,征途是星辰大海的 PowerJob 需要探索出一種更合理、更強大的高可用架構。

分組隔離

其實根據前面遇到的問題,這一套機制的雛形也差不多出來了。

server 既然需要持有某一個分組下完整的集羣信息,那麼可以順其自然的想到,能不能讓某一個分組的所有 worker 都連接到某一台 server 呢?一旦某個分組下所有機器全部連接到了某一台 server,那麼其實這就形成了一個小型的子系統。雖然整個 PowerJob 系統中存在着多台 server 和多個 worker 集羣,但是對於這個分組的運行來説,只要有這個分組對應的 worker 集羣以及它們連接的那一台 server 就夠了。那麼在這個小型“子系統”內部,只存在着一台 server,也就不存在重複調度問題了(server 只調度連接到它的 AppName 下面的任務就能實現這一點)。

所以,經過一層層的剝絲抽繭,問題已經轉化為了:如何讓某個分組下的所有機器都連接到同一台 server 上去呢?

看到這個問題的時候,相信很多人會有和我當時一樣的想法,那就是:就這?

PowerJob 的自實現高可用方案,妙妙妙

“讓所有機器都連接到同一台 server 上去,那也太簡單了吧,你只配置一個 IP 不就行了嗎?”

“配置一個 IP 怎麼做高可用,怎麼利用多台 server 資源?”

“好像有點道理,那就 hash(appName) 取餘作為下標,這樣就能保證同一個同一個分組下所有機器的初始 IP 相同,不同分組也能連接到不同的 server”

“那,萬一連接的 server 掛了怎麼辦?”

“這好辦,可以採取類似於解決哈希衝突的那個什麼開放定址法,從掛掉的 server 下標開始,依次向後重試就行了,同一個分組集羣內所有的機器都從某個下標依次向後重試,還是能連接到同一台 server 的”

“好像很有道理,哼,worker 選主也就不過如此,方案搞定,英雄聯盟 啓動!”

正當我浴血奮戰直指敵將首級時,畫面...永遠定格在了見血前的那一瞬。“正在嘗試重新連接”幾個大字映入眼簾,也把我帶入了深深的沉思。

PowerJob 的自實現高可用方案,妙妙妙

雖説每次玩遊戲必罵騰訊那***的土豆服務器,但罵歸罵,心裏其實還是明白,大部分情況下都是自己網絡波動導致遊戲掉線(誰叫我貪便宜辦了個移動寬帶呢,哎,雷電法王楊永信也就圖一樂,真要戒網癮還得看移動寬帶)。

嗯?自己的原因?網絡波動?掉線?重連?這一連串詞彙,把我拉回了剛剛設計的方案之中,然後給我當頭一棒。一旦 worker 因為自己的網絡波動導致它以為 server 不可用,而重新連接另一台 server,就會導致所有 worker 都連接同一台 server 這個約束被破壞......因此,這個方案自然也就是一個充滿漏洞的不可行方案。

PowerJob 的自實現高可用方案,妙妙妙

在這之後的一週,可以説是支離破碎的一週。為了解決這個問題,我設計了無數個堪稱“奇珍異獸”的方案,然後再一個個否定和槍斃。

其實這段經歷現在回過頭來想特別搞笑,也有被自己蠢到。那無數個方案的失敗原因其實都是同一個,也就是出發點錯了。我一直在嘗試讓 worker 決定連接哪台 server,卻一而再再而三忽略 worker 永遠不可能獲取 server 真正的存活信息(比如心跳無法傳達,可能是 worker 本身的網絡故障),因此 worker 不應該決定連接哪台 server,這應該由 server 來決定。worker 能做的,只有服務發現。想明白了這點,具體的方案也就應運而生了。

PS:這個方案的誕生,我大概付出了1斤腦細胞的代價(不得不説這個減肥方法還蠻好的)...腦細胞不能白死,儘管那些奇奇怪怪得方案沒有活到正式版本,但沒有他們就無法通往真理的大門。為了表達紀念和“哀悼”之情,我將最終的設計命名為——V4:喪鐘為誰鳴。

PowerJob 的自實現高可用方案,妙妙妙
V4:喪鐘為誰鳴

想明白了不能由 Worker 發起 Server 的重新選舉,這個問題就基本上解決了......由於篇幅原因以及網上已經有小夥伴寫了這一塊源碼分析的博客,我這裏就不重複“造輪子”了,在這裏主要講一下設計思路。

就像前面説的那樣,worker 因為沒辦法獲取 server 的準確狀態,所以不能由 worker 來決定連接哪一台 server。因此,worker 需要做的,只是服務發現。即定時使用 HTTP 請求任意一台 server,請求獲取當前該分組(appName)對應的 server。

而 server 收到來自 worker 的服務發現請求後,其實就是進行了一場小型的分佈式選主:server 依賴的數據庫中存在着 server_info 表,其中記錄了每一個分組(appName)所對應的 server 信息。如果該 server 發現表中存在記錄,那就説明該 worker 集羣中已經有別的 worker 事先請求 server 進行選舉,那麼此時只需要發送 PING 請求檢測該 server 是否存活。如果發現該 server 存活,那麼直接返回該 server 的信息作為該分組的 server。否則就完成篡位,將自己的信息寫入數據庫表中,成為該分組的 server。

細心的小夥伴可能又要問了?發送 PING 請求檢測該 server 是否存活,不還是有和剛才一樣的問題嗎?請求不同,發送方和接收方都有可能出問題,憑什麼認為是原先的 server 掛了呢?

確實,在這個方案下,依舊沒辦法解決 server 到底掛沒掛這個堪比“真假美猴王”的玄學問題。但是,這還重要嗎?我們的目標是某個分組下所有的 worker 都連接到同一台 server,因此,即便產生那種誤打誤撞篡位的情況,在服務發現機制的加持下,整個集羣最終還是會連接到同一台 server,完美實現我們的需求。

至此,耗時 6 天,從原來的懷疑人生,到完美方案的落地實現,真是曲折~

PowerJob 的自實現高可用方案,妙妙妙
最後

最後,貼上兩位小夥伴貢獻的源碼分析文章,我親自 check 過,沒有質量問題(這話説的我感覺自己好飄哈哈哈),請各位觀眾老爺放心查看~

  • PowerJob源碼分析-分組隔離設計
  • PowerJob源碼解讀1:Server和Worker之間的通信解讀

那麼以上就是本篇文章全部的內容了~相信通過這篇文章和上篇文章,大家已經對 PowerJob 的調度層和高可用高性能架構有了一定的瞭解了。接下來就是下期預告環節了~

為了保留神秘感,這次就選擇不預告了(才不會告訴你是我還沒想好具體寫什麼)~

所有驚喜,下期再見~

版權聲明:本文源自 網絡, 於,由 楠木軒 整理發佈,共 4148 字。

轉載請註明: PowerJob 的自實現高可用方案,妙妙妙 - 楠木軒