溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

互聯(lián)網(wǎng)架構(gòu):屢試不爽的架構(gòu)三馬車

發(fā)布時間:2020-08-18 03:02:07 來源:ITPUB博客 閱讀:141 作者:技術(shù)瑣話 欄目:軟件技術(shù)

這里所說的三架馬車是指微服務(wù)、消息隊列定時任務(wù)。如下圖所示,這里是一個三駕馬車共同驅(qū)動的一個立體的互聯(lián)網(wǎng)項目的架構(gòu)。不管項目是大是小,這個架構(gòu)模板的形態(tài)一旦定型了之后就不太會變,區(qū)別只是我們有更多的服務(wù)有更復(fù)雜的調(diào)用,更復(fù)雜的消息流轉(zhuǎn),更多的Job,整個架構(gòu)整體是可擴展的,而且不會變形,這個架構(gòu)可以在很長的一段時間內(nèi)無需有大的調(diào)整。

互聯(lián)網(wǎng)架構(gòu):屢試不爽的架構(gòu)三馬車


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

微服務(wù)

微服務(wù)并不是一個很新的概念,在10年前的時候我就開始實踐這個架構(gòu)風(fēng)格,在四個公司的項目中全面實現(xiàn)了微服務(wù),越來越堅信這是非常適合互聯(lián)網(wǎng)項目的一個架構(gòu)風(fēng)格。不是說我們的服務(wù)一定要跨物理機器進(jìn)行遠(yuǎn)程調(diào)用,而是我們通過進(jìn)行有意的設(shè)計讓我們的業(yè)務(wù)在一開始的時候就按照領(lǐng)域進(jìn)行分割,這能讓我們對業(yè)務(wù)有更充分的理解,能讓我們在之后的迭代中輕易在不同的業(yè)務(wù)模塊上進(jìn)行耕耘,能讓我們的項目開發(fā)越來越輕松,輕松來源于幾個方面:

1. 如果我們能進(jìn)行微服務(wù)化,那么我們一定事先經(jīng)過比較完善的產(chǎn)品需求討論和領(lǐng)域劃分,每一個服務(wù)精心設(shè)計自己領(lǐng)域內(nèi)的表結(jié)構(gòu),這是一個很重要的設(shè)計過程,也決定了整個技術(shù)架構(gòu)和產(chǎn)品架構(gòu)是匹配的,對于All-In-One的架構(gòu)往往會省略這一過程,需求到哪里代碼寫到哪里。

2. 我們對服務(wù)的劃分和職責(zé)的定位如果是清晰的,對于新的需求,我們就能知道需要在哪里改怎么樣的代碼,沒有復(fù)制粘貼的存在少了很多坑。

3. 我們大多數(shù)的業(yè)務(wù)邏輯已經(jīng)開發(fā)完畢,直接重用即可,我們的新業(yè)務(wù)只是現(xiàn)有邏輯的聚合。在PRD評審后,開發(fā)得到的結(jié)論是只需要組合分別調(diào)用ABC三個服務(wù)的XYZ方法,然后在C服務(wù)中修改一下Z方法增加一個分支邏輯,就可以構(gòu)建起新的邏輯,這種爽快的感覺難以想象。

4. 在性能存在明顯瓶頸的時候,我們可以針對性地對某些服務(wù)增加更多機器進(jìn)行擴容,而且因為服務(wù)的劃分,我們更清楚系統(tǒng)的瓶頸所在,從10000行代碼定位到一行性能存在問題的代碼是比較困難的,但是如果這10000行代碼已經(jīng)是由10個服務(wù)構(gòu)成的,那么先定位到某個服務(wù)存在性能問題然后再針對這個服務(wù)進(jìn)行分析一下子降低了定位問題的復(fù)雜度。

5. 如果業(yè)務(wù)有比較大的變動需要下線,那么我們可以肯定的是底層的公共服務(wù)是不會淘汰的,下線對應(yīng)業(yè)務(wù)的聚合業(yè)務(wù)服務(wù)停掉流量入口,然后下線相關(guān)涉及到的基礎(chǔ)服務(wù)進(jìn)行部分接口即可。如果擁有完善的服務(wù)治理平臺,整個操作甚至無需改動代碼。

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

