10分鐘帶你了解開發(fā)說的Mq到底是什么?
以下文章來源于懂Java的測試 ,作者懂Java的測試
前言
日常測試工作中,開發(fā)提到的Mq到底什么意思?本篇文章就分享一下所謂的Mq究竟是什么技術、有什么好處,以及常見的Mq組件有那些。
Mq的優(yōu)點有哪些
Mq(Message Queue)是消息隊列,主要有三大用途,我們拿一個電商系統(tǒng)的下單舉例:
解耦:引入消息隊列之前,下單完成之后,需要訂單服務去調用庫存服務減庫存,調用營銷服務加營銷數據……引入消息隊列之后,可以把訂單完成的消息丟進隊列里,下游服務自己去調用就行了,這樣就完成了訂單服務和其它服務的解耦合。
異步:訂單支付之后,我們要扣減庫存、增加積分、發(fā)送消息等等,這樣一來這個鏈路就長了,鏈路一長,響應時間就變長了。引入消息隊列,除了更新訂單狀態(tài),其它的都可以異步去做,這樣一來就來,就能降低響應時間。
削峰:消息隊列合一用來削峰,例如秒殺系統(tǒng),平時流量很低,但是要做秒殺活動,秒殺的時候流量瘋狂懟進來,我們的服務器,Redis,MySQL各自的承受能力都不一樣,直接全部流量照單全收肯定有問題啊,嚴重點可能直接打掛了。
我們可以把請求扔到隊列里面,只放出我們服務能處理的流量,這樣就能抗住短時間的大流量了。
解耦、異步、削峰,是消息隊列最主要的三大作用。
Mq的缺點有哪些
互聯(lián)網很多技術都是雙刃劍,有利益有弊,同樣Mq也是,它有以下幾個缺點:
系統(tǒng)可用性降低
本來系統(tǒng)運行好好的,現在你非要加入個消息隊列進去,那消息隊列掛了,你的系統(tǒng)不是呵呵了。
比如說一個核心鏈路里面:系統(tǒng)A -> 系統(tǒng)B -> 系統(tǒng)C,然后系統(tǒng)C是通過MQ異步調用系統(tǒng)D的。
看起來很好,你用這個MQ異步化的手段解決了一個核心鏈路執(zhí)行性能過差的問題。
但是你有沒有考慮另外一個問題,就是萬一你依賴的那個MQ中間件突然掛掉了怎么辦?這個還真的不是異想天開,MQ、Redis、MySQL這些組件都有可能會掛掉。
一旦你的MQ掛了,就導致你的系統(tǒng)的核心業(yè)務流程中斷了。本來你要是不引入MQ中間件,那其實就是一些系統(tǒng)之間的調用,但是現在你引入了MQ,就導致你多了一個依賴。一旦多了一個依賴,就會導致你的可用性降低。
因此,一旦引入了MQ中間件,你就必須去考慮這個MQ是如何部署的,如何保證高可用性。
甚至在復雜的高可用的場景下,你還要考慮如果MQ一旦掛了以后,你的系統(tǒng)有沒有備用兜底的技術方案,可以保證系統(tǒng)繼續(xù)運行下去
系統(tǒng)復雜度提高
還是上面那張圖,大家再來看一下:
不知道大家有沒有發(fā)現一個問題,這個鏈路除了MQ中間件掛掉這個可能存在的隱患之外,可能還有一些其他的技術問題。
比如說,莫名其妙的,系統(tǒng)C發(fā)了一個消息到MQ,結果那個消息因為網絡故障等問題,就丟失了。這就導致系統(tǒng)D沒有收到那條消息。
這可就慘了,這樣會導致系統(tǒng)D沒完成自己該做的任務,此時可能整個系統(tǒng)會出現業(yè)務錯亂,數據丟失,嚴重的bug,用戶體驗很差等各種問題。
這還只是其中之一,萬一說系統(tǒng)C給MQ發(fā)送消息,不小心一抽風重復發(fā)了一條一模一樣的,導致消息重復了,這個時候該怎么辦?
可能會導致系統(tǒng)D一下子把一條數據插入了兩次,導致數據錯誤,臟數據的產生,最后一樣會導致各種問題。
或者說如果系統(tǒng)D突然宕機了幾個小時,導致無法消費消息,結果大量的消息在MQ中間件里積壓了很久,這個時候怎么辦?
即使系統(tǒng)D恢復了,也需要慢慢的消費數據來進行處理。
所以這就是引入MQ中間件的第二個大問題,系統(tǒng)穩(wěn)定性可能會下降,故障會增多,各種各樣亂七八糟的問題都可能產生。而且一旦產生了一個問題,就會導致系統(tǒng)整體出問題。就需要為了解決各種MQ引發(fā)的技術問題,采取很多的技術方案,系統(tǒng)的復雜性就會提升好幾個層級。
一致性問題
A 系統(tǒng)處理完了直接返回成功了,人都以為你這個請求就成功了;但是問題是,要是 BCD 三個系統(tǒng)那里,BD 兩個系統(tǒng)寫庫成功了,結果 C 系統(tǒng)寫庫失敗了,咋整?你這數據就不一致了
所以消息隊列實際是一種非常復雜的架構,你引入它有很多好處,但是也得針對它帶來的壞處做各種額外的技術方案和架構來規(guī)避掉,做好之后,你會發(fā)現,媽呀,系統(tǒng)復雜度提升了一個數量級,也許是復雜了 10 倍。但是關鍵時刻,用,還是得用的。
你們公司項目用的是什么消息中間件?
Mq的組件有哪些?
Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么優(yōu)缺點?
Mq的組件有哪些常見的問題?
消息的順序問題
消息有序指的是可以按照消息的發(fā)送順序來消費。
假如生產者產生了 2 條消息:M1、M2,假定 M1 發(fā)送到 S1,M2 發(fā)送到 S2,如果要保證 M1 先于 M2 被消費,怎么做?
解決方案:
保證生產者 - MQServer - 消費者是一對一對一的關系
消息的重復問題
造成消息重復的根本原因是:網絡不可達。
所以解決這個問題的辦法就是繞過這個問題。那么問題就變成了:如果消費端收到兩條一樣的消息,應該怎樣處理?
消費端處理消息的業(yè)務邏輯保持冪等性。只要保持冪等性,不管來多少條重復消息,最后處理的結果都一樣。保證每條消息都有唯一編號且保證消息處理成功與去重表的日志同時出現。利用一張日志表來記錄已經處理成功的消息的 ID,如果新到的消息 ID 已經在日志表中,那么就不再處理這條消息。
具體可參考之前的文章測試工程師都能看懂的冪等性問題
消息的丟失問題
每個MQ組件都有自己的方案,由于篇幅有限,我就簡單聊聊RocketMq(互聯(lián)網項目常用的Mq技術,經歷過阿里雙十一高并發(fā)的考驗)防丟失方案吧。
一般RocketMq會有以下幾種場景丟失消息:
1、生產者將消息發(fā)送給Rocket MQ的時候,如果出現了網絡抖動或者通信異常等問題,消息就有可能會丟失
2、消息需要持久化到磁盤中,這時會有兩種情況導致消息丟失
①RocketMQ為了減少磁盤的IO,會先將消息寫入到os cache中,而不是直接寫入到磁盤中,消費者從os cache中獲取消息類似于直接從內存中獲取消息,速度更快,過一段時間會由os線程異步的將消息刷入磁盤中,此時才算真正完成了消息的持久化。在這個過程中,如果消息還沒有完成異步刷盤,RocketMQ中的Broker宕機的話,就會導致消息丟失
②如果消息已經被刷入了磁盤中,但是數據沒有做任何備份,一旦磁盤損壞,那么消息也會丟失
3、消費者成功從RocketMQ中獲取到了消息,還沒有將消息完全消費完的時候,就通知RocketMQ我已經將消息消費了,然后消費者宕機,但是RocketMQ認為消費者已經成功消費了數據,所以數據依舊丟失了
那么如何保證消息的零丟失呢?
1、生產者保證消息不丟失的方案是使用RocketMQ自帶的事務機制來發(fā)送消息,大致流程為
①首先生產者發(fā)送half消息到RocketMQ中,此時消費者是無法消費half消息的,若half消息就發(fā)送失敗了,則執(zhí)行相應的回滾邏輯
②half消息發(fā)送成功之后,且RocketMQ返回成功響應,則執(zhí)行生產者的核心鏈路
③如果生產者自己的核心鏈路執(zhí)行失敗,則回滾,并通知RocketMQ刪除half消息
④如果生產者的核心鏈路執(zhí)行成功,則通知RocketMQ commit half消息,讓消費者可以消費這條數據
其中還有一些RocketMQ長時間沒有收到生產者是要commit/rollback操作的響應,回調生產者接口的細節(jié),感興趣的可以參考我的這篇博文 RocketMQ分布式事務原理
在使用了RocketMQ事務將生產者的消息成功發(fā)送給RocketMQ,就可以保證在這個階段消息不會丟失
消息中間件要保證消息不丟失,首先需要將os cache的異步刷盤策略改為同步刷盤,這一步需要修改Broker的配置文件,將flushDiskType改為SYNC_FLUSH同步刷盤策略,默認的是ASYNC_FLUSH異步刷盤。一旦同步刷盤返回成功,那么就一定保證消息已經持久化到磁盤中了;為了保證磁盤損壞不會丟失數據,我們需要對RocketMQ采用主從機構,集群部署,Leader中的數據在多個Follower中都存有備份,防止單點故障。
消費者如何保證不丟失消息呢?消息到達了消費者,RocketMQ在代碼中就能保證消息不會丟失。
//注冊消息監(jiān)聽器處理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context){
//對消息進行處理
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
上面這段代碼中,RocketMQ在消費者中注冊了一個監(jiān)聽器,當消費者獲取到了消息,就會去回調這個監(jiān)聽器函數,去處理里面的消息
當你的消息處理完畢之后,才會返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS
只有返回了CONSUME_SUCCESS,消費者才會告訴RocketMQ我已經消費完了,此時如果消費者宕機,消息已經處理完了,也就不會丟失消息了
如果消費者還沒有返回CONSUME_SUCCESS時就宕機了,那么RocketMQ就會認為你這個消費者節(jié)點掛掉了,會自動故障轉移,將消息交給消費者組的其他消費者去消費這個消息,保證消息不會丟失。
使用上面一整套的方案就可以在使用RocketMQ時保證消息零丟失,但是性能和吞吐量也將大幅下降
使用事務機制傳輸消息,會比普通的消息傳輸多出很多步驟,耗費性能
同步刷盤相比異步刷盤,一個是存儲在磁盤中,一個存儲在內存中,速度完全不是一個數量級,主從架構的話,需要Leader將數據同步給Follower,消費時無法異步消費,只能等待消費完成再通知RocketMQ消費完成。
消息零丟失是一把雙刃劍,要想用好,還是要視具體的業(yè)務場景而定,選擇合適的方案才是最好的
怎么測試Mq的組件?
最后一點特別關鍵,也是很多面試官愛問的面試題,你怎么測試mq組件的?主要分為以下幾個方面:
Mq異常測試
1、消息重復發(fā)送
消息重復發(fā)送,冪等性測試,可參考文章測試工程師都能看懂的冪等性問題
2、消息到達順序不一致
消息到達順序不一致,導致業(yè)務異常。
比如:訂單下單后再取消,如果先收到取消的消息,再收到下單消息,就會有問題。
3、消費失敗生產者、消費者重試機制
4、Mq性能測試
線上即將投入使用的Mq集群,通常都會做個性能摸底,在Mq的TPS和機器的資源使用率之間取得一個平衡。
舉個例子,mq在資源利用率極高的情況下可以到10萬TPS,但是機器的內存、cpu、io負載特別高,瀕臨宕機,這個是極其不安全的,可是當TPS在8萬的時候,機器的內存、cpu、io負載較高,但是還在可接受范圍內,這批機器就比較安全,不至于宕機。
所以我們的目標就是綜合TPS和機器負載,盡量找到一個最好的Tps,并且機器各項負載都在可接受范圍內,這才是我們的測試目的。為此要性能壓測要進行以下幾個步驟。
1、代碼中創(chuàng)建幾個生產者,投遞消息
我們可以在代碼里讓兩個生產者,不停的往集群中發(fā)送消息,每個生成者啟動多個80個線程(具體參考服務器配置),相當于每臺機器有80個線程并發(fā)往Mq集群發(fā)送消息。每條消息的大小固定。
2、實時查看集群中服務器的cpu、內存負載情況、磁盤io、JVM GC頻率、網卡流量等。
可以使用命令,也可以使用監(jiān)控軟件prometheus 等監(jiān)控服務器設備的負載。
3、當設備負載任何一個指標已經超過安全閾值以后,立即查看Mq管理頁面的tps峰值,譬如機器網卡是128M,網卡實際流量有100M,接近千兆網流量,我們mq的Tps峰值是7萬左右,我們就認為7萬tps是新集群最佳Tps.
其他
Producer(生產者)
所謂的生產者,就是產生消息的應用方,在進行生產者端的測試時,需要注意如何接入對應的MQ,需要哪些信息,可以提前確認(包括但不僅限入接入賬號、接入的主題、消息格式等)。
測試注意點有:
1、數據是否真正推送到隊列中
2、數據是否推送到正確的topic下
3、如果一次推送的數據過多,前面推送的數據如何處理(超過隊列)
4、同時需要注意每個topic下的queue如何分布數據
Consumer(消費者) 在MQ的世界里,消費者(從MQ隊列里獲取數據的應用方)主要有兩種,PUSH和PULL,簡單來說,就是主動拉取消息和被動接收消息(還有一種消費方式是廣播消息,應用場景較少)。不管哪種獲取消息方式,首先都要訂閱消息,即先指定需要消費哪個topic下的消息。
測試注意點:
1、確認應用的消費是哪種
2、測試消費者的消費信息源是否正確(能否從正確的topic中拿到正確的消息)
3、測試Topic的消費隊列策略是什么
4、數據被消費者使用后,有沒有及時的被清除
5、當消息隊列過長(消費速度過慢)時,MQ會溢出的數據如何處理
6、是否會越權消費別的 topic中的信息
7、如果是PULL類型的消費者,需要測試拉取的時間間隔,如果是push的類型,需要測試當有生產者生成消息時,消費者是否能及時得到信息并消費
總結
Mq在互聯(lián)網項目中使用特別廣泛,如果想在面試中增加自己的亮點,拿高薪,個人覺得可以在這方面突破。
作者:懂Java的測試
歡迎關注微信公眾號 :Python測試社區(qū)