互聯網架構:從設計到開發讓你少踩坑

 

本文作者:朱曄,熟悉.NET、Java技術棧,多年MVP,先後任職育碧軟件、英孚教育、5173、空中網、餓了麼等互聯網公司,對技術管理、系統架構、深度學習比較感興趣。

本文出自:技術瑣話

 

這裏所說的三架馬車是指微服務消息隊列定時任務。如下圖所示,這裏是一個三駕馬車共同驅動的一個立體的互聯網項目的架構。不管項目是大是小,這個架構模板的形態一旦定型了之後就不太會變,區別只是我們有更多的服務有更復雜的調用,更復雜的消息流轉,更多的Job,整個架構整體是可擴展的,而且不會變形,這個架構可以在很長的一段時間內無需有大的調整。

 

圖上畫了虛線框的都代表這個模塊或項目是不包含太多業務邏輯的,純粹是一層皮(會調用服務但是不會觸碰數據庫)。黑色線的箭頭代表依賴關係,綠色和紅色箭頭分別是MQ的發送和訂閱消息流的方向。具體在後文都會進一步詳細說明。

微服務

微服務並不是一個很新的概念,在10年前的時候我就開始實踐這個架構風格,在四個公司的項目中全面實現了微服務,越來越堅信這是非常適合互聯網項目的一個架構風格。不是說我們的服務一定要跨物理機器進行遠程調用,而是我們通過進行有意的設計讓我們的業務在一開始的時候就按照領域進行分割,這能讓我們對業務有更充分的理解,能讓我們在之後的迭代中輕易在不同的業務模塊上進行耕耘,能讓我們的項目開發越來越輕鬆,輕鬆來源於幾個方面:

1. 如果我們能進行微服務化,那麼我們一定事先經過比較完善的產品需求討論和領域劃分,每一個服務精心設計自己領域內的表結構,這是一個很重要的設計過程,也決定了整個技術架構和產品架構是匹配的,對於All-In-One的架構往往會省略這一過程,需求到哪裏代碼寫到哪裏。

2. 我們對服務的劃分和職責的定位如果是清晰的,對於新的需求,我們就能知道需要在哪裏改怎麼樣的代碼,沒有複製粘貼的存在少了很多坑。

3. 我們大多數的業務邏輯已經開發完畢,直接重用即可,我們的新業務只是現有邏輯的聚合。在PRD評審後,開發得到的結論是隻需要組合分別調用ABC三個服務的XYZ方法,然後在C服務中修改一下Z方法增加一個分支邏輯,就可以構建起新的邏輯,這種爽快的感覺難以想象。

4. 在性能存在明顯瓶頸的時候,我們可以針對性地對某些服務增加更多機器進行擴容,而且因爲服務的劃分,我們更清楚系統的瓶頸所在,從10000行代碼定位到一行性能存在問題的代碼是比較困難的,但是如果這10000行代碼已經是由10個服務構成的,那麼先定位到某個服務存在性能問題然後再針對這個服務進行分析一下子降低了定位問題的複雜度。

5. 如果業務有比較大的變動需要下線,那麼我們可以肯定的是底層的公共服務是不會淘汰的,下線對應業務的聚合業務服務停掉流量入口,然後下線相關涉及到的基礎服務及部分接口即可。如果擁有完善的服務治理平臺,整個操作甚至無需改動代碼。

這裏也要求我們做到幾個方面的原則:

1. 服務的粒度劃分需要把控好。我的習慣是先按照領域來分不會錯,隨着項目的進展慢慢進行更細粒度的拆分。比如對於互聯網金融P2P業務,一開始可以分爲:

  • a 三方合作服務PartnerInvestService:對接合作的三方理財平臺的流量

  • b 普通投資服務NormalInvestService:最普通形態的資產的主流程

  • c 預約投資產品服務ReserveInvestService:需要預約投資的資產的主流程

  • d 週期性計劃產品服務AutoInvestService:會定期自動復投的理財產品主流程

  • e 投資人交易服務TradeService:專門負責處理投資人的交易行爲,比如投資

  • f 借款人交易服務LoanService:專門負責處理借款人的交易行爲,比如還款

  • g 用戶服務UserService:處理用戶的註冊登錄等

  • h 資產服務ProjectService:處理資產和標的相關

  • i 賬戶賬務服務AccountService:處理用戶的賬戶各個子賬戶和賬務記錄

  • j 營銷活動服務ActivityService:處理各種活動、用戶的積分體系

  • k 會員體系服務VipService:處理用戶的會員成長體系

  • l 銀行存管服務BankService:專門用於對接銀行存管系統

  • m 電子簽章服務DigSignService:專門用於對接三方數字簽章系統

  • n 消息推送服務MessageService:專門用於對接三方短信通道和推送SDK