1. 服務(wù)的粒度劃分需要把控好。我的習(xí)慣是先按照領(lǐng)域來分不會錯,隨著項目的進(jìn)展慢慢進(jìn)行更細(xì)粒度的拆分。比如對于互聯(lián)網(wǎng)金融P2P業(yè)務(wù),一開始可以分為:

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

  • b 普通投資服務(wù)NormalInvestService:最普通形態(tài)的資產(chǎn)的主流程

  • c 預(yù)約投資產(chǎn)品服務(wù)ReserveInvestService:需要預(yù)約投資的資產(chǎn)的主流程

  • d 周期性計劃產(chǎn)品服務(wù)AutoInvestService:會定期自動復(fù)投的理財產(chǎn)品主流程

  • e 投資人交易服務(wù)TradeService:專門負(fù)責(zé)處理投資人的交易行為,比如投資

  • f 借款人交易服務(wù)LoanService:專門負(fù)責(zé)處理借款人的交易行為,比如還款

  • g 用戶服務(wù)UserService:處理用戶的注冊登錄等

  • h 資產(chǎn)服務(wù)ProjectService:處理資產(chǎn)和標(biāo)的相關(guān)

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

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

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

  • l 銀行存管服務(wù)BankService:專門用于對接銀行存管系統(tǒng)

  • m 電子簽章服務(wù)DigSignService:專門用于對接三方數(shù)字簽章系統(tǒng)

  • n 消息推送服務(wù)MessageService:專門用于對接三方短信通道和推送SDK

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

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

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

公共基礎(chǔ)服務(wù):負(fù)責(zé)某一個方面的基礎(chǔ)業(yè)務(wù)(沒有什么領(lǐng)域業(yè)務(wù)邏輯在里面),可以是自治的處理某一個方面的基礎(chǔ)業(yè)務(wù),也可以和外部通訊實現(xiàn)某一個方面的功能,服務(wù)之間是不會相互調(diào)用的,但是會被聚合業(yè)務(wù)服務(wù)和基礎(chǔ)業(yè)務(wù)服務(wù)調(diào)用。在第一點里說的標(biāo)有橙色字體的l~n這些服務(wù)都是此類服務(wù)。如果以后和外部的合作有變動,因為我們已經(jīng)定義了對外的服務(wù)契約,可以輕易替換這個服務(wù)來更換合作的第三方,系統(tǒng)其余的地方幾乎不需要修改。所有的三方對接都建議獨立出公共基礎(chǔ)服務(wù),如果同一個業(yè)務(wù)對接多個三方渠道,比如推送對接了極光和個推,甚至公共基礎(chǔ)服務(wù)還可以由一個抽象聚合的推送服務(wù),下面再路由到具體的極光推送和個推推送服務(wù)。

希望在這里把這個事情說清楚了,怎么來劃分服務(wù)怎么劃分三個層次的服務(wù)是一個很有意思很有必要的事情,在服務(wù)劃分之后最好有一個明確的文檔來描述每一個服務(wù)的職責(zé),這樣我們在無需閱讀API的情況下可以大概定位到業(yè)務(wù)所在的服務(wù),整個復(fù)雜的系統(tǒng)就變得很直白了。

3.每一個服務(wù)對接的底層數(shù)據(jù)表是獨立的沒有交叉關(guān)聯(lián)的,也就是數(shù)據(jù)結(jié)構(gòu)是不直接對外的,需要使用其他服務(wù)的數(shù)據(jù)一定通過訪問接口進(jìn)行。好處也就是面向?qū)ο笤O(shè)計中封裝的好處:

  • 可以很方便地重構(gòu)底層的數(shù)據(jù)結(jié)構(gòu)甚至是數(shù)據(jù)源,只要接口不變,外部不會感知到。

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

說白了就是我的數(shù)據(jù)我做主,我想怎么搞外面管不著,在重構(gòu)或是做一些高層次技術(shù)架構(gòu)(比如異地多活)的時候,沒有底層數(shù)據(jù)被依賴,這太重要了。當(dāng)然,壞處或是麻煩的地方就是跨服務(wù)的調(diào)用使得數(shù)據(jù)操作無法在一個數(shù)據(jù)庫事務(wù)中完成,這并不是什么大問題,一是因為我們這種拆分方式并不會讓粒度太細(xì),大部分的業(yè)務(wù)邏輯是在一個業(yè)務(wù)服務(wù)里完成的,二是后面會提到跨服務(wù)的調(diào)用不管是通過MQ進(jìn)行的還是直接調(diào)用進(jìn)行的,都會有補償來實現(xiàn)最終一致性。

