hi,我是桑小榆,坐在電腦桌旁肝了幾小時的linux服務實作負載均衡等等,乘著還有點時間把訊息中間件的內容整理了下,比如現有ActiveMQ、RabbitMQ、RocketMQ、Kafka等常見的訊息中間件的各有千秋,以及運用較多的RabbitMQ為例出現的高頻知識內容,
公司生產環境用的是什么訊息中間件?
你可以說下你們公司選用的是什么訊息中間件,比如用的是RabbitMQ,然后可以初步給一些你對不同MQ中間件技術的選型分析,
ActiveMQ:然后你可以說說RabbitMQ,他的好處在于可以支撐高并發、高吞吐、性能很高,同時有非常完善便捷的后臺管理界面可以使用,
另外,他還支持集群化、高可用部署架構、訊息高可靠支持,功能較為完善,
RabbitMQ:國內各大互聯網公司落地大規模RabbitMQ集群支撐自身業務的案例較多,國內各種中小型互聯網公司RabbitMQ的實踐也比較多,
RabbitMQ的開源社區很活躍,較高頻率的迭代版本,來修復發現的bug以及進行各種優化,因此綜合考慮過后,公司采取了RabbitMQ,
RabbitMQ也有一點缺陷,就是他自身是基于erlang語言開發的,所以導致較為難以分析里面的原始碼,也較難進行深層次的原始碼定制和改造,畢竟需要較為扎實的erlang語言功底才可以,
RocketMQ:RocketMQ,是阿里開源的,經過阿里的生產環境的超高并發、高吞吐的考驗,性能卓越,同時還支持分布式事務等特殊場景,
RocketMQ是基于Java語言開發的,適合深入閱讀原始碼,有需要可以站在原始碼層面解決線上生產問題,包括原始碼的二次開發和改造,
Kafka:Kafka提供的訊息中間件的功能明顯較少一些,相對上述幾款MQ中間件要少很多,
Kafka的優勢在于專為超高吞吐量的實時日志采集、實時資料同步、實時資料計算等場景來設計,
Kafka在大資料領域中配合實時計算技術(比如Spark Streaming、Storm、Flink)使用的較多,但是在傳統的MQ中間件使用場景中較少采用,
Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么優缺點?

