雲片 RocketMQ 實戰:Stargate 的前世今生

YunPian發表於2019-08-19

RocketMQ訊息佇列,專業訊息中介軟體,既可為分散式應用系統提供非同步解耦和削峰填谷的能力,同時也具備網際網路應用所需的海量訊息堆積、高吞吐、可靠重試等特性,是應對企業業務峰值時刻必備的技術。

雲片由於業務特點,對訊息佇列的使用十分頻繁,由此雲片服務號從本期推文開始將釋出“雲片RocketMQ實戰”系列文章,講述雲片根據簡訊業務的特點,運用RocketMQ訊息佇列實戰經驗。

本期推文《Stargate的前世今生》,由雲片資深Java開發工程師周凱帆提供。

雲片RocketMQ實戰:Stargate的前世今生
本文字數3025,預計需要閱讀20分鐘

雲片由於其業務特點對應訊息佇列的使用十分頻繁,這裡以雲片簡訊業務為例,簡訊業務的邏輯十分簡單,我們只看主流程,本質就是接受使用者請求,尋找合適的通道,使用cmpp/smpp協議提交給運營商。

雲片RocketMQ實戰:Stargate的前世今生

我們可以發現,使用者的每次請求要一直等待運營商的響應,這樣主要的問題是:

  • 雲片的伺服器再運營商返回時需要一直維護http連線

  • 運營商的處理速率直接限制了雲片的處理速率

  • 雲片的最大併發為所有供應商併發之和

這種情況下我們無法提供穩定的服務。

實際上對於使用者來說並不關心具體流程他們只需將簡訊提交給雲片即可,所以我們可以非同步的處理這些傳送過程,確認收到簡訊後就可以給使用者返回結果以提高響應速度。

雲片RocketMQ實戰:Stargate的前世今生

日常情況我們的系統流量會是一個比較平穩的值X,所以我們提供能滿足的當前流量的消費能力,這樣訊息就不會積壓。

不過隨著流量的增加最終實踐流量會超過我們的消費能力,這樣就會出現簡訊送達延遲,圖中虛線右邊是我們不希望看到的。

雲片RocketMQ實戰:Stargate的前世今生

所以我們在流量到達虛線前提高系統的消費能力。

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

我們可以看到雲片對應訊息佇列的重度依賴,使得在微服務化的時候沒有找到一個適合雲片的好用的annotation元件,當時SpringCloud框架並沒有對RocketMQ支援的相關元件,而RocketMQ官方github上僅有一個不成熟的專案。

對於目前重度依賴RocketMQ的簡訊業務我們需要一個簡單易用並且能夠與我們老專案中程式碼相容的註解,同時還要滿足各團隊的不同需求,於是我們開始一個名為Stargate的元件來支援我們後續的服務化推進。

@StargateProducer
public interface TestProducter {
    @StargateMapper("testaaa")
    SendResult test(@StargateBody TestVO message);
}
`@StargateConsumer`

`public` `class` `TestConsumer {`

`@StargateMapper``(``"testaaa"``)`

`public` `void` `test(``@StargateBody` `TestVO message){`

`//TODO`

`}`

`}`

雲片RocketMQ實戰:Stargate的前世今生

Stargate元件為什麼出現?

事實上在有Stargate之前我們的專案中有一個對RocketMQ SDK的封裝,它確實解決的許多問題,但是面對越來越多的生產者和消費者我們越來越難以維護,甚至於碰到不熟悉的程式碼我曾經花了半小時去找一個生產者的消費者在哪裡,這個消費者被各種繼承重寫,生產者的topic是以一種極其複雜的規則生成的,而且各種配置檔案散落在程式碼的各各角落。

然後舊的元件也十分難以遷移到新的微服務專案中,以至於有的業務線開始自己從新封裝一套元件,而且他們生產者的訊息難以被其他團隊的消費。於是我希望做一套能夠避免這些不良使用習慣的元件,並且提供強大的相容性讓大家遷移過來。

雲片RocketMQ實戰:Stargate的前世今生

設計Stargate的目標是?

所以在設計Stargate的時候主要考慮以下幾點:

  • 簡單易用

註解的方式相比直接使用RocketMQ更加清晰,上手更快,更易用,避免各種不良寫法。

  • 擴充套件性強

SG1提供的擴充套件外掛能夠豐富Stargate的功能,而且這個外掛的開發能力是開放的,後續會提到。實際上外掛功能是在2.0增加應為我發現在限制大家的使用方式後,我需要定製許多的註解來相容各種使用場景,於是我開放了這部分能力讓大家選擇性的開發和使用自己需要的功能。

  • 相容各種老專案

通過編解碼器我們可以相容各種不同的老專案,不需要修改老專案的程式碼。

  • 單元測試更方便

另外再單元測試和開發階段,無需對外部依賴可以方便的進行mock。

雲片RocketMQ實戰:Stargate的前世今生

Stargate元件的價值

最初的時候我簡單的認為這個元件的價值在於提供了一個更方便使用RocketMQ的方式,但後續的開發中慢慢的我發現並不是這樣,目前來說我認為最有價值的兩點在於:

  • 服務間非同步呼叫的“規範”

由於它的擴充套件性和相容性被各各業務線團隊採用,似乎成了一個“規範”,服務之間的通訊多了一種可選項。我們可以把自己的StargateProducer介面定義放在一個jar包中提供給其他人

  • “使用心得”的分享中心

外掛的開發能力使得大家會把自己的使用模式封裝成一個註解釋出出來,這樣許多十分巧妙的使用方法會被髮布出來,而且這些都是開箱即用的,使用者只需要知道這個註解能實現什麼,在釋出要求上讓開發者符上文件,這樣就能形成一個生態,把大家的經驗使用沉澱下來。