4.考慮到跨機器跨進(jìn)程調(diào)用服務(wù)穩(wěn)定性方面的顯著差異。在方法內(nèi)部進(jìn)行方法調(diào)用,我們需要考慮調(diào)用出現(xiàn)異常的情況,但是幾乎不需要考慮超時的情況,幾乎不需要考慮請求丟失的情況,幾乎不需要考慮重復(fù)調(diào)用的情況,對于遠(yuǎn)程服務(wù)調(diào)用,這些點都需要去重點考慮,否則系統(tǒng)整體就是基本可用,測試環(huán)境不出問題,但是到了線上問題百出的狀態(tài)。這就要求對于每一個服務(wù)的提供和調(diào)用多問幾個上面的問題,細(xì)細(xì)考慮到因為網(wǎng)絡(luò)問題方法沒有執(zhí)行多次執(zhí)行或部分執(zhí)行的情況:

  • 我們在對外提供服務(wù)的時候,不但要告知用戶服務(wù)提供的業(yè)務(wù)能力,還要告知用戶服務(wù)的特性,比如是否是冪等的(對于訂單類型的操作服務(wù),相同的訂單相同的操作強烈建議是冪等的,這樣調(diào)用方可以放心進(jìn)行重試或補償);是否需要外部進(jìn)行補償(在這里你可能說為什么需要外部進(jìn)行補償,服務(wù)就不能自己補償嗎,對于內(nèi)部的子邏輯服務(wù)當(dāng)然可以自己補償,但是有的時候因為網(wǎng)絡(luò)原因請求就沒有到服務(wù)端,服務(wù)端一無所知這個調(diào)用當(dāng)然無從去補償);是否有頻控的限制;是否有權(quán)限的限制;降級后的處理方式等等。

  • 反過來,我們調(diào)用其它服務(wù)也需要多問幾句目標(biāo)服務(wù)的特性,針對性進(jìn)行設(shè)計相應(yīng)的補償邏輯、一致性處理邏輯和降級邏輯。我們必須考慮到有些時候并不是服務(wù)端的問題,而是請求根本沒有到達(dá)服務(wù)端。

  • 服務(wù)本身往往也會有復(fù)雜的邏輯,作為客戶端的身份調(diào)用大量外部的服務(wù),所以服務(wù)端和客戶端的角色不是固定不變的,當(dāng)我們的服務(wù)內(nèi)部有許多客戶端來調(diào)用服務(wù)端的時候,對于每一個子邏輯我們都需要仔細(xì)考慮每一個環(huán)節(jié)。否正會出現(xiàn)的情況就是,這個服務(wù)是部分邏輯冪等的或是部分邏輯是具備最終一致性的。

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

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

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

  • 可以在參數(shù)中傳類似于EnumSet的數(shù)據(jù)結(jié)構(gòu),讓客戶端告知服務(wù)端我需要什么層次的數(shù)據(jù),比如GetUserInfo接口可以提供給客戶端BasicInfo、VIPInfo、InvestData、RechargeData、WithdrawData,客戶端可以按需從服務(wù)端拿BasicInfo|VipInfo。

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

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

8. 最后不得不說,在整個公司都搞起了微服務(wù)后,跨部門的一些服務(wù)調(diào)用在商定API的時候難免會有一些扯皮的現(xiàn)象發(fā)生,到底是我傳給你呢還是你自己來拉,這個數(shù)據(jù)對我沒用為什么要在我這里留一下呢?拋開非技術(shù)層面的事情不說,這些扯皮也是有一些技術(shù)手段來化解的:

  • 明確服務(wù)職責(zé),也就明確了服務(wù)應(yīng)該感知到什么不應(yīng)該感知到什么。

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

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

你可能看到這里覺得很頭暈,為什么微服務(wù)需要額外考慮這么多東西,實現(xiàn)的復(fù)雜度一下子上升了。我想說的是我們需要換一個角度來考慮這個事情:

1. 我們不需要在一開始的時候?qū)λ羞壿嫸歼M(jìn)行嚴(yán)密的考慮,先覆蓋核心流程核心邏輯。因為跨服務(wù)成為了服務(wù)的提供方和使用方,相當(dāng)于除了我自己,還有很多其它人會來關(guān)系我的服務(wù)能力,大家會提出各種問題,這對設(shè)計一個可靠的方法是有好處的。

