作者:上善若水;
來源:碼農有道

說到消息中間件,身在互聯網的童鞋們肯定下意識的就是高併發,高性能io調度等浮現在腦海,但是對應用來說,可能他的作用遠不止性能這麼簡單,尤其是對與交易,金融打交道的業務平臺來說。

ok,下面給大家介紹一下金融交易平臺中,哪些場景是需要我們用到消息中間件的?爲什麼要使用?怎麼設計中間件私有云讓開發比較爽?(鑑於不同同學語言擅長不相同,這裏只聊設計原理和機制方面的內容,本文會涉及市面上流行的開源產品,如activemq、rabbitmq、kafka、metaq等)

消息中間件的作用就是用來異步化併發能力的一個載體,不僅如此,它仍然需要在架構上保證很多能力,高可用,高併發,可擴展,可靠性,完整性,保證順序等,光是這些都已經讓各種設計者比較頭疼了; 更有一些變態的需求,例如慢消費,不可重複等需要花的設計代價是相當高的,所以不要盲目的迷信開源大牛,對於很多機制,幾乎都要重建;建立一個符合所有業務,好用,通用的私有云,沒那麼簡單。

如果說一個支付系統每天要處理億級業務單的話,那麼消息中間件的處理能力至少得達到近百億,因爲很多系統都是依賴於中間件的集羣能力,並且要保證不能出錯,so,讓我們從架構的一些層面上來一點點來分析中間件是怎麼做到的?

高可用

高可用是一個永恆的話題,這個也是在金融界是否靠譜的一個衡量標準,要知道,金融界的架構師們會想方設法的讓數據不會丟失,哪怕是一條數據,但是事實上,這個東西從理論上來講,得靠人品。。。這個不是忽悠。

舉一個例子來說,互聯網數據架構中一份數據至少要存三份才叫高保證,但是事實上,谷歌的比利時數據中心在8.13日遭到雷劈後數據中心永久丟失0.000001%,不到0.05%的磁盤未能修復,這裏要說的是,天時地利人和很重要,極限條件下沒有什麼不可能,一定會有架構漏洞,下面看一下mq高可用的一般做法:

下圖是activemq的HA方案:

聊聊開源消息中間件的架構和原理

activemq的HA 通過master/slave的failover進行託管,其中主從切換可以通過多種方式進行切換:

1:通過一個nfs或其它共享磁盤設備進行一個共享鎖,通過對共享文件鎖的佔有,來標記master的狀態,當m掛掉以後,對應的slave會佔有shared_lock而轉換爲master

2:通過zookeeper進行集羣的管理,比較常見,這裏不再介紹

下圖是metaq的HA方案


聊聊開源消息中間件的架構和原理


如上圖,如出一轍,也是通過zk管理broker的主從結點。

當然這個只是其中的一個failover機制,只能保證消息在broker掛掉時轉換到slave上,但是不能保證在這中間過程中的消息的丟失

當消息從broker流經時,很有可能因爲宕機或是其它硬件故障而導致後,就有可能導致消息丟失掉,這個時候,就需要有相關的存儲介質對消息的進行一個保障了

那麼我們舉kafka的存儲機制作爲一個參考,要知道消息中間件對存儲的依賴不但要求速度快,並且要求IO的需求成本非常低,kafka自己設計了一套存儲機制來滿足上述的需求,這裏簡單介紹一下。

首先kafka中的topic在分佈式部署下分做多個分區,分區的就相當於消息進行了一個負載,然後由多臺機器進行路由,舉個例子: 一個topic,debit_account_msg會切分爲 debit_account_msg_0, debit_account_msg_1, debit_account_msg_2。。。等N個partition,每個partition會在本地生成一個目錄比如/debit_account_msg/topic

裏面的文件會分出很多segment,每個segment會定義一個大小,比如500mb一個segment,一個file分爲index和log二個部分

00000000000000000.index

00000000000000000.log

00000000000065535.index

00000000000065535.log

其中數字代表msgId的值的索引起點,對應的數據結構如下圖:


聊聊開源消息中間件的架構和原理


1,0代表msgId爲1的消息,0代表在這個文件中的偏移量,讀取到這個文件後再尋找到查詢到對應的segment log文件讀取對應的msg信息,對應的信息是一個固定格式消息體:


聊聊開源消息中間件的架構和原理


顯然,這種機制單純應用肯定是不能滿足高併發IO的,首先二分查找segmentfile,然後再通過offset找到對應數據,再讀取msgsize,再讀取報體,至少是4次磁盤io,開銷較大,但是在拉取時候是使用的順序讀取,基本上影響不大。

除了要上面所說的查詢外。其實在寫入磁盤之前都是在os上的pagecache上進行讀寫的,然後通過異步線程對硬盤進行定時的flush(LRU策略),但其實這個風險很大的,因爲一旦os宕掉,會導致數據的丟失,尤其是在進行慢消費多積壓很多數據的情況下,但是kafka他弟metaq對這塊已經做了很多改造,對這些分區文件進行了replication機制(阿里內部使用),所以在這個層面上再怎麼遭雷劈丟消息的機率就會比較小了,當然也不排除主機房光纜被人挖掉會有什麼樣的情況發生。