綜上,各種對比之后,有如下建議:
一般的業務系統要引入MQ,最早大家都用ActiveMQ,但是現在確實大家用的不多了,沒經過大規模吞吐量場景的驗證,社區也不是很活躍,并不是很推薦;后來大家開始用RabbitMQ,但是確實erlang語言阻止了大量的Java工程師去深入研究和掌控它,對公司而言,幾乎處于不可控的狀態,但是確實人家是開源的,比較穩定的支持,活躍度也高;
不過現在確實越來越多的公司會去用RocketMQ,確實很不錯,畢竟是阿里出品,但社區可能有突然黃掉的風險,目前RocketMQ已捐給Apache,但 GitHub上的活躍度其實不算高,對自己公司技術實力有絕對自信的,推薦用RocketMQ,否則回去老老實實用RabbitMQ吧,人家有活躍的開源社區,絕對不會黃,
所以中小型公司,技術實力較為一般,技術挑戰不是特別高,用RabbitMQ 是不錯的選擇;大型公司,基礎架構研發實力較強,用RocketMQ是很好的選擇,
如果是大資料領域的實時計算、日志采集等場景,用Kafka是業內標準的,絕對沒問題,社區活躍度很高,絕對不會黃,何況幾乎是全世界這個領域的事實性規范,
解耦、異步、削峰是什么?
解耦:A系統發送資料到BCD三個系統,通過介面呼叫發送,如果E系統也要這個資料呢?那如果C系統現在不需要了呢?A系統負責人幾乎崩潰…A系統跟其它各種復雜的系統嚴重耦合,A系統產生一條比較關鍵的資料,很多系統都需要A系統將這個資料發送過來,
如果使用MQ,A系統產生一條資料,發送到MQ里面去,哪個系統需要資料自己去MQ里面消費,如果新系統需要資料,直接從MQ里消費即可;如果某個系統不需要這條資料了,就取消對MQ訊息的消費即可,
這樣下來,A系統壓根不需要去考慮要給誰發送資料,不需要維護這個代碼,也不需要考慮人家是否呼叫成功、失敗超時等情況,
異步:A系統接收一個請求,需要在自己本地寫庫,還需要在BCD三個系統寫庫,自己本地寫庫要3ms,BCD三個系統分別寫庫要300ms、450ms、200ms,最終請求總延時是 3 + 300 + 450 + 200 = 953ms,接近1s,用戶體驗會非常不好,用戶通過瀏覽器發起請求,如果使用MQ,那么A系統連續發送3條訊息到MQ佇列中,假如耗時5ms,A 系統從接受一個請求到回傳回應給用戶,總時長是 3 + 5 = 8ms,
削峰:減少高峰時期對服務器壓力,
訊息佇列有什么缺點
降低可用性:系統可用性降低本來系統運行好好的,現在你非要加入個訊息佇列進去,那訊息佇列掛了,你的系統不是涼了,因此,系統可用性會降低;
系統復雜度提高:加入了訊息佇列,要多考慮很多方面的問題,比如:一致性問題、如何保證訊息不被重復消費、如何保證訊息可靠性傳輸等,因此,需要考慮的東西更多,復雜性增大,
一致性問題:A系統處理完了直接回傳成功了,用戶以為你這個請求就成功了;實際上是,要是BCD三個系統那里,BD兩個系統寫庫成功了,結果C系統寫庫失敗了,咋整?你這資料就不一致了,
RabbitMQ一般用在什么場景
服務間異步通信
順序消費
定時任務
請求削峰
簡單說RabbitMQ有哪些角色
Broker:簡單來說就是訊息佇列服務器物體,
Exchange:訊息交換機,它指定訊息按什么規則,路由到哪個佇列,
Queue:訊息佇列載體,每個訊息都會被投入到一個或多個佇列
Binding:系結,它的作用就是把exchange和queue按照路由規則系結起來,
Routing Key:路由關鍵字,exchange根據這個關鍵字進行訊息投遞,
Virtual Host:vhost可以理解為虛擬broker,即mini-RabbitMQ server,
其內部均含有獨立的queue、exchange和binding等,但最最重要的是,其擁有獨立的權限系統,可以做到 vhost 范圍的用戶控制,
當然,從RabbitMQ的全域角度,vhost可以作為不同權限隔離的手段,一個典型的例子就是不同的應用可以跑在不同的 vhost 中,
Producer:訊息生產者,就是投遞訊息的程式Consumer:訊息消費者,就是接受訊息的程式,
Channel:訊息通道,在客戶端的每個連接里,可建立多個channel,每個channel代表一個會話任務
RabbitMQ有幾種作業模式
1.simple模式(即最簡單的收發模式)

訊息產生訊息,將訊息放入佇列訊息的消費者(consumer)監聽訊息佇列,如果佇列中有訊息,就消費掉,訊息被拿走后,自動從佇列中洗掉(隱患訊息可能沒有被消費者正確處理,已經從佇列中消失了,造成訊息的丟失,這里可以設定成手動的ack,但如果設定成手動ack,處理完后要及時發送ack訊息給佇列,否則會造成記憶體溢位),
2.work作業模式(資源的競爭)

訊息產生者將訊息放入佇列消費者可以有多個,消費者1,消費者2同時監聽同一個佇列,訊息被消費,C1 C2共同爭搶當前的訊息佇列內容,誰先拿到誰負責消費訊息(隱患:高并發情況下,默認會產生某一個訊息被多個消費者共同使用,可以設定一個開關(syncronize)保證一條訊息只能被一個消費者使用),
3.publish/subscribe發布訂閱(共享資源)

每個消費者監聽自己的佇列;生產者將訊息發給broker,由交換機將訊息轉發到系結此交換機的每個佇列,每個系結交換機的佇列都將接收到訊息,
4.routing路由模式

訊息生產者將訊息發送給交換機按照路由判斷,路由是字串(info)當前產生的訊息攜帶路由字符(物件的方法),交換機根據路由的key,只能匹配上路由key對應的訊息佇列,對應的消費者才能消費訊息;根據業務功能定義路由字串從系統的代碼邏輯中獲取對應的功能字串,將訊息任務扔到對應的佇列中,
業務場景:error通知;EXCEPTION;錯誤通知的功能;傳統意義的錯誤通知;客戶通知;利用key路由,可以將程式中的錯誤封裝成訊息傳入到訊息佇列中,開發者可以自定義消費者,實時接收錯誤;
5.topic 主題模式(路由模式的一種)

#:匹配一個或多個詞舉例:queue.#等于:queue.1/queue.qu.q.m/queue.qu.后面多個單詞匹配
*:匹配不多不少恰好1個詞舉例:queue.*等于:queue.que/queue.qu .后面一個單詞
路由功能添加模糊匹配,訊息產生者產生訊息,把訊息交給交換機交換機根據key的規則模糊匹配到對應的佇列,由佇列的監聽消費者接收訊息消費
如何保證RabbitMQ訊息的順序性?
將原來的一個queue拆分成多個queue,每個queue都有一個自己的consumer,該種方案的核心是生產者在投遞訊息的時候根據業務資料關鍵值(例如訂單ID哈希值對訂單佇列數取模)來將需要保證先后順序的同一類資料(同一個訂單的資料)發送到同一個queue當中,
一個queue就一個consumer,在consumer中維護多個記憶體佇列,根據業務資料關鍵值(例如訂單ID哈希值對記憶體佇列數取模)將訊息加入到不同的記憶體佇列中,然后多個真正負責處理訊息的執行緒去各自對應的記憶體佇列當中獲取訊息進行消費,
訊息怎么路由?
訊息提供方->路由->一至多個佇列訊息發布到交換器時,訊息將擁有一個路由鍵(routing key),在訊息創建時設定,
通過佇列路由鍵,可以把佇列系結到交換器上,
訊息到達交換器后,RabbitMQ會將訊息的路由鍵與佇列的路由鍵進行匹配(針對不同的交換器有不同的路由規則);
常用的交換器主要分為一下三種:
fanout:如果交換器收到訊息,將會廣播到所有系結的佇列上,
direct:如果路由鍵完全匹配,訊息就被投遞到相應的佇列,
topic:可以使來自不同源頭的訊息能夠到達同一個佇列,
使用topic交換器時,可以使用通配符,
如何保證訊息不被重復消費?
題型分析:為什么會出現訊息重復?
訊息重復的原因有兩個:1.生產時訊息重復,2.消費時訊息重復,
分析重復消費原因:生產時訊息重復由于生產者發送訊息給MQ,在MQ確認的時候出現了網路波動,生產者沒有收到確認,實際上MQ已經接收到了訊息,這時候生產者就會重新發送一遍這條訊息,
生產者中如果訊息未被確認,或確認失敗,我們可以使用定時任務+(redis/db)來進行訊息重試,
消費時訊息重復,消費者消費成功后,再給MQ確認的時候出現了網路波動,MQ沒有接收到確認,為了保證訊息被消費,MQ就會繼續給消費者投遞之前的訊息,這時候消費者就接收到了兩條一樣的訊息,
解決方案:讓每個訊息攜帶一個全域的唯一ID,即可保證訊息的冪等性消費者獲取到訊息后先根據id去查詢redis/db是否存在該訊息,如果不存在,則正常消費,消費完畢后寫入redis/db,如果存在,則證明訊息被消費過,直接丟棄,
如何確保訊息接收方消費了訊息?
發送方確認模式:將信道設定成confirm模式(發送方確認模式),則所有在信道上發布的訊息都會被指派一個唯一的ID,
一旦訊息被投遞到目的佇列后,或者訊息被寫入磁盤后(可持久化的訊息),信道會發送一個確認給生產者(包含訊息唯一ID),
如果RabbitMQ發生內部錯誤從而導致訊息丟失,會發送一條nack(notacknowledged,未確認)訊息,發送方確認模式是異步的,生產者應用程式在等待確認的同時,可以繼續發送訊息,當確認訊息到達生產者應用程式,生產者應用程式的回呼方法就會被觸發來處理確認訊息,
接收方確認機制:消費者接收每一條訊息后都必須進行確認(訊息接收和訊息確認是兩個不同操作),
只有消費者確認了訊息,RabbitMQ才能安全地把訊息從佇列中洗掉,這里并沒有用到超時機制,RabbitMQ僅通過Consumer的連接中斷來確認是否需要重新發送訊息,
也就是說,只要連接不中斷,RabbitMQ給了Consumer足夠長的時間來處理訊息,保證資料的最終一致性,
列舉幾種特殊情況:如果消費者接收到訊息,在確認之前斷開了連接或取消訂閱,RabbitMQ會認為訊息沒有被分發,然后重新分發給下一個訂閱的消費者,
如果消費者接收到訊息卻沒有確認訊息,連接也未斷開,則RabbitMQ認為該消費者繁忙,將不會給該消費者分發更多的訊息,(可能存在訊息重復消費的隱患,需要去重)
如何保證RabbitMQ訊息的可靠傳輸?
題型分析:訊息不可靠的情況可能是訊息丟失,劫持等原因;丟失又分為:生產者丟失訊息、訊息串列丟失訊息、消費者丟失訊息,
生產者丟失訊息:從生產者弄丟資料這個角度來看,RabbitMQ提供transaction和confirm模式來確保生產者不丟訊息,
事務機制:發送訊息前,開啟事務channel.txSelect(),然后發送訊息,如果發送程序中出現什么例外,事務就會回滾channel.txRollback(),如果發送成功則提交事務channel.txCommit(),
然而,這種方式有個缺點:吞吐量下降,
confirm模式:一旦channel進入confirm模式,所有在該信道上發布的訊息都將會被指派一個唯一的ID(從1開始),一旦訊息被投遞到所有匹配的佇列之后;rabbitMQ就會發送一個ACK給生產者(包含訊息的唯一ID),這就使得生產者知道訊息已經正確到達目的佇列了;如果rabbitMQ沒能處理該訊息,則會發送一個Nack訊息給你,你可以進行重試操作,
訊息佇列丟資料:處理訊息佇列丟資料的情況,一般是開啟持久化磁盤的配置,
這個持久化配置可以和confirm機制配合使用,你可以在訊息持久化磁盤后,再給生產者發送一個Ack信號,這樣,如果訊息持久化磁盤之前,RabbitMQ陣亡了,那么生產者收不到Ack信號,生產者會自動重發,
為什么不應該對所有的message都使用持久化機制?
首先,必然導致性能的下降,因為寫磁盤比寫RAM慢的多,message的吞吐量可能有10倍的差距,
其次,message的持久化機制用在RabbitMQ的內置cluster方案時會出現“坑爹”問題,
矛盾點在于,若message設定了persistent屬性,但queue未設定durable 屬性,那么當該queue的owner node出現例外后,在未重建該queue前,發往該queue的message將被blackholed,
若message設定了persistent屬性,同時queue也設定了durable屬性,那么當queue的owner node例外且無法重啟的情況下,則該queue無法在其他node上重建,只能等待其owner node重啟后,才能恢復該queue的使用,而在這段時間內發送給該queue的message將被blackholed,
所以,是否要對message進行持久化,需要綜合考慮性能需要,以及可能遇到的問題,若想達到100,000條/秒以上的訊息吞吐量(單RabbitMQ服務器),則要么使用其他的方式來確保message的可靠delivery,要么使用非常快速的存盤系統以支持全持久化(例如使用SSD),
另外一種處理原則是:僅對關鍵訊息作持久化處理(根據業務重要程度),且應該保證關鍵訊息的量不會導致性能瓶頸,
如何保證RabbitMQ高可用的?
RabbitMQ是比較有代表性的,因為是基于主從(非分布式)做高可用性的,我們就以RabbitMQ為例子講解第一種M的高可用性怎么實作,
RabbitMQ 有三種模式:
單機模式、普通集群模式、鏡像集群模式,
單機模式:就是Demo級別的,一般就是學習時候用的,沒人在生產環境使用單機模式,
普通集群模式:意思就是在多臺機器上啟動多個RabbitMQ實體,每個機器啟動一個,
你創建的queue,只會放在一個RabbitMQ實體上,但是每個實體都同步queue的元資料,
元資料可以認為是queue的一些配置資訊,通過元資料,可以找到queue所在實體,
你消費的時候,實際上如果連接到了另外一個實體,那么那個實體會從queue所在實體上拉取資料過來,
這方案主要是提高吞吐量的,就是說讓集群中多個節點來服務某個queue的讀寫操作,
鏡像集群模式:這種模式,才是所謂的RabbitMQ的高可用模式,
跟普通集群模式不一樣的是,在鏡像集群模式下,你創建的queue,無論元資料還是queue里的訊息都會存在于多個實體上,
就是說,每個RabbitMQ節點都有這個queue的一個完整鏡像,包含queue的全部資料的意思,
然后每次你寫訊息到queue的時候,都會自動把訊息同步到多個實體的queue上,
RabbitMQ有很好的管理控制臺,就是在后臺新增一個策略,這個策略是鏡像集群模式的策略,
指定的時候是可以要求資料同步到所有節點的,也可以要求同步到指定數量的節點,
再次創建queue的時候,應用這個策略,就會自動將資料同步到其他的節點上去了,
這樣的好處在于,你任何一個機器宕機了,其它機器(節點)還包含了這個queue的完整資料,別的consumer都可以到其它節點上去消費資料,
壞處在于,第一,開銷比較大,影響性能,訊息需要同步到所有機器上,導致網路帶寬壓力和消耗很重,
RabbitMQ一個queue的資料都是放在一個節點里的,鏡像集群下,也是每個節點都放這個queue的完整資料,
如何解決訊息佇列的延時以及過期失效問題?訊息佇列滿了以后該怎么處理?有幾百萬訊息持續積壓幾小時,說說怎么解決?
一般常見于,消費端每次消費之后要寫mysql,結果mysql掛了,消費端掛那不動了,或者是消費端出了個什么岔子,導致消費速度極其慢,
一般這個時候,只能臨時緊急擴容了,具體操作步驟和思路如下:
先修復consumer的問題,確保其恢復消費速度,然后將現有consumer都停掉,
再新建一個topic,partition是原來的10倍,臨時建立好原先10倍的queue 數量,
然后寫一個臨時的分發資料的consumer程式,這個程式部署上去消費積壓的資料,消費之后不做耗時的處理,直接均勻輪詢寫入臨時建立好的10倍數量的queue,
接著臨時征用10倍的機器來部署consumer,每一批consumer消費一個臨時queue的資料,這種做法相當于是臨時將queue資源和consumer資源擴大10倍,以正常的10倍速度來消費資料,等快速消費完積壓資料之后,得恢復原先部署的架構,重新用原先的consumer機器來消費息,
必須考慮的特殊情況:
1.訊息過期失效了
假設你用的是RabbitMQ,RabbtiMQ是可以設定過期時間的,也就是 TTL,
如果訊息在queue中積壓超過一定的時間就會被RabbitMQ給清理掉,這個資料就沒了,
這個情況下,就不是說要增加consumer消費積壓的訊息,因為實際上沒啥積壓,而是丟了大量的訊息,
16、RabbitMQ中訊息可能有的幾種狀態?
alpha:訊息內容(包括訊息體、屬性和headers)和訊息索引都存盤在記憶體中,
beta:訊息內容保存在磁盤中,訊息索引保存在記憶體中,
gamma:訊息內容保存在磁盤中,訊息索引在磁盤和記憶體中都有,
delta:訊息內容和索引都在磁盤中,
如何理解RabbitMQ死信佇列?
DLX,全稱為Dead-Letter-Exchange,死信交換器,死信郵箱,
當訊息在一個佇列中變成死信(dead message) 之后,它能被重新被發送到另一個交換器中,這個交換器就是DLX,系結DLX的佇列就稱之為死信佇列,
導致的死信的幾種原因?
訊息被拒(Basic.Reject/Basic.Nack)且requeue = false,訊息TTL過期,
佇列滿了,無法再添加,

▲ 天冷冷的,我的心是冰冰的
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/498988.html
標籤:其他
上一篇:微服務之服務網關
