如果你的簡歷中有寫到MQ,那么面試官一般會問到如下幾個問題,至少我在面試中經常常被問到,所以今天總結一下,有不對的地方還望多多包涵:
首先第一個問題,為什么要用MQ?
如果這個問題你都沒考慮過,那么說明你只是一個單純會用MQ技術的一個coder,而不是一個會獨立思考的程序員,如果被面試官問到這個問題,你都沒有回答上來,那么你的第一印象肯定會很差。
一、為什么要用MQ?
(1)解耦:如果多個模塊或者系統中,互相調用很復雜,維護起來比較麻煩,但是這個調用又不是同步調用,就可以運用MQ到這個業務中。
(2)異步:這個很好理解,比如用戶的操作日志的維護,可以不用同步處理,節約響應時間。
(3)削峰:在高峰期的時候,系統每秒的請求量達到 5000,那么調用 MySQL 的請求也是 5000,一般情況下 MySQL 的請求大概在 2000 左右,那么在高峰期的時候,數據庫就被打垮了,那系統就不可用了。此時引入MQ,在系統 A 前面加個 MQ,用戶請求先到 MQ,系統 A 從 MQ 中每秒消費 2000 條數據,這樣就把本來 5000 的請求變為 MySQL 可以接受的請求數量了,可以保證系統不掛掉,可以繼續提供服務。MQ 里的數據可以慢慢的把它消費掉。
二、使用了MQ會有什么問題?
這個問題記住一個降低一個增加即可。
(1)降低了系統可用性 (2)增加了系統的復雜性
三、怎樣選型MQ?
如果你被問及這個問題,面試官主要是想看你選型的時候是否會比較架構的區別,是否滿足業務需求。
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
單機吞吐量 | 萬級 | 萬級 | 十萬級 | 十萬級 |
topic 數量對吞吐量的影響 | - | - | topic 可以達到幾百,幾千個的級別,吞吐量會有較小幅度的下降 | topic 從幾十個到幾百個的時候,吞吐量會大幅度下降 |
時效性 | 毫秒級 | 微妙級 | 毫秒級 | 毫秒級 |
可用性 | 高 | 高 | 非常高,分布式架構 | 非常高,分布式架構 |
消息可靠性 | 有較低的概率丟失數據 | - | 經過參數優化配置,可以做到 0 丟失 | 經過參數優化配置,消息可以做到 0 丟失 |
功能支持 | 完善 | 並發能力很強,性能極其好,延時很低 | MQ 功能較為完善,還是分布式的,擴展性好 | 功能較為簡單,主要支持簡單的 MQ 功能,在大數據領域的實時計算以及日志采集被大規模使用,是事實上的標准 |
優劣勢總結 | 非常成熟,功能強大;偶爾會有較低概率丟失消息;社區不活躍了 | 性能極其好,延時很低;功能完善;提供管理界面;社區比較活躍;吞吐量較低;使用 erlang 開發源碼閱讀不方便; | 接口簡單易用;吞吐量高;分布式擴展方便;社區還算活躍;經過雙 11 的考驗 | MQ 功能比較少;吞吐量高;分布式架構;可能存在消息重復消費問題;主要適用大數據實時計算以及日志收集; |
個人建議:
中小型公司,技術一般,可以考慮用 RabbitMQ;
大型公司,基礎架構研發實力較強,用 RocketMQ 是很好的選擇
實時計算、日志采集:使用 kafka;
四、怎樣保證MQ的高可用?
RabbitMQ是比較有代表性的,因為是基於主從做高可用性的。以他為例,自行查閱以下模式。
rabbitmq有三種模式:單機模式、普通集群模式、鏡像集群模式。
五、如何保證不被重復消費?
不通的消費隊列都會有一個消費通知的機制,如:
RabbitMQ提供了一個ACK確認消息機制,
rocketMQ返回一個CONSUME_SUCCESS成功標志
(1)比如,你拿到這個消息做數據庫的insert操作。那就容易了,給這個消息做一個唯一主鍵,那么就算出現重復消費的情況,就會導致主鍵沖突,避免數據庫出現臟數據。
(2)再比如,你拿到這個消息做redis的set的操作,那就容易了,不用解決,因為你無論set幾次結果都是一樣的,set操作本來就算冪等操作。
(3)如果上面兩種情況還不行,上大招。准備一個第三方介質,來做消費記錄。以redis為例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄即可。
六、如何保證消費的可靠傳輸性?
每個MQ都要從生產者丟失數據、消費隊列丟失數據、消費丟失數據三個層面回答
(1) 生產者丟失數據
以rabbitMQ為例,rabbitMQ提供了transaction和confirm模式來確保生產者不丟失數據,事務機制,發送消息前,開啟事務(channel.txSelect()),發送消息后出現異常,事務回滾(channel.txRollback()),發送成功提交事務(channel.txCommit())。
上面這種方式吞吐量會下降,因此生產上應該使用confirm模式居多。
一旦channel進入confirm模式,所有在該通道發布上的消息都會被指派一個唯一的ID(從1開始),消息進來如隊列后,rabbitMq會立即發送一個ACK給生產者,其中包含了消息的唯一ID,這就使得生產者知道消息已經到達了消息隊列里。如果消息隊列沒能處理該消息,則會發送一個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給生產者,如果生產者收不到ack信息,會重發信息。
如何持久化硬盤:
①將queue的持久化標識durable設置為true,
②發送消息的時候將deliveryMode=2
這樣設置就算rabbitMQ掛了,重啟后也能恢復數據
(3) 消費者丟失數據
消費者丟失數據一般都是以為采用了自動確認消息模式。消費者收到消息后,rabbitMQ會立即從隊列里面刪除該消息,這是情況如果消費者出現異常而沒有及時處理該消息就會丟失數據。
解決方案:采用手動確認消息。
七、如何保證消息的時序性?
先進先出隊列,比如linkedBlockingQueue。