1 為什么要使用消息隊列
最主要得應用場景:解耦 異步 削峰
(1)解耦
傳統模式:系統間得耦合度強 如系統A直接調用系統B系統C得代碼,如果再有系統D接入,則系統A還要修改代碼。
中間件模式:系統A將消息寫入消息隊列,系統B,系統C 訂閱消息隊列,如果再有系統D介入,直接訂閱消息隊列即可 系統A不必修改代碼
(2)異步
傳統模式:一些非必要得業務邏輯以同步得方式運行浪費時間
中間件模式:將消息寫入消息隊列 一些非必要得業務邏輯以異步得方式運行 提高響應速度
(3)削峰
傳統模式:并發量大得時候所有請求全部到數據庫,造成數據庫連接異常
中間件模式:系統慢慢得按照數據庫能處理得并發量從消息隊列中慢慢拉去消息。在生產環境中這種短暫得高峰期積壓是允許的。
2? 使用消息隊列的缺點
? (1) 系統的可用性降低
? ? ?消息隊列會掛掉 一但掛掉 就會影響可用性
(2) 系統復雜性增加?
考慮一致性問題 保證消息不被重復消費 保證消息可靠傳輸
3? 消息隊列選型
四種主流消息隊列:ActiveMQ RabbitMQ? RocketMQ? Kafka?
去ActiveMQ 社區看看MQ更新頻率(幾個月更新一次)
去RabbitMQ社區看看RabbitMQ的更新頻率(一月兩次)
建議:
(1)中小型軟件公司,建議選RabbitMQ ,一方面erlang語言天生具備高并發的特性,而且它的管理界面用起來十分方便。(定制erlang開發少 但 rabbitMQ社區活躍 可以解決開發中遇到的bug 這點對于中小型公司來書十分重要,
中小型軟件公司 數據量不是特別大(跟互聯網公司比) 應首選功能比較完備的,所以kafka排除。
Rocketmq 是阿里出品,如果阿里放棄維護Rocketmq ,中小型公司一般抽不出人來進行rocketmq的定制化開發。
(2)大型軟件公司,根據具體使用在RocketMq和kafka 之間二選一。一方面,大型軟件公司具有足夠的資金搭建分布式環境,也具備足夠大的數據量,針對rocketMq,大型軟件公司也可以抽出人手對rocketMQ 進行定制化開發(java開發)。至于kafka.根據業務場景的選擇,如果有日志采集功能,可定是首選kafka了。具體該選那個,看使用場景
4 保證消息隊列的高可用
以RocketMQ為例 他的集群就有多master模式、多master 多slave異步復制模式、多master多slave同步雙寫模式。
通訊過程:Producer與NameServer集群中的其中一個節點(隨機選擇)建立長連接,定期從NameServer獲取Topic路由信息,并向提供Topic服務的Broker Master 建立長連接,且定時向Broker發送心跳。Producer 只能將消息發送到 Broker Master,但是Consumer則不一樣,它同時和提供Topic服務的Master和Slave 建立長連接,既可以從Broker Maser 訂閱消息,也可以從Broker Slave 訂閱消息。
以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也有普通集群和鏡像集群模式
5? 保證消息不被重復消費
正常情況下 消費者在消費消息的時候,消費完畢后會發送一個確認消息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發送的確認信息形式不同,RabbitMQ時發送一個ACK確認消息,RocketMQ 是返回一個CONSUME_SUCCESS成功標志,kafka實際上有個offset概念,就是每一個kafka消息都有一個offset,kafka消費過消息后,需要提交offset,讓消息隊列知道自己已經消費過了。所以造成重復消費的原因一般是因為網絡傳輸等故障,確認信息沒有傳到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將該消息分發給其他的消費者。
分業務場景解決
(1)如果是做insert數據庫的操作消息 (創建主鍵解決,多次插入不成功 主鍵沖突)
(2) 冪等性的操作 比如set? 多次操作 等同一次操作
(3) 準備一個第三方介質,來做消費記錄。以redis為例 給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis 那消費者開始消費前,先去redis中查詢有沒有消費記錄即可。
6 保證消費的可靠性傳輸
從三個角度分析:生產者弄丟數據 消息隊列東丟數據 消費者弄丟數據
RabbitMQ:
(1)生產者丟失數據
提供Transacton 和confir嗎模式來確保生產者不丟消息
Transacton? 模式發消息前開啟事務(channel.txSelect()),然后發送消息,如果發送過程中有什么異常,事務就會回滾(channel,txRollback()),如果發送成功則提交事務(channel.txCommit()).然而缺點就是吞吐量下降了。因此上產上用confirm模式的居多。一但channel 進入confirm 模式,所有該信道上面發送的消息都將會指派一個唯一的ID(從1開始),一但消息被投遞到所有匹配的隊列之后,rabbitMQ就會發送一個ACK給生產者(包含消息的唯一ID),這就使生產這知道消息已經到達消息隊列了。如果RabbitMQ沒有處理該消息,則會發送一個Nack消息給你,你可以進行重試操作。處理Ack和Nack的代碼如下
channel.addConfirmListener(new ConfirmListener(){
@Override
public void handleNack(long deliveryTag,boolean multiple)throws IOException{
System.out.println("nack:deliveryTag="+deliveryTag+"multiple:"+multiple);
}
@Override
public void handleAck(long deliveryTag,boolean multiple) throws IOException{
System.out.println("ack:deliveryTag="+deliveryTag+"multiple:"+multiple);
}
});
(2)消息隊列丟失數據
處理消息隊列丟失數據的情況,一把是開啟持久化磁盤的配置。這個持久話配置可以和confirm 機制配合使用,你可以在持久化磁盤之后,再給生產這發送一個Ack信號。這樣如果持久化磁盤之前,rabbitMQ陣亡了,那么生產者收不到Ack信號,生產者會自動重發。
持久化一般分兩部
《1》 將queue的持久化標識durable設置為true,則代表一個持久化隊列
《2》發送消息的時候將deliveryMode=2
這樣設置之后,rabbitMQ就算掛掉了,重啟后也能恢復數據
(3)消費者丟數據
消費者丟數據一般是因為采用了自動確認消息模式,這種模式下,消費者會自動確認收到信息。這時rabbitMQ會立即將消息刪除,這種情況下如果消費者出現異常而沒能處理該消息,就會丟失該消息
解決方案:采用手動確認消息即可
Kafka
Producer 在發布消息到某個partition時,先通過ZooKeeper 找到該Partition的leader,然后無論該Topic的Replication Tactor為多少(也即 該partition 有多少個Replica),Producer只將該消息發送到該Partition的Leader.Leader會將該消息寫入其本地log.每個Follower都從Leader中pull數據。
針對上述情況得出分析
(1)生產者丟失數據
在kafka生產中,基本都有一個leader和多個follwer.follwer會去同步liader的信息。因此為了避免生產這丟數據,做如下亮點配置
《1》第一個配置要在Producer端設置acks=all .這個配置保證了,follwer同步完成后,才認為i消息發送成功。
《2》在priducer端設置retries=MAX,一但寫入失敗,則無限重試
(2)消息隊列丟失數據?
針對消息隊列丟失數據的情況,無外乎就是,數據還沒同步,leader就掛掉了,這時zookpeer會其他的follwer切換為leader,那數據就丟失了。針對這種情況,應該做兩個配置。
《1》 replication。factor參數,這個值必須大于1 即 要求每個partition必須有兩個副本
《2》min.insync.replicas參數這個值必須大于1,這個是要求一個leader至少感知到有至少一個follower還跟自己保持聯系
這兩個配置加上上面生產者的配置聯合起來用基本可以確保kafka不丟數據
(3)消費者丟數據
這種情況一般是自動提交了offset,然后你處理程序過程中掛了。kafka以為你處理好了。
offset:指的是kafka的topic中的每個消費組消費的下標。簡單的來說就是一條消息對應一個offset下標,每次消費數據的時候如果提交offset,那么下次消費就會從提交的offset加1 那里消費。offset 消費下標從0開始 ,如10條消費了7條并且提交了,那么kafka服務器記錄的提交的offset的下表就是6,那么下次消費的時候offset就從7開始。
解決方案跟rabbitMq一樣改成手動提交即可。
ActiveMQ
略
RocketMQ
略
7 保證消息的順序性
通過某種算法,將需要保持先后順序的消息放到同一個消息隊列(kafka 就是partition,rabbitMq中就是queue)然后只用一個消費者去消費該隊列。
注:本文來源于網絡 作者孤獨煙 如有侵權請聯系我刪除即可