說了這麼多,似乎看起來的比較完美和美好,但是實際上運維成本似乎很大。因爲這些都是文件,一旦發生問題,需要人工去處理起來相當麻煩,而且是在一臺一臺機器上,需要比較大的運維成本去做一些運維規範以及api調用設施等。

所以,在這塊我們可以通過改造,將數據存儲在一些nosql上,比如mongoDB上,當然mysql也是可以,但是io能力和nosqldb完全不在一個水平線上,除非我們有強烈的事務處理機制,而在金融裏的確對這塊要求比較相當嚴謹。像在支付寶後面就使用了metaq,因爲之前的中間件tbnotify在處理慢消費的情況下會很被動,而metaq在這塊會有極大的優勢,爲什麼,請聽後面分解。

高併發

最開始大家使用mq很大部分工程師都用於解決性能和異步化的問題,其實對於同一個點來說,一個io調度其實並不是那麼耗資源,廢話少說讓我們看下mq裏的一些高併發點,首先在這裏先介紹一下幾個比較有名的中間件背景:

activemq當時就是專門的企業級解決方案,遵守jee裏的jms規範,其實性能也還是不錯的,但是拉到互聯網裏就是兔子抱西瓜,無能爲力了

rabbitmq採用erlang語言編寫,遵守AMQP協議規範,更具有跨平臺性質,模式傳遞模式要更豐富,並且在分佈式

rocketmq(metaq3.0現今最新版本, kafka也是metaq的前身,最開始是linkedIn開源出來的日誌消息系統 ),metaq基本上把kafka的原理和機制用java寫了一遍,經過多次改造,支持事務,發展速度很快,並且在阿里和國內有很比較好的社區去做這塊的維護 。

性能比較,這裏從網上找一些數據,僅供參考:


聊聊開源消息中間件的架構和原理


說實話來講,這些數據級別來講,相差沒有太離譜,但是我們可以通過分析一些共性來講,這些主要性能差別在哪裏?

rocketmq是metaq的後繼者,除了在一些新特性和機制方面有改進外,性能方面的原理都差不多,下面說下這些高性能的一些亮點:

  • rocketmq的消費主要採用pull機制,所以對於broker來講,很多消費的特性都不需要在broker上實現,只需要通過consumer來拉取相關的數據即可,而像activemq,rabbitmq都是採取比較老的方式讓broker去dispatch消息,當然些也是jms或amqp的一些標準投遞方式
  • 文件存儲是順序存儲的,所以來拉消息的時候只需要通過調用segment的數據就可以了,並且consumer在做消費的時候是最大程度的去消費信息,不太可能產生積壓,而且可以通過設置io調度算法,像noop模式,可以提高一些順序讀取的性能 。
  • 通過pagecache去命中在os緩存中的數據達到一個熱消費
  • metaq的批量磁盤IO以及網絡IO,儘量讓數據在一次io中運轉,消息起來都是批量的,這樣對io的調度不太需要消耗太多資源
  • NIO傳輸,如下圖,這個是最初metaq的一個架構,最初metaq使用的是taobao內部的gecko和notify-remoting集成的一些高性能的NIO框架去分發消息


聊聊開源消息中間件的架構和原理


  • 消費隊列的輕量化,要知道我們的消息能力是通過隊列來獲取的


看下面的圖:


聊聊開源消息中間件的架構和原理


metaq在消費的物理隊列上添加了邏輯隊列,隊列對應的磁盤數據是串行化的,隊列的添加不會添加磁盤的iowait負擔,寫入可以順序,但是在讀取的時候仍然需要去用隨機讀,首先是邏輯隊列 ,然後再讀取磁盤,所以pagecache很重要,儘量讓內存大一些,這塊分配就會充分得到利用。

其實做到上面這些已經基本上能保證我們的性能在一個比較高的水平; 但是有時候性能並不是最重要的,最重要的是要和其它的架構特性做一個最佳的平衡,畢竟還有其它的機制要滿足。因爲在業界基本上最難搞定的三個問題:高併發,高可用,一致性是互相沖突的。

可擴展

這是一個老生常談的問題,對於一般系統或是中間件,可以較好的擴展,但是在消息中間件這塊,一直是一個麻煩事,爲什麼?

先說下activemq的擴展起來的侷限性,因爲activemq的擴展需要業務性質,作爲broker首先要知道來源和目的地,但是這些消息如果都是分佈式傳輸的話,就會變的複雜,下面看一下activemq的負載是怎麼玩轉的


聊聊開源消息中間件的架構和原理


我們假設producer去發topicA的消息,如果正常情況下所有的consumer都連到每一個broker上的,辣麼假如broker上有producer上的消息過來,是可以transfer到對應的consumer上的。

