更新時間:2022-12-28 16:19:39 來源:動力節(jié)點(diǎn) 瀏覽1889次
現(xiàn)在是各個軟件開發(fā)相關(guān)企業(yè)的招人高峰期,消息隊列也是高頻發(fā)的面試題目,所以我們今天精心的準(zhǔn)備了有關(guān)消息隊列經(jīng)常提及到的面試題以及答案的梳理,希望能給正在求職的人提供幫助。

說一說生產(chǎn)者與消費(fèi)者模式
參考答案
所謂生產(chǎn)者-消費(fèi)者問題,實(shí)際上主要是包含了兩類線程。一種是生產(chǎn)者線程用于生產(chǎn)數(shù)據(jù),另一種是消費(fèi)者線程用于消費(fèi)數(shù)據(jù),為了解耦生產(chǎn)者和消費(fèi)者的關(guān)系,通常會采用共享的數(shù)據(jù)區(qū)域,就像是一個倉庫。生產(chǎn)者生產(chǎn)數(shù)據(jù)之后直接放置在共享數(shù)據(jù)區(qū)中,并不需要關(guān)心消費(fèi)者的行為。而消費(fèi)者只需要從共享數(shù)據(jù)區(qū)中去獲取數(shù)據(jù),就不再需要關(guān)心生產(chǎn)者的行為。但是,這個共享數(shù)據(jù)區(qū)域中應(yīng)該具備這樣的線程間并發(fā)協(xié)作的功能:
如果共享數(shù)據(jù)區(qū)已滿的話,阻塞生產(chǎn)者繼續(xù)生產(chǎn)數(shù)據(jù)放置入內(nèi);
如果共享數(shù)據(jù)區(qū)為空的話,阻塞消費(fèi)者繼續(xù)消費(fèi)數(shù)據(jù)。
在Java語言中,實(shí)現(xiàn)生產(chǎn)者消費(fèi)者問題時,可以采用三種方式:
使用 Object 的 wait/notify 的消息通知機(jī)制;
使用 Lock 的 Condition 的 await/signal 的消息通知機(jī)制;
使用 BlockingQueue 實(shí)現(xiàn)。
消息隊列如何保證順序消費(fèi)?
參考答案
在生產(chǎn)中經(jīng)常會有一些類似報表系統(tǒng)這樣的系統(tǒng),需要做 MySQL 的 binlog 同步。比如訂單系統(tǒng)要同步訂單表的數(shù)據(jù)到大數(shù)據(jù)部門的 MySQL 庫中用于報表統(tǒng)計分析,通常的做法是基于 Canal 這樣的中間件去監(jiān)聽訂單數(shù)據(jù)庫的 binlog,然后把這些 binlog 發(fā)送到 MQ 中,再由消費(fèi)者從 MQ 中獲取 binlog 落地到大數(shù)據(jù)部門的 MySQL 中。
在這個過程中,可能會有對某個訂單的增刪改操作,比如有三條 binlog 執(zhí)行順序是增加、修改、刪除。消費(fèi)者愣是換了順序給執(zhí)行成刪除、修改、增加,這樣能行嗎?肯定是不行的。不同的消息隊列產(chǎn)品,產(chǎn)生消息錯亂的原因,以及解決方案是不同的。下面我們以RabbitMQ、Kafka、RocketMQ為例,來說明保證順序消費(fèi)的辦法。
RabbitMQ:
對于 RabbitMQ 來說,導(dǎo)致上面順序錯亂的原因通常是消費(fèi)者是集群部署,不同的消費(fèi)者消費(fèi)到了同一訂單的不同的消息。如消費(fèi)者A執(zhí)行了增加,消費(fèi)者B執(zhí)行了修改,消費(fèi)者C執(zhí)行了刪除,但是消費(fèi)者C執(zhí)行比消費(fèi)者B快,消費(fèi)者B又比消費(fèi)者A快,就會導(dǎo)致消費(fèi) binlog 執(zhí)行到數(shù)據(jù)庫的時候順序錯亂,本該順序是增加、修改、刪除,變成了刪除、修改、增加。如下圖:

RabbitMQ 的問題是由于不同的消息都發(fā)送到了同一個 queue 中,多個消費(fèi)者都消費(fèi)同一個 queue 的消息。解決這個問題,我們可以給 RabbitMQ 創(chuàng)建多個 queue,每個消費(fèi)者固定消費(fèi)一個 queue 的消息,生產(chǎn)者發(fā)送消息的時候,同一個訂單號的消息發(fā)送到同一個 queue 中,由于同一個 queue 的消息是一定會保證有序的,那么同一個訂單號的消息就只會被一個消費(fèi)者順序消費(fèi),從而保證了消息的順序性。如下圖:

Kafka:
對于 Kafka 來說,一個 topic 下同一個 partition 中的消息肯定是有序的,生產(chǎn)者在寫的時候可以指定一個 key,通過我們會用訂單號作為 key,這個 key 對應(yīng)的消息都會發(fā)送到同一個 partition 中,所以消費(fèi)者消費(fèi)到的消息也一定是有序的。
那么為什么 Kafka 還會存在消息錯亂的問題呢?問題就出在消費(fèi)者身上。通常我們消費(fèi)到同一個 key 的多條消息后,會使用多線程技術(shù)去并發(fā)處理來提高消息處理速度,否則一條消息的處理需要耗時幾十 毫秒,1 秒也就只能處理幾十條消息,吞吐量就太低了。而多線程并發(fā)處理的話,binlog 執(zhí)行到數(shù)據(jù)庫的時候就不一定還是原來的順序了。如下圖:

Kafka 從生產(chǎn)者到消費(fèi)者消費(fèi)消息這一整個過程其實(shí)都是可以保證有序的,導(dǎo)致最終亂序是由于消費(fèi)者端需要使用多線程并發(fā)處理消息來提高吞吐量,比如消費(fèi)者消費(fèi)到了消息以后,開啟 32 個線程處理消息,每個線程線程處理消息的快慢是不一致的,所以才會導(dǎo)致最終消息有可能不一致。
所以對于 Kafka 的消息順序性保證,其實(shí)我們只需要保證同一個訂單號的消息只被同一個線程處理的就可以了。由此我們可以在線程處理前增加個內(nèi)存隊列,每個線程只負(fù)責(zé)處理其中一個內(nèi)存隊列的消息,同一個訂單號的消息發(fā)送到同一個內(nèi)存隊列中即可。如下圖:

RocketMQ:
對于 RocketMQ 來說,每個 Topic 可以指定多個 MessageQueue,當(dāng)我們寫入消息的時候,會把消息均勻地分發(fā)到不同的 MessageQueue 中,比如同一個訂單號的消息,增加 binlog 寫入到 MessageQueue1 中,修改 binlog 寫入到 MessageQueue2 中,刪除 binlog 寫入到 MessageQueue3 中。
但是當(dāng)消費(fèi)者有多臺機(jī)器的時候,會組成一個 Consumer Group,Consumer Group 中的每臺機(jī)器都會負(fù)責(zé)消費(fèi)一部分 MessageQueue 的消息,所以可能消費(fèi)者A消費(fèi)了 MessageQueue1 的消息執(zhí)行增加操作,消費(fèi)者B消費(fèi)了 MessageQueue2 的消息執(zhí)行修改操作,消費(fèi)者C消費(fèi)了 MessageQueue3 的消息執(zhí)行刪除操作,但是此時消費(fèi) binlog 執(zhí)行到數(shù)據(jù)庫的時候就不一定是消費(fèi)者A先執(zhí)行了,有可能消費(fèi)者C先執(zhí)行刪除操作,因為幾臺消費(fèi)者是并行執(zhí)行,是不能夠保證他們之間的執(zhí)行順序的。如下圖:

RocketMQ 的消息亂序是由于同一個訂單號的 binlog 進(jìn)入了不同的 MessageQueue,進(jìn)而導(dǎo)致一個訂單的 binlog 被不同機(jī)器上的 Consumer 處理。
要解決 RocketMQ 的亂序問題,我們只需要想辦法讓同一個訂單的 binlog 進(jìn)入到同一個 MessageQueue 中就可以了。因為同一個 MessageQueue 內(nèi)的消息是一定有序的,一個 MessageQueue 中的消息只能交給一個 Consumer 來進(jìn)行處理,所以 Consumer 消費(fèi)的時候就一定會是有序的。

以上就是“精心準(zhǔn)備了高頻出現(xiàn)的消息隊列面試題”,你能回答上來嗎?如果想要了解更多的Java面試題相關(guān)內(nèi)容,可以關(guān)注動力節(jié)點(diǎn)Java官網(wǎng)。
相關(guān)閱讀

初級 202925

初級 203221

初級 202629

初級 203743