另外,Stargate對於程式碼結構方面的幫助也是巨大的,現在我們可以很快速的找到一個生產者的消費者在什麼地方,後續甚至考慮提供IDE外掛來更好的維護這些程式碼。

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

Stargate的初始化

那麼我們接下來,看一下Stargate的這些設計目標是如何實現的,首先我們來看一下元件的入口,我們如何初始化Stargate的。

在應用啟動時我們處理部分註解獲得一個Bean的配置資訊,然後向Spring註冊這些Bean,我們為Producer生成代理類,建立Consumer客戶端監聽訊息並且呼叫StargateConsumer處理這些訊息。

生成這些的Bean的入口是從一個工廠類開始的,通常一個StargateProducer|StargateConsumer的建立會經過以下幾個步驟:

雲片RocketMQ實戰:Stargate的前世今生

事實上Stargate並不負責初始化這些生產者消費者bean,Stargate僅僅提供了建立的過程,我們把這些bean註冊到Spring中然後提供一個工廠方法,由spring在適當的時候建立這些bean,維護這些bean。

這樣我們在spring中就有了這些生產者介面的例項,我們可以把他注入到任何地方然後使用它們傳送訊息。監聽他的消費者就會呼叫事先配置好的StargateConsumer。

雲片RocketMQ實戰:Stargate的前世今生

編解碼器

每個傳送者和消費者都會在收發訊息前進行編解碼,這也是相容原有專案的關鍵。大家可以思考一下,所有專案的都是使用RocketMQ的,本質的直接呼叫SDK的Send方法就能傳送訊息,但是老專案對於如何將一個訊息變成二進位制陣列這是不一樣的,所以我們提供編解碼器的介面讓大家可以替換這些轉換過程的實現。

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

擴充套件介面

但是訊息的編解碼只能實現相容性但是對於擴充套件能力的需求無法滿足,所以我們再初始化過程和傳送消費過程中抽象出了6個介面,讓使用者可以擴充套件自己的邏輯。

這個些介面主要用於處理”註解解析“,”RocketMQ Client建立“,”訊息加工“,我們可以從下圖中看到,工廠在返回一個bean前會呼叫這些介面的實現。訊息收發階段也會呼叫相應的實現。

通常情況下我們實現一個新的註解@DemoModel有這麼幾個流程:

  • 實現註解處理器處理註解資料

  • 實現Client處理器,根據註解解析的資料加工Client

  • 實現訊息處理器,根據註解解析的資料加工訊息

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

上下文

這時會有另一個問題,我們通常的流程在解析註解獲得的資料需要儲存給另外兩個處理器使用,我們當然不希望讓使用者自己處理這些資料,這會增加使用者的使用成本。

於是我們定義了一個上下文的概念,上下文有這幾個特點:

  • 每個生成消費者有自己的上下文

  • 上下文是會被繼承的

舉一個例子,假如我們現在所有的生產者的topic加上一個公共字首,那麼我們只需要在生產者根上下文中的topic加上這個字首的內容,所有的生產者都會有這個字首。

事實上在Stargate2.0開始提供擴充套件功能後,我停止了對core專案的功能迭代,所有的新功能以外掛方式在一個名為SG1的專案的釋出,而每個使用者也可以擴充套件自己的外掛上傳到SG1中,以此期望形成一個生態。

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

另外在雲片國際版YCloud上線之後我們又有一些新的挑戰,YCloud主要服務海外客戶我們的伺服器並不在國內,但是我們的訊息需要提交回國內節點消費,而且MQ都部署在國內,從香港節點到國內節點的延時讓人無法接受,因為這個延時是使用者能感知到的,所以我的目標是把這個延時交給消費者來承擔。

雲片RocketMQ實戰:Stargate的前世今生

如果簡單的在部署一套叢集或許實施起來是最快的,但是這樣成本非常大,而且消費者1和消費者2的負載通常是不均衡的,如果有了第三的機房,難道每個消費者都要部署3套?

雲片RocketMQ實戰:Stargate的前世今生

所以我們的方向是在香港節點部署一個broker,重寫客戶端的佇列選擇器,讓生產者找出離自己最近的broker中的佇列,而消費者消費所有佇列。

雲片RocketMQ實戰:Stargate的前世今生

其實這裡對應消費者也是一樣的,我們可以通過改變消費者的客戶端負載均衡器來消費指定的佇列,這樣我們就能實現一個隔離的環境。

在後續的發展中我們將消費者使用同樣的方式去指定消費指定的broker上的佇列,於是就形成了圖中這樣的隔離的環境,這樣做的好處是:

  • 多個環境隔離正常訊息不互通,達到隔離的目標

  • 多個環境的broker仍然是一體的,如果一個消費者出現故障,另一個消費者可以代替

雲片RocketMQ實戰:Stargate的前世今生

如果發現消費者1出現異常的話,可以臨時讓消費者2代替消費者1的工作保證功能正常,但是目前來說這部分功能仍然無法實現,應為我們需求有一個指揮中心告訴消費者2去消費環境1的訊息。

雲片RocketMQ實戰:Stargate的前世今生

雲片RocketMQ實戰:Stargate的前世今生

為了實現生產者消費者之間的協作,我們需要一個指揮中心,去收集和協調全部Stargate應用的工作。

雲片RocketMQ實戰:Stargate的前世今生

StargateCommand會充當一個指揮中心,但是它只是一個協調機制,如果沒有它Stargate應用仍然會按照其原型設定的方式去執行。

相關文章