但是如果像圖中 broker2中如果沒有對應的消息者連接到上面,這種情況下怎麼辦呢?因爲假設同一個topic的應用系統(producer)和依賴系統 (consumer)節點很多,那又該如何擴容呢?activemq是可以做上圖中正常部分,但是需要改變producer,broker,consumer的對應的配置,相當麻煩。

當然activemq也可以通過multicast的方式來做動態的查找(也有人提到用lvs或f5做負載,但是對於consumer一樣存在較大的問題,而且這種負載配置對於topic的分發,沒實質性作用),但是,仍然會有我說的這個問題,如果topic太大,每個broker都需要連接所有的producer或是consumer, 不然就會出現我說的情況,擴容這方面activemq是相當的麻煩

下面來說一下metaq是如何做這塊事情的,看圖說話


聊聊開源消息中間件的架構和原理


metaq上是以topic爲分區的,在這個層面來講,我們只要配置topic的分區有多少個就好了,這樣切片起來就是有個"業務"概念作爲路由規則;一般一個broker機器上配置有多個topic,每個topic在一個機器上一般是隻有一個分區,假如機器不夠了,也是可以支持多個分區的,一般來說,我們可以通過業務id來取模自定義分區,通過獲取發區參數即可。

聊聊開源消息中間件的架構和原理


metaq的消費者也是通過group(這個分組一般根據partition的能力來配置)負載的方式去partition去拉消息,假如有多的消費者,不需要參與消費。一般線上都是這種情況,因爲畢竟應用服務器要遠大於消息服務器。


聊聊開源消息中間件的架構和原理


另外一種情況,當分區過多時,如下圖

聊聊開源消息中間件的架構和原理


這樣的負載在多依賴的核心消息時,對於服務器broker的要求還是比較高的,畢竟依賴的量較大,另外對於消息具有廣播特點的話,可能會更大,所以對於broker而言,需要高io的硬盤以及大內存做pagecache,真正需要的運算並不需要太大


聊聊開源消息中間件的架構和原理



可靠性

可靠性是消息中間件的重要特性,看下mq是怎麼流轉這些消息的,拿activemq來先來做下參考,它是基於push&push機制。

如何保證每次的消息發送都被消費到?Activemq的生產者發送消息後都需要收到一條broker的ack纔會確認消收到,同樣對於broker到consumer也是同樣的保障。

Metaq的機制也是同樣的,但是broker到consumer是通過pull的方式,所以它的到達保障要看consumer的能力如何,但是一般情況下,應用服務器集羣不太可能出現雪崩效應。

如何保證消息的冪等性?目前來說基本上activemq,metaq都不能保證消息的冪等性,這就需要一些業務來保證了。因爲一旦broker超時,就會重試,重試的話都會產生新的消息,有可能broker已經落地消息了,所以這種情況下沒法保證同一筆業務流水產生二條消息出來

消息的可靠性如何保證?這點上activemq和metaq基本上機制一樣:

生產者保證:生產數據後到broker後必須要持久化才能返回ACK給來源

broker保證:metaq服務器接收到消息後,通過定時刷新到硬盤上,然後這些數據都是通過同步/異步複製到slave上,來保證宕機後也不會影響消費

activemq也是通過數據庫或是文件存儲在本地,做本地的恢復

消費者保證:消息的消費者是一條接着一條地消費消息,只有在成功消費一條消息後纔會接着消費下一條。如果在消費某條消息失敗(如異常),則會嘗試重試消費這條消息(默認最大5次),超過最大次數後仍然無法消費,則將消息存儲在消費者的本地磁盤,由後臺線程繼續做重試。而主線程繼續往後走,消費後續的消息。因此,只有在MessageListener確認成功消費一條消息後,meta的消費者纔會繼續消費另一條消息。由此來保證消息的可靠消費。

一致性

mq的一致性我們討論二個場景:

1:保證消息不會被多次發送/消費

2:保證事務

剛纔上面介紹的一些mq都是不能保證一致性的,爲什麼不去保證?代價比較大,只能說,這些都是可以通過改造源碼來進行保證的,而且方案比較相對來說不是太複雜,但是額外的開銷比較大,比如通過額外的緩存集羣來保證某段時間的不重複性,相信後面應該會有一些mq帶上這個功能。

Activemq支持二種事務,一個是JMS transaction,一個是XA分佈式事務,如果帶上事務的話,在交互時會生成一個transactionId去到broker,broker實現一些TM去分配事務處理,metaq也支持本地事務和XA,遵守JTA標準這裏activemq和metaq的事務保證都是通過redo日誌方式來完成的,基本上一致。

這裏的分佈式事務只在broker階段後保證,在broker提交之前會把prepare的消息存儲在本地文件中,到commit階段纔將消息寫入隊列,最後通過TM實現二階段提交。

小結

像公司內部也有一些性能很不錯的消息中間件,希望後面也能做到開源給到更多的人去使用。針對現在流行的一些消息中間件我們可以針對不同的應用,不同的成本,不同的開發定製不同的架構,當然,這些架構一定是需要我們經過多方面考量。

相关文章