2. 即使在不跨服務(wù)調(diào)用的時候我們把所有邏輯堆積在一起,也不意味著這些邏輯一定是事務(wù)性的,實現(xiàn)嚴(yán)密的,跨服務(wù)調(diào)用往往是一定程度放大了問題產(chǎn)生的可能性。

3. 我們還有服務(wù)框架呢,服務(wù)框架往往會在監(jiān)控跟蹤層次和運維系統(tǒng)結(jié)合在一起提供很多一體化的功能,這將封閉在內(nèi)部的方法邏輯打散暴露出來,對于有一個完善的監(jiān)控平臺的微服務(wù)系統(tǒng),在排查問題的時候你往往會感嘆這是一個遠(yuǎn)程服務(wù)調(diào)用就好了。

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

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

 

消息隊列

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

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

2. 流量洪峰:互聯(lián)網(wǎng)項目的一個特點是有的時候會做一些toC的促銷,免不了有一些流量洪峰,如果我們引入了消息隊列在模塊之間作為緩沖,那么backend的服務(wù)可以以自己既有的舒服的頻次來被動消耗數(shù)據(jù),不會被強壓的流量擊垮。當(dāng)然,做好監(jiān)控是必不可少的,下面再細(xì)說一下監(jiān)控。

3. 模塊解耦:隨著項目復(fù)雜度的上升,我們會有各種來源于項目內(nèi)部和外部的事件(用戶注冊登陸、投資、提現(xiàn)事件等),這些重要事件可能不斷有各種各樣的模塊(營銷模塊、活動模塊)需要關(guān)心,核心業(yè)務(wù)系統(tǒng)去調(diào)用這些外部體系的模塊,讓整個系統(tǒng)在內(nèi)部糾纏在一起顯然是不合適的,這個時候通過MQ進(jìn)行解耦,讓各種各樣的事件在系統(tǒng)中進(jìn)行松耦合流轉(zhuǎn),模塊之間各司其職也相互沒有感知,這是比較適合的做法。

4. 消息群發(fā):有一些消息是會有多個接收者的,接收者的數(shù)量還是動態(tài)的(類似指責(zé)鏈的性質(zhì)也是可能的),在這個時候如果上下游進(jìn)行一對多的耦合就會更麻煩,對于這種情況就更適用使用MQ進(jìn)行解耦了。上游只管發(fā)消息說現(xiàn)在發(fā)生了什么事情,下游不管有多少人關(guān)心這個消息,上游都是沒有感知的。

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

1. 我更傾向于獨立一個專門的listener項目(而不是合并在server中)來專門做消息的監(jiān)聽,然后這個模塊其實沒有過多的邏輯,只是在收到了具體的消息之后調(diào)用對應(yīng)的service中的API進(jìn)行消息處理。listener是可以啟動多份做一個負(fù)載均衡的(取決于具體使用的MQ產(chǎn)品),但是因為這里幾乎沒有什么壓力,不是100%必須。注意,不是所有的service都是需要有一個配到的listener項目的,大多數(shù)公共基礎(chǔ)服務(wù)因為本身很獨立不需要感知到外部的其它業(yè)務(wù)事件,所以往往是沒有l(wèi)istener的,基礎(chǔ)業(yè)務(wù)服務(wù)也有一些是類似的原因不需要有l(wèi)istener。

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

3. 補償?shù)膶崿F(xiàn)是不帶任何業(yè)務(wù)邏輯的,我們再梳理一下補償這個事情。如果A服務(wù)是消息的提供者,B-listener是消息監(jiān)聽器,聽到消息后會調(diào)用B-server中具體的方法handleXXMessage(XXMessage message)來執(zhí)行業(yè)務(wù)邏輯,在MQ停止工作的時候,有一個Job(可配置補償時間以及每次拉取的量)來定期調(diào)用A服務(wù)提供的專有方法getXXMessages(LocalDateTime from, LocalDateTime to, int batchSize)來拉取消息,然后還是(可以并發(fā))調(diào)用B-server的那個handleXXMessage來處理消息。這個補償?shù)腏ob可以重用的可配置的,無需每次為每一個消息都手寫一套,唯一需要多做的事情是A服務(wù)需要提供一個拉取消息的接口。那你可能會說,我A服務(wù)這里還需要維護(hù)一套基于數(shù)據(jù)庫的消息隊列嗎,這個不是自己搞一套基于被動拉的消息隊列了嗎?其實這里的消息往往只是一個轉(zhuǎn)化工作,A一定在數(shù)據(jù)庫中有落地過去一段時間發(fā)生過變動的數(shù)據(jù),只要把這些數(shù)據(jù)轉(zhuǎn)化為Message對象提供出去即可。B-server的handleXXMessage由于是冪等的,所以無所謂消息是否重復(fù)處理,這里只是在應(yīng)急情況下進(jìn)行無腦的過去一段時間的數(shù)據(jù)的依次處理。

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

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

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

