Java 高並發編程之最實用的任務執行架構設計建議收藏
前言
隨著互聯網與軟件的發展,除瞭程序員,架構師也是越來越火的職業。他們伴隨著項目的整個生命過程,他們更像是傳統工業的設計師,將項目當做生命一般細心雕琢。
目前對於項目架構而言,基本都會需要設計的幾個架構。
1、業務架構
項目或者產品的市場定位、需求范圍、作用場景都是需要在項目啟動初期進行系統性分析的。在設計業務架構中,架構師還需要明確角色。我看過很多關於架構的文章,談到角色的很少。
什麼是角色?
例如:商場作為一個整體系統,角色就有消費者、店員、收費員、保安等等。各個角色完成好自己角色所需要承擔的任務,整體系統就能完美的運行。
對應到軟件系統中,根據產品的定位和需求,也會有著對照的角色,比如:用戶、數據審核者、產品制作者、運維人員等。在項目啟動初期,架構師需要對項目中的每個角色做好職責定位,我相信在這點上,大部分開發同學在工作中,或多或少都有過職責不明確帶來的困擾。
2、技術架構
在軟件項目研發過程中,我們會用到許多外部組件。在使用組件中,架構師必須結合業務需求合理的選擇各個組件。項目是個生命,她會成長,架構師需要明白如果一開始就選擇重量級組件會讓還是個孩童的項目不受重負,架構師也需要明白如果技術架構的設計不具備拓展性,那麼這個孩子無法茁壯成長。所以技術架構尤為重要。
3、物理架構
物理架構又叫做部署架構,項目產品如果要在生產環境穩定運行,一個穩定又高效的物理架構是必不可少的。而且往往物理架構和技術架構是相輔相成的,性能監控、異常告警、業務日志等等設計,都是為瞭讓項目做更好的自己。
高並發任務執行架構
在我十年的工作中,業務相關、中間件、大數據都有做過。本文主要分享一下高並發任務執行框架設計,會由淺入深的講述一下設計演化過程。如果你不隻是想做業務後端開發,那麼本文會給你一個全新的視野。
需求場景
我們列一下該項目的需求場景,看看工作中是否遇到過。
1、有個復雜的數據需要制作,而且制作的時間很長,無法讓請求方持續等待。所以請求方隻能給你個回調地址,需要你完成這個制作後將產物通知他。
2、復雜的制作過程需要消耗資源,而且資源有限,無法無限量提供。如果你有接觸過AI,就會比較瞭解資源有限的感受。除瞭ASR、TTS這類識別類型的AI功能能做到近實時的反饋,大部分的算法在運行的時候都會消耗整張顯卡,而且耗時很長。
初看場景,很多後端可能會第一時間想到elastic-job(一個分佈式任務調度框架)。即便你熟悉使用elastic-job,一開始就選擇重框架是不是有種殺雞用牛刀的感覺。不著急,我們一步步分析,一步步設計。
業務架構設計
高度抽象一下我們的業務,對產品設計者而言,貌似是個簡單的不能再簡單的東西。等到瞭技術架構,我們深入分析其中演化的功能點,就會發現這是個龐大的機器。我們先給他起個簡單的名字:Task Execution Engine(縮寫:TEE)
技術架構設計
下面我們開始進行核心模塊的技術架構設計,按照我們的初始需求開始我們的設計旅程。
初始設計
設計說明:
1、業務後端發出q1請求,我們首先需要對該請求的參數做矯正,為瞭可用性考慮。
2、參數校驗過後,給到執行引擎模塊。執行引擎主要的職責有從資源表獲取資源數據、將任務參數與資源參數封裝到任務對象內、將任務提交線程池。有一點要說明執行引擎最好使用隊列模式,任務先進隊列,可以通過while循環方式或者定時線程池都可以,後面會推薦更好的。
3、任務執行的狀態與結果需要同步到數據庫中,建議使用mysql。
小結:
按照初始需求,該設計相對比較簡單,完全夠用瞭。但是按照產品的迭代,業務方的需求不會僅限於此。繼續演化。
演化階段一
隨著業務的上線,業務端會馬上迎來新的問題。
1、由於提交的任務太多瞭,排在後面的任務遲遲無法等到自己獲取到資源執行任務。當然我們可以完全靠增加資源來解決,但是資源的數量在產品前期是不可知的。所以需要有一些策略,比如讓用戶可以取消自己任務,而不是一直等待。
2、任務的種類開始增加,業務端不滿足於單一制作,開始要求多樣化。
3、任務的執行過程開始需要用到其他資源,不再是一個資源對一個任務的模式瞭。
4、任務的整體執行情況不可知,需要一定的量化分析,至少讓業務組知道每天的任務成功率。
按照需求進行第二版的設計,在盡量不改變原來整體設計的情況下,補充功能。
設計說明:
1、為瞭解決排隊問題,增加瞭雙隊列算法來解決。用圖解的方式解釋一下雙隊列。
邏輯簡單說明一下,任務優先提交至執行隊列,引擎的定時讀取隊列的順序優先為等待隊列。如果等待隊列中的任務可以獲取所需資源,則立即啟動線程執行,否則原封不動回到等待隊列。引擎其次讀取執行隊列,如果無法獲取資源則進入等待隊列,如獲取資源,則立即啟動線程執行。
那麼取消隊列,則隻需要將隊列中的任務踢出隊列即可。在送回隊裡的過程中,一定要保證隊列的有序性。
2、創建瞭任務池,增加瞭任務封裝層,在任務池中挑選需要執行的任務類。
3、增加瞭策略機模塊,添加資源調度策略,由資源調度策略堆任務所需資源合理分配。可以由業務方提供分配方案,盡可能保證任務的公平性。
4、數據庫增加統計表,可以考慮使用定時任務,將任務表的數據統計存入統計表。
小結:
現在看上去已經比較完善瞭,合理瞭任務調度、增加瞭任務種類、合理的資源調度,好像還不錯。但是產品總會有新要求的,那麼繼續演化。
演化階段二
漸漸的,你設計的引擎還不錯。那麼新的挑戰來瞭。
1、更多的業務方找到你,希望也使用你的項目進行任務制作,但是他們並不想共享資源,而是希望有自己的獨立資源,和獨立的隊列。但並不是所有的資源都需要獨立,一些可以支持高並發的資源,是可以共享的。簡而言之,更多的業務方,由業務方為維度的獨立隊列,獨立和共享的資源分配。
2、業務方找到你,說如果把任務1的結果給到任務2,其實就能拿到我要的結果。問題來瞭,原子任務要具備可以編排成復雜任務的能力。
3、任務的狀態過程無法監控。OK,任務狀態機。
4、既然大傢都需要對接你的項目,能不能提供標準的sdk,我隻需要引入就可以完美的對接你的系統。
5、相同的任務參數,是不是制作出來的結果一致呢?那麼是否需要增加結果緩存,降低對資源的消耗呢?
6、完正的生產項目必然需要將日志、告警等關鍵信息傳遞出來,一旦發生問題可以馬上定位到問題的起因。
這些問題對於新人來說還是很有挑戰的,需要對系統深層的含義有充足的理解。沒事,我來好好來說下設計所需要掌握的知識點。
設計說明:
1、需要在資源表中區別資源類型,共享資源組所有業務組都可以使用,獨立資源則資源具備業務標識。在執行引擎的隊列管理中,也需要區分業務組,避免共用排隊。這裡給一個建議,共享的資源一定要是可以支持並發或者可以部署多個實例的,避免所有的業務組產品制作癱瘓。
2、增加瞭高級任務概念,高級任務可以將任意的原子任務進行組合編排,形成全新的任務。需要定義專屬於TEE的語法規則。對語法規則引擎的開發,有一些建議。你可能會選擇規則引擎,建議其實可以自己開發,畢竟語法不會太過復雜,沒必要引入三方的引擎。
3、增加任務狀態機,執行引擎在提交線程的同時,也想任務狀態機提交任務線程信息。任務的進度狀態可以同步給任務狀態機中,同時一旦任務執行過長的時間,除瞭任務自己的超時機制外,也可由狀態機的看門狗程序將卡死線程釋放、資源回收。
4、研發屬於TEE的SDK,作為內部系統不建議SDK增加鑒權模塊。畢竟你對接的往往都是業務後端,鑒權不通過的話根本滲透不到TEE層面。給開發SDK一些建議,盡量引用較少的包,避免業務端引入帶來的包沖突。SDK也需要添加一些回調Consumer或者Function,盡可能讓業務端對接起來代碼簡單。
5、增加瞭緩存策略,可以設想一下,大部分情況下,相同的參數制作出來的結果也必然相同。使用redis,將任務參數與任務結果進行緩存,主鍵可以采用任務參數的MD5值。任務在提交給任務執行引擎前,檢查緩存中是否已經存在結果。緩存的過期時間按照具體情況而定。
6、增加日志系統和監控系統的對接,狀態機與任務執行中的信息接入到日志系統中。對於日志系統的建議是,最好采用成熟的ELK架構。可以考慮兩種方式
a、將日志異步推送到消息隊列(例如:kafka),使用flink將kafka存入es。
b、使用logstash將日志內容清洗處理,推送到es。
兩種方式都可以,但是一定要異步推送日志,避免任務阻塞。
告警系統的接入可以使用Prometheus,將TEE的指標信息開放出來,特別是告警信息。在Prometheus的告警監控規則中,可以將告警信息按照某些策略發送郵件或者短信,通知運維人員。
小結:
做到這裡,我們再看看我們的技術架構圖,是不是感到很滿足。但是這真的夠瞭嗎?
演化階段三
隨著業務愈發繁重,一個新的問題出現。
1、TEE在本機運行很順利,但是每個任務都是需要消耗線程的,單臺模式線程必然不是無限的,總有吃滿的時候。問題來瞭,TEE得支持分佈式部署結構。但是有資源管理的存在,你無法通過加實例的方式來實現,因為資源調度必然混亂。
2、假設TEE掛掉,則等於業務組此刻提交的任務均失敗,容災機制需要建立。
到瞭這一步,很多小夥伴可能覺著一陣頭大,分佈式不是大數據的東西嗎?不是的,不是大數據就不能分佈式部署嗎?就不能有主從節點嗎?就不能有註冊中心嗎?要跨過內心的固有思想,我們往下看。
設計說明:
1、需要先將TEE項目做一下代碼分解,將管理調度模塊與任務執行模塊拆解開瞭。消耗性能和線程較高的是任務執行模塊,定義為TEE執行節點。消耗性能和線程較低,卻需要參數校驗、任務封裝、資源調度、隊列管理的是管理調度模塊,定義為TEE管理節點。
2、TEE執行節點可以多實例,形成節點池。管理節點可以考慮做成共享任務隊列的管理池。這裡有個難點,如何共享任務隊列。建議使用zookeeper或者緩存方式,但是不管哪種方式都要註意使用集群,避免單點故障導致隊列數據丟失。
3、關於註冊中心,可以使用開源組件,nacos、zookeeper都可以。在該架構設計中,其實註冊中心並沒有太多功能,如果你對自己有信心,可以嘗試自己寫一個註冊中心,核心功能就是服務註冊與心跳檢測。可以用netty架構做一做,提高一下自己的代碼能力。
4、在管理池的調用方面,增加網關代理,可以使用nginx、konga等。主要功能是業務端調用的時候,隨機打到管理節點上,就算一個管理節點掛瞭,也不會影響使用,保證瞭生產線的穩定。
小結:
分佈式架構,主從節點模式也好、哨兵模式也好、選舉模式也好,按照自己的業務需求選擇最適合自己的。不是大數據才有分佈式,它是一種設計思想,要知道開發大數據組件的大佬們,也是一行行java寫出來的。大佬們可以,為什麼你不可以呢?
代碼設計
在著手開發該系統的時候,我給大傢一些代碼開發的建議:
1、定時任務的實現,從最簡單的while死循環加sleep,到定時線程池,或者springboot的@Scheduled註解,都可以實現。我在這裡推薦一下時間輪算法TimeWheel,有興趣的可以去瞭解一下。
2、異步消息處理,如果你隻是想在項目內部使用消息總線,推薦使用guava包內的EventBus。按照消息的數量級,考慮使用RabbitMQ或者Kafka作為消息中間件。
3、任務執行,推薦使用CompletableFuture進行異步非阻塞任務編程。
4、在資源的使用中,可能存在多種協議類型http、ws、tcp等。所以代碼設計中,盡可能提供完整全面的協議工具類。
總結
最近和一個同事閑聊的時候,他和我說瞭說最近面試高級研發的情況。總結一下,現在想招一個做過高並發場景的太難瞭,大部分人選隻做業務相關。這讓我想到瞭十年前,我剛工作的時候。那時候沒有那麼多框架,大部分功能需要看很多資料研究底層的原理與算法。隨著軟件行業的日益成熟,從以前拿著谷歌翻譯看國外的組建說明,到從阿裡雲直接對接功能,軟件研發的成本和時間也在大大縮短。漸漸失去瞭研究分析的動力,框架什麼都有為什麼要抓破腦袋自己寫。
怎麼成長?怎麼進步?
上面給出的架構圖,看上去都挺好理解。但真要自己去代碼實現,中間各個環節出現的問題真的好解決嗎?冰凍三尺,非一日之寒。沒有日積月累的學習,是很難成功的。不要懼怕那些你看上去遙遠的東西,獲取你的幾個晚上的學習,它就成瞭你最趁手的武器。對學習而言,難的永遠不是過程,而是踏出第一步。
高並發任務執行架構中,有一個模塊看上去很不起眼,但是在你研發的過程就會發現他會給你制造大麻煩-資源調度。以後有時間我會單獨做一篇關於資源調度的架構設計,與大傢說道說道裡面的坑。
最後說一句:為什麼不ban猛獁?
到此這篇關於Java 高並發編程之最實用的任務執行架構設計建議收藏的文章就介紹到這瞭,更多相關Java 高並發編程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!