網際網路架構:屢試不爽的架構三馬車

技術瑣話發表於2019-03-13

這裡所說的三架馬車是指微服務訊息佇列定時任務。如下圖所示,這裡是一個三駕馬車共同驅動的一個立體的網際網路專案的架構。不管專案是大是小,這個架構模板的形態一旦定型了之後就不太會變,區別只是我們有更多的服務有更復雜的呼叫,更復雜的訊息流轉,更多的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的架構多多少,看到這裡你可能覺得並不同意這個觀點。其實這個還是要看團隊的積累的,如果團隊大家熟悉這套架構體系,玩轉微服務多年的話,那麼其實很多問題會在編碼的過程中直接考慮進去,很多時候設計也可以認為是一個熟能生巧的活,做了多了自然知道什麼東西應該放在哪裡,怎麼去分怎麼去合,所以並不會有太多的額外時間成本。這三駕馬車構成的這麼一套簡單實用的架構方案我認為可以適用於大多數的網際網路專案,只是有些網際網路專案會更偏重其中的某一方面弱化另一方面,希望本文對你有用。

網際網路架構:屢試不爽的架構三馬車

網際網路架構:屢試不爽的架構三馬車

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562044/viewspace-2638295/,如需轉載,請註明出處,否則將追究法律責任。

相關文章