1. 爲何大家公司選擇RabbitMQ做爲消息中間件
在消息隊列選型時,咱們調研了市場上比較經常使用ActiveMQ,RabbitMQ,RocketMQ,Kafka。java
- RabbitMQ相對成熟穩定,這是咱們選擇它最主要的緣由。
- 社區比較活躍,有完善的資料能夠參考。
- Rabbitmq的吞吐量能夠達到萬級,徹底知足咱們系統的要求。
- RabbitMQ是Erlang語言開發的,性能比較好。
- 有完善的可視化界面,方便查看。
2. 消息隊列的優勢和缺點有哪些
優勢有:面試
- 異步處理 - 相比於傳統的串行、並行方式,提升了系統吞吐量。
- 應用解耦 - 系統間經過消息通訊,不用關心其餘系統的處理。
- 流量削鋒 - 能夠經過消息隊列長度控制請求量;能夠緩解短期內的高併發請求。
缺點有:redis
- 系統可用性下降
- 系統複雜度提升
3. RabbitMQ經常使用的工做模式有哪些
2.1 簡單模型
- p:生成者
- C:消費者
- 紅色部分:quene,消息隊列
2.2 工做模型
這種模式下一條消息只能由一個消費者進行消費,默認狀況下,每一個消費者是輪詢消費的。spring
- p:生成者
- C一、C2:消費者
- 紅色部分:quene,消息隊列
2.3 發佈訂閱模型(fanout)
這種模型中生產者發送的消息全部消費者均可以消費。微信
- p:生成者
- X:交換機
- C一、C2:消費者
- 紅色部分:quene,消息隊列
2.4 路由模型(routing)
這種模型消費者發送的消息,不一樣類型的消息能夠由不一樣的消費者去消費。網絡
- p:生成者
- X:交換機,接收到生產者的消息後將消息投遞給與routing key徹底匹配的隊列
- C一、C2:消費者
- 紅色部分:quene,消息隊列
2.5 主題模型(topic)
這種模型和direct模型同樣,都是能夠根據routing key將消息路由到不一樣的隊列,只不過這種模型可讓隊列綁定routing key 的時候使用通配符。這種類型的routing key都是由一個或多個單詞組成,多個單詞之間用.
分割。併發
通配符介紹:app
*
:只匹配一個單詞dom
#
:匹配一個或多個單詞異步
4. 如何保證消息不丟失(如何保證消息的可靠性)
一條消息從生產到消費經歷了三個階段,分別是生產者,MQ和消費者,對於RabbitMQ來講,消息的傳遞還涉及到交換機。所以RabbitMQ出現消息丟失的狀況有四個
分別是
- 消息生產者沒有成功將消息發送到MQ致使消息丟失
- 交換機未路由到消息隊列致使消息丟失
- 消息在MQ中時,MQ發生宕機致使消息丟失
- 消費者消費消息時出現異常致使消息丟失
針對上面提到的四種狀況,分別進行處理
- amqp協議提供了事務機制,在投遞消息時開啓事務,若是消息投遞失敗,則回滾事務,不多有人去使用事務。除了事務以外,RabbitMQ還提供了生產者確認機制(publisher confirm)。生產者將信道設置成confirm(確認)模式,一旦信道進入confirm模式,全部在該信道上面發佈的消息都會被指派一個惟一的ID(從1開始),一旦消息被投遞到全部匹配的隊列以後,RabbitMQ就會發送一個確認(Basic.Ack)給生產者(包含消息的惟一ID),這就使得生產者知曉消息已經正確到達了目的地了。
# 開啓生產者確認機制, # 注意這裏確認的是是否到達交換機 spring.rabbitmq.publisher-confirm-type=correlated
@RestController public class Producer { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("send") public void sendMessage(){ /** * 生產者確認消息 */ rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println(correlationData); System.out.println(ack); System.out.println(cause); } }); rabbitTemplate.convertAndSend("s","error","這是一條錯誤日誌!!!"); } }
- 消息從交換機未能匹配到隊列時將此條消息返回給生產者
spring.rabbitmq.publisher-returns=true
@RestController public class Producer { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("send") public void sendMessage(){ /** * 消息未達隊列時返回該條消息 */ rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.out.println(returnedMessage); } }); rabbitTemplate.convertAndSend("s","error","這是一條錯誤日誌!!!"); } }
- 消息在交換機或隊列中發生丟失,咱們只須要將交換機和隊列進行持久化。
/** * 定義一個持久化的topic交換機 * durable 持久化 * @return */ @Bean public Exchange exchangeJavatrip(){ return ExchangeBuilder.topicExchange(EXCHANGE).durable(true).build(); } /** * 定義一個持久化的隊列 * durable 持久化 * @return */ @Bean public Queue queueJavatrip(){ return QueueBuilder.durable(QUEUE).build(); }
- 消費者開啓手動簽收模式,消費完成後進行ack確認。
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@RabbitListener(queues = MqConfig.QUEUE) public void receive(String body, Message message, Channel channel) throws Exception{ long deliveryTag = message.getMessageProperties().getDeliveryTag(); System.out.println(deliveryTag); // 系統業務邏輯判斷是否簽收 if(deliveryTag % 2 == 0){ channel.basicAck(deliveryTag,false); }else{ // 第二個參數是否批量確認,第三個參數是否從新回隊列 channel.basicNack(deliveryTag,false,true); } }
5. 如何保證消息不重複消費(如何保證消息的冪等性)
消息重複的緣由有兩個:
-
生產時消息重複
因爲生產者發送消息給MQ,在MQ確認的時候出現了網絡波動,生產者沒有收到確認,實際上MQ已經接收到了消息。這時候生產者就會從新發送一遍這條消息。
-
消費時消息重複。
消費者消費成功後,在給MQ確認的時候出現了網絡波動,MQ沒有接收到確認,爲了保證消息被消費,MQ就會繼續給消費者投遞以前的消息。這時候消費者就接收到了兩條同樣的消息。
因爲消息重複是網絡波動等緣由形成的,沒法避免,咱們能作的的就是保證消息的冪等性,以防業務重複處理。具體處理方案爲:
讓每一個消息攜帶一個全局的惟一ID,便可保證消息的冪等性,具體消費過程爲:
- 消費者獲取到消息後先根據id去查詢redis/db是否存在該消息。
- 若是不存在,則正常消費,消費完畢後寫入redis/db。
- 若是存在,則證實消息被消費過,直接丟棄。
@RabbitListener(queues = MqConfig.QUEUE) public void receive(Message message, Channel channel){ String messageId = message.getMessageProperties().getMessageId(); String body = new String(message.getBody()); String redisId = redisTemplate.opsForValue().get(messageId)+""; // 若是redis中存有當前消息的消息id // 則證實消費過 if(messageId.equals(redisId)){ return; } redisTemplate.opsForValue().set(messageId, UUID.randomUUID()); }
6. 消息大量堆積應該怎麼處理
消息堆積的緣由有兩個
- 網絡故障,消費者沒法正常消費
- 消費方消費後未進行ack確認
解決方案以下:
- 檢查並修復消費者故障,使其正常消費
- 編寫臨時程序將堆積的消息發送到容量更大的MQ集羣,增長消費者快速消費
- 堆積消息消費完畢後,中止臨時程序,恢復正常消費
7. 死信是什麼?死信如何處理
當一條消息在隊列中出現如下三種狀況的時候,該消息就會變成一條死信。
- 消息被拒絕(basic.reject / basic.nack),而且requeue = false
- 消息TTL過時
- 隊列達到最大長度
當消息在一個隊列中變成一個死信以後,若是配置了死信隊列,它將被從新publish到死信交換機,死信交換機將死信投遞到一個隊列上,這個隊列就是死信隊列。
一條消息成爲死信後,通常會經過死信隊列進行存庫,而後定時將庫中的死信進行從新投遞到消息隊列上。
8. 若是我有一筆訂單,30分鐘未支付則關閉訂單,使用RabbitMQ如何來實現
RabbitMQ可使用死信隊列來實現延時消費,用戶下單以後,將訂單信息投遞到消息隊列中,而且設置消息過時時常爲30分鐘。若是用戶支付則正常關閉訂單,若是用戶未支付,消息達到過時時間,消息會進入死信交換,由消費者進行消費死信隊列來關閉訂單。
9. RabbitMQ如何保證高可用
RabbitMQ有兩種集羣模式,分別是普通集羣和鏡像集羣,普通模式沒法保證RabbitMQ的高可用。
普通集羣
假若有三個節點,rabbitmq一、rabbitmq二、rabbitmq3,消息實際上只存在於其中一個節點,三個節點僅有相同的元數據,即隊列的結構,當消息進入rabbitmq2節點的queue後,consumer從rabbitmq1的節點進行消費,rabbitmq1和rabbitmq2會進行臨時通訊,從rabbitmq2中獲取消息而後返回給consumer。
這種模式存在如下兩個問題:
-
當rabbitmq2宕機後,消息沒法正常消費,沒有作到真正的高可用
-
實際數據仍是在單個實例上,存在瓶頸問題
鏡像集羣
假若有三個節點,rabbitmq一、rabbitmq二、rabbitmq3,每一個實例之間均可以相互通訊,每次生產者寫消息到queue的時候,每一個rabbitmq節點上都有queue的消息數據和元數據。這種模式使用於可靠性要求較高的場景。
點關注、不迷路
若是以爲文章不錯,歡迎關注、點贊、收藏,大家的支持是我創做的動力,感謝你們。
若是文章寫的有問題,請不要吝惜文筆,歡迎留言指出,我會及時覈查修改。
若是你還想看到更多別的東西,能夠微信搜索「Java旅途」進行關注。回覆「手冊」領取Java面試手冊!