2. 服務一定是立體的,不是在一個層次上的,如上圖,我們的服務有三個層次: 

  • 聚合業務服務:高層次的串起來整個流程的具有完整業務形態的業務服務。和基礎業務服務不同的是,這裏是在完整描述一方面的業務,這個業務往往是由各種基礎業務拼裝組合起來的。和不同外部合作方的不同合作形式,給用戶提供的產品的不同服務形態,都決定了聚合業務服務會有業務流程上的差異化,如果把此類服務下放到基礎業務服務中,那麼基礎業務服務會有各種if-else邏輯(根據產品類型、用戶類型進行各種if-else),隨着業務的合作不合作,需求變動,基礎業務服務會腐化得很厲害,爲了避免這個情況,我們把變動的多的聚合業務邏輯放到獨立的業務服務中。一般而言,聚合業務服務因爲代表了獨立的業務流程,它們之間是不會進行相互調用的,但是它們一定會調用大量的各類基礎業務服務。在第一點裏說的標有藍色字體的a~d這些服務都是此類服務。這個層次的服務的業務邏輯更多是在表達業務流程的複雜性和差異性,不會涉及到具體怎麼處理賬戶信息、賬務信息、用戶信息,不會涉及到怎麼處理具體的投資人和借款人的交易。比如對於預約這類業務形態,它關注的是先要預約資產,然後再由系統進行自動投資,底層完全依賴於投資人交易服務來做整個交易的過程。

  • 基礎業務服務:某一個領域業務相關的服務。此類服務之間是允許相互調用的,比如投資人交易服務和借款人交易服務免不了需要和用戶服務、資產服務、賬戶賬務服務進行通訊做相關的用戶信息查詢、標的信息查詢、記賬等業務操作。之所以投資人交易服務和借款人交易服務定位爲基礎業務服務是因爲,它們處理的是某一個具體方面的業務,並不是全流程,在這個抽象層次上,業務不是那麼容易變動的,對於複雜的各種業務形態(比如預約交易、自動復投交易、等額本息交易)會在這些服務之上形成聚合業務服務。在第一點裏說的標有綠色字體的e~k這些服務都是此類服務。在這個層次的服務雖然擁有大量的業務邏輯,但是其實已經享受到了很大程度的公共基礎服務的重用了,而且和自己業務耦合較弱的額外邏輯往往沒有在本服務中堆積,由更多專職的基礎業務服務來承擔了這部分邏輯。

  • 公共基礎服務:負責某一個方面的基礎業務(沒有什麼領域業務邏輯在裏面),可以是自治的處理某一個方面的基礎業務,也可以和外部通訊實現某一個方面的功能,服務之間是不會相互調用的,但是會被聚合業務服務和基礎業務服務調用。在第一點裏說的標有橙色字體的l~n這些服務都是此類服務。如果以後和外部的合作有變動,因爲我們已經定義了對外的服務契約,可以輕易替換這個服務來更換合作的第三方,系統其餘的地方几乎不需要修改。所有的三方對接都建議獨立出公共基礎服務,如果同一個業務對接多個三方渠道,比如推送對接了極光和個推,甚至公共基礎服務還可以由一個抽象聚合的推送服務,下面再路由到具體的極光推送和個推推送服務。

希望在這裏把這個事情說清楚了,怎麼來劃分服務怎麼劃分三個層次的服務是一個很有意思很有必要的事情,在服務劃分之後最好有一個明確的文檔來描述每一個服務的職責,這樣我們在無需閱讀API的情況下可以大概定位到業務所在的服務,整個複雜的系統就變得很直白了。

3.每一個服務對接的底層數據表是獨立的沒有交叉關聯的,也就是數據結構是不直接對外的,需要使用其他服務的數據一定通過訪問接口進行。好處也就是面向對象設計中封裝的好處:

  • 可以很方便地重構底層的數據結構甚至是數據源,只要接口不變,外部不會感知到。

  • 性能有問題的情況下需要加緩存、分表、拆庫、歸檔是比較方便的事情,畢竟數據源沒有外部依賴。 

說白了就是我的數據我做主,我想怎麼搞外面管不着,在重構或是做一些高層次技術架構(比如異地多活)的時候,沒有底層數據被依賴,這太重要了。當然,壞處或是麻煩的地方就是跨服務的調用使得數據操作無法在一個數據庫事務中完成,這並不是什麼大問題,一是因爲我們這種拆分方式並不會讓粒度太細,大部分的業務邏輯是在一個業務服務裏完成的,二是後面會提到跨服務的調用不管是通過MQ進行的還是直接調用進行的,都會有補償來實現最終一致性。

4.考慮到跨機器跨進程調用服務穩定性方面的顯著差異。在方法內部進行方法調用,我們需要考慮調用出現異常的情況,但是幾乎不需要考慮超時的情況,幾乎不需要考慮請求丟失的情況,幾乎不需要考慮重複調用的情況,對於遠程服務調用,這些點都需要去重點考慮,否則系統整體就是基本可用,測試環境不出問題,但是到了線上問題百出的狀態。這就要求對於每一個服務的提供和調用多問幾個上面的問題,細細考慮到因爲網絡問題方法沒有執行多次執行或部分執行的情況:

  • 我們在對外提供服務的時候,不但要告知用戶服務提供的業務能力,還要告知用戶服務的特性,比如是否是冪等的(對於訂單類型的操作服務,相同的訂單相同的操作強烈建議是冪等的,這樣調用方可以放心進行重試或補償);是否需要外部進行補償(在這裏你可能說爲什麼需要外部進行補償,服務就不能自己補償嗎,對於內部的子邏輯服務當然可以自己補償,但是有的時候因爲網絡原因請求就沒有到服務端,服務端一無所知這個調用當然無從去補償);是否有頻控的限制;是否有權限的限制;降級後的處理方式等等。

  • 反過來,我們調用其它服務也需要多問幾句目標服務的特性,針對性進行設計相應的補償邏輯、一致性處理邏輯和降級邏輯。我們必須考慮到有些時候並不是服務端的問題,而是請求根本沒有到達服務端。

  • 服務本身往往也會有複雜的邏輯,作爲客戶端的身份調用大量外部的服務,所以服務端和客戶端的角色不是固定不變的,當我們的服務內部有許多客戶端來調用服務端的時候,對於每一個子邏輯我們都需要仔細考慮每一個環節。否則會出現的情況就是,這個服務是部分邏輯冪等的或是部分邏輯是具備最終一致性的。

如果你說,這麼多服務,我在實現的時候很難考慮到這些點,我完全不去考慮分佈式事務、冪等性、補償(毫不誇張地說,有的時候我們花了20%的時間實現了業務邏輯,然後花80%的時間在實現這些可靠性方面的外圍邏輯),行不行?也不是不可以,那麼業務在線上跑的時候一定會是千瘡百孔的,如果整個業務的處理對可靠性方面的要求不高或是業務不面向用戶不會受到投訴的話,這部分業務是可以暫時不考慮這些點,但是諸如訂單業務這種核心的不允許有不一致性的業務還是需要全面考慮這些點的。

5. 考慮到跨機器跨進程調用服務數據傳輸方面的顯著差異。對於本地的方法調用,如果參數和返回值傳的是對象,那麼對於大部分的語言來說,傳的是指針(或指針的拷貝),指針指向的是堆中分配的對象,對象在數據傳輸上的成本幾乎忽略不計,也沒有序列化和反序列化的開銷。對於跨進程的服務調用,這個成本往往不能忽略不計。如果我們需要返回很多數據,往往接口的定義需要進行特殊的改造:

  • 通過使用分頁的形式,一次返回固定的少量數據,客戶端按需拉取更多數據。

  • 可以在參數中傳類似於EnumSet的數據結構,讓客戶端告知服務端我需要什麼層次的數據,比如GetUserInfo接口可以提供給客戶端BasicInfo、VIPInfo、InvestData、RechargeData、WithdrawData,客戶端可以按需從服務端拿BasicInfo|VipInfo。

6. 這裏還引申出方法粒度的問題,比如我們可以定義GetUserInfo通過傳入不用的參數來返回不同的數據組合,也可以分別定義GetUserBasicInfo、GetUserVIPInfo、GetUserInvestData等等細粒度的接口,接口的粒度定義取決於使用者會怎麼來使用數據,更趨向於一次使用單種類型數據還是複合類型的數據等等。

7. 然後我們需要考慮接口升級的問題,接口的改動最好是兼容之前的接口,如果接口需要淘汰下線,需要先確保調用方改造到了新接口,確保調用方流量爲0觀察一段時間後方能從代碼下線老接口。一旦服務公開出去,要進行接口定義調整甚至下線往往就沒有這麼容易了,不是自己說了算了。所以對外API的設計需要慎重點。

8. 最後不得不說,在整個公司都搞起了微服務後,跨部門的一些服務調用在商定API的時候難免會有一些扯皮的現象發生,到底是我傳給你呢還是你自己來拉,這個數據對我沒用爲什麼要在我這裏留一下呢?拋開非技術層面的事情不說,這些扯皮也是有一些技術手段來化解的:

  • 明確服務職責,也就明確了服務應該感知到什麼不應該感知到什麼。

  • 跨部門的服務交互的接口定義可以定的很輕,採用只有一個訂單號的接口或MQ通知+數據回拉的策略(誰數據多誰提供數據接口,不用把數據一次性推給下游)。

  • 數據提供方可以構建一套通用數據接口,這樣可以滿足多個部門的需求,無需做定製化的處理。甚至在接口上可以提供落地和不落地兩種性質的透傳。

 

你可能看到這裏覺得很頭暈,爲什麼微服務需要額外考慮這麼多東西,實現的複雜度一下子上升了。我想說的是我們需要換一個角度來考慮這個事情:

1. 我們不需要在一開始的時候對所有邏輯都進行嚴密的考慮,先覆蓋核心流程核心邏輯。因爲跨服務成爲了服務的提供方和使用方,相當於除了我自己,還有很多其它人會來關係我的服務能力,大家會提出各種問題,這對設計一個可靠的方法是有好處的。

2. 即使在不跨服務調用的時候我們把所有邏輯堆積在一起,也不意味着這些邏輯一定是事務性的,實現嚴密的,跨服務調用往往是一定程度放大了問題產生的可能性。

3. 我們還有服務框架呢,服務框架往往會在監控跟蹤層次和運維繫統結合在一起提供很多一體化的功能,這將封閉在內部的方法邏輯打散暴露出來,對於有一個完善的監控平臺的微服務系統,在排查問題的時候你往往會感嘆這是一個遠程服務調用就好了。

4. 最大的紅利還是之前說的,當我們以清晰的業務邏輯形成了一個立體化的服務體系之後,任何需求可以解剖爲很少量的代碼修改和一些組合的服務調用,而且你知道我這麼做是不會有任何問題的,因爲底層的服務ABCDEFG都是經過歷史考驗的,這種爽快感體驗過一次就會大呼過癮。

但是,如果服務粒度劃分的不合理,層次劃分的不合理,底層數據源有交叉,沒考慮到網絡調用失敗,沒考慮到數據量,接口定義不合理,版本升級過於魯莽,整個系統會出各種各樣的擴展問題性能問題和Bug,這是很頭痛的,這也就需要我們有一個完善的服務框架來幫助我們定位各種不合理,在之後說到中間件的文章中會再具體着重介紹服務治理這塊。

 

消息隊列

消息隊列MQ的使用有下面幾個好處,或者說我們往往處於這些目的來考慮引入MQ:

1. 異步處理:類似於訂單這樣的流程一般可以定義出一個核心流程,這個流程用於處理核心訂單的狀態機,需要儘快同步落庫完成,然後圍繞訂單會衍生出一系列和用戶相關的庫存相關的後續的業務處理,這些處理完全不需要卡在用戶點擊提交訂單的那剎那進行處理。下單只是一個確認合法受理訂單的過程,後續的很多事情都可以慢慢在幾十個模塊中進行流轉,這個流轉過程哪怕是消耗5分鐘,用戶也無需感受到。

2. 流量洪峯:互聯網項目的一個特點是有的時候會做一些toC的促銷,免不了有一些流量洪峯,如果我們引入了消息隊列在模塊之間作爲緩衝,那麼backend的服務可以以自己既有的舒服的頻次來被動消耗數據,不會被強壓的流量擊垮。當然,做好監控是必不可少的,下面再細說一下監控。

3. 模塊解耦:隨着項目複雜度的上升,我們會有各種來源於項目內部和外部的事件(用戶註冊登陸、投資、提現事件等),這些重要事件可能不斷有各種各樣的模塊(營銷模塊、活動模塊)需要關心,核心業務系統去調用這些外部體系的模塊,讓整個系統在內部糾纏在一起顯然是不合適的,這個時候通過MQ進行解耦,讓各種各樣的事件在系統中進行松耦合流轉,模塊之間各司其職也相互沒有感知,這是比較適合的做法。

4. 消息羣發:有一些消息是會有多個接收者的,接收者的數量還是動態的(類似指責鏈的性質也是可能的),在這個時候如果上下游進行一對多的耦合就會更麻煩,對於這種情況就更適用使用MQ進行解耦了。上游只管發消息說現在發生了什麼事情,下游不管有多少人關心這個消息,上游都是沒有感知的。

 

這些需求互聯網項目中基本都存在,所以消息隊列的使用是非常重要的一個架構手段。在使用上有幾個注意點:

1. 我更傾向於獨立一個專門的listener項目(而不是合併在server中)來專門做消息的監聽,然後這個模塊其實沒有過多的邏輯,只是在收到了具體的消息之後調用對應的service中的API進行消息處理。listener是可以啓動多份做一個負載均衡的(取決於具體使用的MQ產品),但是因爲這裏幾乎沒有什麼壓力,不是100%必須。注意,不是所有的service都是需要有一個配到的listener項目的,大多數公共基礎服務因爲本身很獨立不需要感知到外部的其它業務事件,所以往往是沒有listener的,基礎業務服務也有一些是類似的原因不需要有listener。

2. 對於重要的MQ消息,應當配以相應的補償線作爲備份,在MQ集羣一切正常作爲補漏,在MQ集羣癱瘓的時候作爲後背。我在日千萬訂單的項目中使用過RabbitMQ,雖然QPS在幾百上千,遠遠低於RabbitMQ壓測下來能抗住的數萬QPS,但是整體上有那麼十萬分之一的丟消息概率(我也用過阿里的RocketMQ,但是因爲單量較小目前沒有觀察到有類似的問題),這些丟掉的消息馬上會由補償線進行處理了。在極端的情況下,RabbitMQ發生了整個集羣宕機,A服務發出的消息無法抵達B服務了,這個時候補償Job開始工作,定期從A服務批量拉取消息提供給B服務,雖然消息處理是一批一批的,但是至少確保了消息可以正常處理。做好這套後備是非常重要的,因爲我們無法確保中間件的可用性在100%。

3. 補償的實現是不帶任何業務邏輯的,我們再梳理一下補償這個事情。如果A服務是消息的提供者,B-listener是消息監聽器,聽到消息後會調用B-server中具體的方法handleXXMessage(XXMessage message)來執行業務邏輯,在MQ停止工作的時候,有一個Job(可配置補償時間以及每次拉取的量)來定期調用A服務提供的專有方法getXXMessages(LocalDateTime from, LocalDateTime to, int batchSize)來拉取消息,然後還是(可以併發)調用B-server的那個handleXXMessage來處理消息。這個補償的Job可以重用的可配置的,無需每次爲每一個消息都手寫一套,唯一需要多做的事情是A服務需要提供一個拉取消息的接口。那你可能會說,我A服務這裏還需要維護一套基於數據庫的消息隊列嗎,這個不是自己搞一套基於被動拉的消息隊列了嗎?其實這裏的消息往往只是一個轉化工作,A一定在數據庫中有落地過去一段時間發生過變動的數據,只要把這些數據轉化爲Message對象提供出去即可。B-server的handleXXMessage由於是冪等的,所以無所謂消息是否重複處理,這裏只是在應急情況下進行無腦的過去一段時間的數據的依次處理。

4. 所有消息的處理端最好對相同的消息處理實現冪等,即使有一些MQ產品支持消息處理且只處理一次,靠自己做好冪等能讓事情變得更簡單。

5. 有一些場景下有延遲消息或延遲消息隊列的需求,諸如RabbitMQ、RocketMQ都有不同的實現方式。

6. MQ消息一般而言有兩種,一種是(最好)只能被一個消費者進行消費並且只消費一次的,另一種是所有訂閱者都可以來處理,不限制人數。不用的MQ中間件對於這兩種形式都有不同的實現,有的時候使用消息類型來做,有的使用不同的交換機來做,有的是使用group的劃分來做(不同的group可以重複消息相同的消息)。一般來說都是支持這兩種實現的。在使用具體產品的時候務必研究相關的文檔,做好實驗確保這兩種消息是以正確的方式在處理,以免發生妖怪問題。

7. 需要做好消息監控,最最重要的是監控消息是否有堆積,有的話需要及時增強下游處理能力(加機器,加線程),當然做的更好點可以以熱點拓撲圖繪製所有消息的流向流速一眼就可以看到目前哪些消息有壓力。你可能會想既然消息都在MQ體系中不會丟失,消息有堆積處理慢一點其實也沒什麼問題。是的,消息可以有適當的堆積,但是不能大量堆積,如果MQ系統出現存儲問題,大量堆積的消息有丟失也是比較麻煩的,而且有一些業務系統對於消息的處理是看時間的,過晚到達的消息是會認爲業務違例進行忽略的。

8. 圖上畫了兩個MQ集羣,一套對內一套對外。原因是對內的MQ集羣我們在權限上控制可以相對弱點,對外的集羣必須明確每一個Topic,而且Topic需要由固定的人來維護不能在集羣上隨意增刪Topic造成混亂。對內對外的消息實現硬隔離對於性能也有好處,建議在生產環境把對內對外的MQ集羣進行隔離劃分。

 

定時任務

定時任務的需求有那麼幾類:

1. 如之前所說,跨服務調用,MQ通知難免會有不可達的問題,我們需要有一定的機制進行補償。

2. 有一些業務是基於任務表進行驅動的,有關任務表的設計下面會詳細說明。

3. 有一些業務是定時定期來進行處理的,根本不需要實時進行處理(比如通知用戶紅包即將過期,和銀行進行日終對賬,給用戶出賬單等)。和2的區別在於,這裏的任務的執行時間和頻次是五花八門的,2的話一般而言是固定頻次的。

詳細說明一下任務驅動是怎麼一回事。其實在數據庫中做一些任務表,以這些表驅動作爲整個數據處理的核心體系,這套被動的運作方式是最最可靠的,比MQ驅動或服務驅動兩種形態可靠多,天生必然是可負載均衡的+冪等處理+補償到底的,任務表可以設計下面的字段:

  • 自增ID

  • 任務類型:表明具體的任務類型,當然也可以不同的任務類型直接做多個任務表。

  • 外部訂單號:和外部業務邏輯的唯一單號關聯起來。

  • 執行狀態:未處理(等待處理),處理中(防止被其它Job搶佔),成功(最終成功了),失敗(暫時失敗,會繼續進行重試),人工介入(永遠不會再變了,一定需要人工處理,需要報警通知)

  • 重試次數:處理過太多次還是失敗的可以歸類爲死信,由專門的死信隊列任務單獨再進行若干次的重試不行的話就報警人工干預

  • 處理歷史:每一次的處理結果,Json的List保存在這裏供參考

  • 上次處理時間:最近一次執行時間

  • 上次處理結果:最近一次執行結果

  • 創建時間:數據庫維護

  • 最後修改時間:數據庫維護

除了這些字段之外,還可能會加一些業務自己的字段,比如訂單狀態,用戶ID等等信息作爲冗餘。任務表可以進行歸檔減少數據量,任務表扮演了消息隊列的性質,我們需要有監控可以對數據積壓,出入隊不平衡處理不過來,死信數據發生等等情況進行報警。如果我們的流程處理是任務ABCD順序來處理的話,每一個任務因爲有自己的檢查間隔,這套體系可能會浪費一點時間,沒有通過MQ實時串聯這麼高效,但是我們要考慮到的是,任務的處理往往是批量數據獲取+並行執行的,和MQ基於單條數據的處理是不一樣的,總體上來說吞吐上不會有太多的差異,差的只是單條數據的執行時間,考慮到任務表驅動執行的被動穩定性,對於有的業務來說,這不失爲一種選擇。

這裏再說明一下Job的幾個設計原則:

1. Job可以由各種調度框架來驅動,比如ElasticJob、Quartz等等,需要獨立項目處理,不能和服務混在一起,部署啓動多份往往會有問題。當然,自己實現一個任務調度框架也不是很麻煩的事情,在執行的時候來決定Job在哪臺機器來跑,讓整個集羣的資源使用更合理。說白了就是兩種形態,一種是Job部署在那裏由框架來觸發,還有就是隻是代碼在那裏,由框架來起進程。

2. Job項目只是一層皮,最多有一些配置的整合,不應該有實際的業務邏輯,不會觸碰數據庫,大部分情況就是在調用具體服務的API接口。Job項目就負責配置和頻次的控制。

3. 補償類的Job注意補償次數,避免整個任務被死信數據卡住的問題。

三馬車都說完了,那麼,最後我們來梳理一下這麼一套架構下整個項目的模塊劃分:

  • Site:

    • front

    • console

    • app-gateway

  • Façade Service:

    • partnerinvestservice-api

    • partnerinvestservice-server

    • partnerinvestservice-listener

    • normalinvestservice-api

    • normalinvestservice-server

    • normalinvestservice-listener

    • reserveinvestservice-api

    • reserveinvestservice-server

    • reserveinvestservice-listener

    • autoinvestservice-api

    • autoinvestservice-server

    • autoinvestservice-listener

  • Business Service:

    • tradeservice-api

    • tradeservice-server

    • tradeservice-listener

    • loanservice-api

    • loanservice-server

    • loanservice-listener

    • userservice-api

    • userservice-server

    • projectservice-api

    • projectservice-server

    • accountservice-api

    • accountservice-server

    • accountservice-listener

    • activityservice-api

    • activityservice-server

    • activityservice-listener

    • vipservice-api

    • vipservice-server

    • vipservice-listener

  • Foundation Service:

    • bankservice-api

    • bankservice-server

    • digsignservice-api

    • digsignservice-server

    • messageservice-api

    • messageservice-server

  • Job:

    • scheduler-job

    • task-job

    • compensation-job

這每一個模塊都可以打包成獨立的包,所有的項目不一定都要在一個項目空間內,可以拆分爲20個項目,服務的api+server+listener放在一個項目內,這樣其實有利於CICD缺點就是修改代碼的時候需要打開N個項目。

 

之前開篇的時候說過,使用這套簡單的架構既能夠有很強的擴展餘地,複雜程度上或者說工作量上不會比All-In-One的架構多多少,看到這裏你可能覺得並不同意這個觀點。其實這個還是要看團隊的積累的,如果團隊大家熟悉這套架構體系,玩轉微服務多年的話,那麼其實很多問題會在編碼的過程中直接考慮進去,很多時候設計也可以認爲是一個熟能生巧的活,做了多了自然知道什麼東西應該放在哪裏,怎麼去分怎麼去合,所以並不會有太多的額外時間成本。這三駕馬車構成的這麼一套簡單實用的架構方案我認爲可以適用於大多數的互聯網項目,只是有些互聯網項目會更偏重其中的某一方面弱化另一方面,希望本文對你有用。

 

版權申明:內容來源網絡,版權歸原創者所有。除非無法確認,我們都會標明作者及出處,如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章