7. 需要做好消息監(jiān)控,最最重要的是監(jiān)控消息是否有堆積,有的話需要及時增強下游處理能力(加機器,加線程),當(dāng)然做的更好點可以以熱點拓?fù)鋱D繪制所有消息的流向流速一眼就可以看到目前哪些消息有壓力。你可能會想既然消息都在MQ體系中不會丟失,消息有堆積處理慢一點其實也沒什么問題。是的,消息可以有適當(dāng)?shù)亩逊e,但是不能大量堆積,如果MQ系統(tǒng)出現(xiàn)存儲問題,大量堆積的消息有丟失也是比較麻煩的,而且有一些業(yè)務(wù)系統(tǒng)對于消息的處理是看時間的,過晚到達(dá)的消息是會認(rèn)為業(yè)務(wù)違例進(jìn)行忽略的。

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

定時任務(wù)

定時任務(wù)的需求有那么幾類:

1. 如之前所說,跨服務(wù)調(diào)用,MQ通知難免會有不可達(dá)的問題,我們需要有一定的機制進(jìn)行補償。

2. 有一些業(yè)務(wù)是基于任務(wù)表進(jìn)行驅(qū)動的,有關(guān)任務(wù)表的設(shè)計下面會詳細(xì)說明。

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

詳細(xì)說明一下任務(wù)驅(qū)動是怎么一回事。其實在數(shù)據(jù)庫中做一些任務(wù)表,以這些表驅(qū)動作為整個數(shù)據(jù)處理的核心體系,這套被動的運作方式是最最可靠的,比MQ驅(qū)動或服務(wù)驅(qū)動兩種形態(tài)可靠多,天生必然是可負(fù)載均衡的+冪等處理+補償?shù)降椎模蝿?wù)表可以設(shè)計下面的字段:

  • 自增ID

  • 任務(wù)類型:表明具體的任務(wù)類型,當(dāng)然也可以不同的任務(wù)類型直接做多個任務(wù)表。

  • 外部訂單號:和外部業(yè)務(wù)邏輯的唯一單號關(guān)聯(lián)起來。

  • 執(zhí)行狀態(tài):未處理(等待處理),處理中(防止被其它Job搶占),成功(最終成功了),失?。〞簳r失敗,會繼續(xù)進(jìn)行重試),人工介入(永遠(yuǎn)不會再變了,一定需要人工處理,需要報警通知)

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

  • 處理歷史:每一次的處理結(jié)果,Json的List保存在這里供參考

  • 上次處理時間:最近一次執(zhí)行時間

  • 上次處理結(jié)果:最近一次執(zhí)行結(jié)果

  • 創(chuàng)建時間:數(shù)據(jù)庫維護(hù)

  • 最后修改時間:數(shù)據(jù)庫維護(hù)

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

這里再說明一下Job的幾個設(shè)計原則:

1. Job可以由各種調(diào)度框架來驅(qū)動,比如ElasticJob、Quartz等等,需要獨立項目處理,不能和服務(wù)混在一起,部署啟動多份往往會有問題。當(dāng)然,自己實現(xiàn)一個任務(wù)調(diào)度框架也不是很麻煩的事情,在執(zhí)行的時候來決定Job在哪臺機器來跑,讓整個集群的資源使用更合理。說白了就是兩種形態(tài),一種是Job部署在那里由框架來觸發(fā),還有就是只是代碼在那里,由框架來起進(jìn)程。

2. Job項目只是一層皮,最多有一些配置的整合,不應(yīng)該有實際的業(yè)務(wù)邏輯,不會觸碰數(shù)據(jù)庫,大部分情況就是在調(diào)用具體服務(wù)的API接口。Job項目就負(fù)責(zé)配置和頻次的控制。

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

三馬車都說完了,那么,最后我們來梳理一下這么一套架構(gòu)下整個項目的模塊劃分:

  • 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

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

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

互聯(lián)網(wǎng)架構(gòu):屢試不爽的架構(gòu)三馬車

互聯(lián)網(wǎng)架構(gòu):屢試不爽的架構(gòu)三馬車

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI