1. 程式人生 > >分布式之消息隊列

分布式之消息隊列

spa log 好的 技術分享 art 消息 為什麽 rocketmq tor

1、為什麽要使用消息隊列?

主要有三個原因:解耦、異步、削峰

(1)解耦

傳統模式:
技術分享圖片
傳統模式的缺點

  • 系統間耦合性太強,如上圖所示,系統A在代碼中直接調用系統B和系統C的代碼,如果將來D系統接入,系統A還需要修改代碼,過於麻煩!

中間件模式:
技術分享圖片
中間件模式的的優點

  • 將消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,從而系統A不需要做任何修改。

(2)異步

傳統模式:
技術分享圖片
傳統模式的缺點

  • 一些非必要的業務邏輯以同步的方式運行,太耗費時間。

中間件模式:
技術分享圖片
中間件模式的的優點

  • 將消息寫入消息隊列,非必要的業務邏輯以異步的方式運行,加快響應速度

(3)削峰

傳統模式
技術分享圖片
傳統模式的缺點

  • 並發量大的時候,所有的請求直接懟到數據庫,造成數據庫連接異常

中間件模式:
技術分享圖片
中間件模式的的優點

  • 系統A慢慢的按照數據庫能處理的並發量,從消息隊列中慢慢拉取消息。在生產中,這個短暫的高峰期積壓是允許的。

2、使用了消息隊列會有什麽缺點?

分析:一個使用了MQ的項目,如果連這個問題都沒有考慮過,就把MQ引進去了,那就給自己的項目帶來了風險。我們引入一個技術,要對這個技術的弊端有充分的認識,才能做好預防。要記住,不要給公司挖坑!
回答:回答也很容易,從以下兩個個角度來答

  • 系統可用性降低:你想啊,本來其他系統只要運行好好的,那你的系統就是正常的。現在你非要加個消息隊列進去,那消息隊列掛了,你的系統不是呵呵了。因此,系統可用性降低
  • 系統復雜性增加:要多考慮很多方面的問題,比如一致性問題、如何保證消息不被重復消費,如何保證保證消息可靠傳輸。因此,需要考慮的東西更多,系統復雜性增大。

但是,我們該用還是要用的。

3、消息隊列如何選型?

1》查看社區的活躍程度

2》選擇適合自己性能的

特性ActiveMQRabbitMQRocketMQkafka
開發語言 java erlang java scala
單機吞吐量 萬級 萬級 10萬級 10萬級
時效性 ms級 us級 ms級 ms級以內
可用性 高(主從架構) 高(主從架構) 非常高(分布式架構) 非常高(分布式架構)
功能特性 成熟的產品,在很多公司得到應用;有較多的文檔;各種協議支持較好 基於erlang開發,所以並發能力很強,性能極其好,延時很低;管理界面較豐富 MQ功能比較完備,擴展性佳 只支持主要的MQ功能,像一些消息查詢,消息回溯等功能沒有提供,畢竟是為大數據準備的,在大數據領域應用廣。

綜合上面的材料得出以下兩點:
(1)中小型軟件公司,建議選RabbitMQ.一方面,erlang語言天生具備高並發的特性,而且他的管理界面用起來十分方便。正所謂,成也蕭何,敗也蕭何!他的弊端也在這裏,雖然RabbitMQ是開源的,然而國內有幾個能定制化開發erlang的程序員呢?所幸,RabbitMQ的社區十分活躍,可以解決開發過程中遇到的bug,這點對於中小型公司來說十分重要。不考慮rocketmq和kafka的原因是,一方面中小型軟件公司不如互聯網公司,數據量沒那麽大,選消息中間件,應首選功能比較完備的,所以kafka排除。不考慮rocketmq的原因是,rocketmq是阿裏出品,如果阿裏放棄維護rocketmq,中小型公司一般抽不出人來進行rocketmq的定制化開發,因此不推薦。
(2)大型軟件公司,根據具體使用在rocketMq和kafka之間二選一。一方面,大型軟件公司,具備足夠的資金搭建分布式環境,也具備足夠大的數據量。針對rocketMQ,大型軟件公司也可以抽出人手對rocketMQ進行定制化開發,畢竟國內有能力改JAVA源碼的人,還是相當多的。至於kafka,根據業務場景選擇,如果有日誌采集功能,肯定是首選kafka了。具體該選哪個,看使用場景。

4、如何保證消息隊列是高可用的?

分析:在第二點說過了,引入消息隊列後,系統的可用性下降。在生產中,沒人使用單機模式的消息隊列。因此,作為一個合格的程序員,應該對消息隊列的高可用有很深刻的了解。如果面試的時候,面試官問,你們的消息中間件如何保證高可用的?你的回答只是表明自己只會訂閱和發布消息,面試官就會懷疑你是不是只是自己搭著玩,壓根沒在生產用過。
以rcoketMQ為例,他的集群就有多master 模式、多master多slave異步復制模式、多 master多slave同步雙寫模式。多master多slave模式部署架構圖:
技術分享圖片
其實博主第一眼看到這個圖,就覺得和kafka好像,只是NameServer集群,在kafka中是用zookeeper代替,都是用來保存和發現master和slave用的。通信過程如下:
Producer 與 NameServer集群中的其中一個節點(隨機選擇)建立長連接,定期從 NameServer 獲取 Topic 路由信息,並向提供 Topic 服務的 Broker Master 建立長連接,且定時向 Broker 發送心跳。Producer 只能將消息發送到 Broker master,但是 Consumer 則不一樣,它同時和提供 Topic 服務的 Master 和 Slave建立長連接,既可以從 Broker Master 訂閱消息,也可以從 Broker Slave 訂閱消息。
那麽kafka呢,為了對比說明直接上kafka的拓補架構圖(也是找的,懶得畫)
技術分享圖片
如上圖所示,一個典型的Kafka集群中包含若幹Producer(可以是web前端產生的Page View,或者是服務器日誌,系統CPU、Memory等),若幹broker(Kafka支持水平擴展,一般broker數量越多,集群吞吐率越高),若幹Consumer Group,以及一個Zookeeper集群。Kafka通過Zookeeper管理集群配置,選舉leader,以及在Consumer Group發生變化時進行rebalance。Producer使用push模式將消息發布到broker,Consumer使用pull模式從broker訂閱並消費消息。
至於rabbitMQ,也有普通集群和鏡像集群模式,自行去了解,比較簡單,兩小時即懂。
要求,在回答高可用的問題時,應該能邏輯清晰的畫出自己的MQ集群架構或清晰的敘述出來。

5、如何保證消息不被重復消費?

分析:這個問題其實換一種問法就是,如何保證消息隊列的冪等性?這個問題可以認為是消息隊列領域的基本問題。換句話來說,是在考察你的設計能力,這個問題的回答可以根據具體的業務場景來答,沒有固定的答案。
回答:先來說一下為什麽會造成重復消費?
??其實無論是那種消息隊列,造成重復消費原因其實都是類似的。正常情況下,消費者在消費消息時候,消費完畢後,會發送一個確認信息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發送的確認信息形式不同,例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標誌,kafka實際上有個offset的概念,簡單說一下(如果還不懂,出門找一個kafka入門到精通教程),就是每一個消息都有一個offset,kafka消費過消息後,需要提交offset,讓消息隊列知道自己已經消費過了。那造成重復消費的原因?,就是因為網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將該消息分發給其他的消費者。
??如何解決?這個問題針對業務場景來答分以下幾點
??(1)比如,你拿到這個消息做數據庫的insert操作。那就容易了,給這個消息做一個唯一主鍵,那麽就算出現重復消費的情況,就會導致主鍵沖突,避免數據庫出現臟數據。
??(2)再比如,你拿到這個消息做redis的set的操作,那就容易了,不用解決,因為你無論set幾次結果都是一樣的,set操作本來就算冪等操作。
??(3)如果上面兩種情況還不行,上大招。準備一個第三方介質,來做消費記錄。以redis為例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄即可。

6、如何保證消費的可靠性傳輸?

kafka

這裏先引一張kafka Replication的數據流向圖
技術分享圖片
Producer在發布消息到某個Partition時,先通過ZooKeeper找到該Partition的Leader,然後無論該Topic的Replication Factor為多少(也即該Partition有多少個Replica),Producer只將該消息發送到該Partition的Leader。Leader會將該消息寫入其本地Log。每個Follower都從Leader中pull數據。
針對上述情況,得出如下分析
(1)生產者丟數據
在kafka生產中,基本都有一個leader和多個follwer。follwer會去同步leader的信息。因此,為了避免生產者丟數據,做如下兩點配置

  1. 第一個配置要在producer端設置acks=all。這個配置保證了,follwer同步完成後,才認為消息發送成功。
  2. 在producer端設置retries=MAX,一旦寫入失敗,這無限重試

(2)消息隊列丟數據
針對消息隊列丟數據的情況,無外乎就是,數據還沒同步,leader就掛了,這時zookpeer會將其他的follwer切換為leader,那數據就丟失了。針對這種情況,應該做兩個配置。

  1. replication.factor參數,這個值必須大於1,即要求每個partition必須有至少2個副本
  2. min.insync.replicas參數,這個值必須大於1,這個是要求一個leader至少感知到有至少一個follower還跟自己保持聯系

這兩個配置加上上面生產者的配置聯合起來用,基本可確保kafka不丟數據

(3)消費者丟數據
這種情況一般是自動提交了offset,然後你處理程序過程中掛了。kafka以為你處理好了。再強調一次offset是幹嘛的
offset:指的是kafka的topic中的每個消費組消費的下標。簡單的來說就是一條消息對應一個offset下標,每次消費數據的時候如果提交offset,那麽下次消費就會從提交的offset加一那裏開始消費。
比如一個topic中有100條數據,我消費了50條並且提交了,那麽此時的kafka服務端記錄提交的offset就是49(offset從0開始),那麽下次消費的時候offset就從50開始消費。
解決方案也很簡單,改成手動提交即可。

7、如何保證消息的順序性?

分析:其實並非所有的公司都有這種業務需求,但是還是對這個問題要有所復習。
回答:針對這個問題,通過某種算法,將需要保持先後順序的消息放到同一個消息隊列中(kafka中就是partition)。然後只用一個消費者去消費該隊列。
有的人會問:那如果為了吞吐量,有多個消費者去消費怎麽辦?
這個問題,沒有固定回答的套路。比如我們有一個微博的操作,發微博、寫評論、刪除微博,這三個異步操作。如果是這樣一個業務場景,那只要重試就行。比如你一個消費者先執行了寫評論的操作,但是這時候,微博都還沒發,寫評論一定是失敗的,等一段時間。等另一個消費者,先執行寫評論的操作後,再執行,就可以成功。
總之,針對這個問題,我的觀點是保證入隊有序就行,出隊以後的順序交給消費者自己去保證,沒有固定套路。

摘自:http://rjzheng.cnblogs.com/

分布式之消息隊列