從不知道到了解—RabbitMQ 基礎(chǔ)概念及 Spring 的配置和使用

序言

你在系統(tǒng)中是否寫過這樣的接口:客戶端訪問服務(wù)器,服務(wù)器進(jìn)行了大量邏輯/耗時(shí)操作之后,才能將結(jié)果返回給客戶端,而這時(shí),客戶端的連接或許已經(jīng)因?yàn)槌瑫r(shí)而關(guān)閉了。為了能夠及時(shí)的給客戶端返回?cái)?shù)據(jù), 在項(xiàng)目中,將一些無需即時(shí)返回且耗時(shí)的操作提取出來,進(jìn)行了異步處理,而這種異步處理的方式大大的節(jié)省了服務(wù)器的請求響應(yīng)時(shí)間,從而提高了系統(tǒng)的吞吐量。
RabbitMQ 是一個(gè)在 AMQP 基礎(chǔ)上完成的,可復(fù)用的企業(yè)消息系統(tǒng)。RabbitMQ 用 Erlang 語言編寫,支持多種消息協(xié)議,消息隊(duì)列,傳送確認(rèn),可以將消息靈活的路由到隊(duì)列,并支持多種交換類型。同時(shí)也可部署為負(fù)載均衡的集群,實(shí)現(xiàn)高可用性和吞吐量,聯(lián)跨多個(gè)可用性區(qū)域和地區(qū)。RabbitMQ 支持 Java, .NET, PHP, Python, JavaScript, Ruby, Go 等多種語言,方便開發(fā)者使用。

基礎(chǔ)介紹

在使用 RabbitMQ 之前,需要了解一些 RabbitMQ 的基礎(chǔ)知識(shí),這些基礎(chǔ)知識(shí)最好還是要了解的,否則在面對 Spring 中配置文件的時(shí)候會(huì)有很多疑惑。

Queue

MQ 就是 Message Queuing,Queue 就是隊(duì)列了。在 RabbitMQ 中,Queue 是最主要的地方,Message Queuing 就是存儲(chǔ) Message 的隊(duì)列。生產(chǎn)者 Producer(以下簡寫為 p)將消息投遞到 Message Queuing(以下簡寫為 q)中,消費(fèi)者(以下簡寫為 c)從 queue 中獲取消息進(jìn)行處理。

如上圖中所畫(圖中缺少 exchange,下面會(huì)說到)的那樣,多個(gè) p 將消息交給了 q,然后就不關(guān)心系統(tǒng)怎么處理這些消息了。而 q 在收到這些消息后,會(huì)分發(fā)給在它這里注冊了的 c,讓他們?nèi)シ謩e處理。每一條消息只會(huì)分發(fā)給一個(gè) c,而不是每一個(gè) c 接受到所有的消息然后全部去處理。

Producer

Producer 負(fù)責(zé)產(chǎn)生消息并將消息發(fā)送給 Queue,它不關(guān)心消息怎么處理,只把消息按照指定的規(guī)則(下面會(huì)說)發(fā)送出去。

Consumer

系統(tǒng)在啟動(dòng)的時(shí)候,會(huì)將所有的消費(fèi)者 Consumer 注冊到 RabbitMQ 中并對指定的 Queue(有規(guī)則,下面會(huì)說)進(jìn)行監(jiān)聽,如果 Queue 中產(chǎn)生了數(shù)據(jù),queue 會(huì)將消息平攤發(fā)送給所有注冊到當(dāng)前 quene 中的 consumer 中,由 consumer 進(jìn)行處理。

Exchange

Exchange 在 RabbitMQ 中是一個(gè)很重要的組件。上面我們說 Producer 負(fù)責(zé)產(chǎn)生消息并將消息發(fā)送給 Queue,實(shí)際上 Producer 并不直接連接 Queue,而是連接了 Exchange,由 Exchange 將消息路由到指定的 Queue 中。也就是在 producer 和 queue 中間插入了一個(gè) Exchange 來分發(fā)消息給 queue。
ExchangeType、binding 和 ExchangeName 三者確定了一個(gè) Exchange,ExchangeName 確定了這個(gè) Exchange 的名字,ExchangeType 決定了這是一個(gè)什么類型的 Exchange,binding 決定了它以什么規(guī)則連接一個(gè) queue。

  • ExchangeName 標(biāo)記了這個(gè) Exchange,在 producer 發(fā)送消息的時(shí)候需要指定,消息會(huì)發(fā)送到指定 ExchangeName 的 Exchange 中。比如說你去參加婚禮宴會(huì),主人之前告訴你到了酒店去大廳找服務(wù)員 A,她會(huì)帶你到包廂。這里的“服務(wù)員 A”這個(gè)名字就是 ExchangeName
  • binding(其實(shí)是一個(gè)字符串或者說一種特別的正則)會(huì)將 Exchange 和 Queue 聯(lián)系起來,而 Exchange 路由到 Queue 的規(guī)則就是這個(gè) binding/binding-key。比如說你去參加一個(gè)婚宴,請?zhí)麑懼?302 包廂,那服務(wù)員就會(huì)領(lǐng)著你到 302 包廂。在這個(gè)例子中,服務(wù)員就是 Exchange,包廂是 queue,而服務(wù)員腦中這個(gè)“根據(jù)你的請柬領(lǐng)你到哪個(gè)包廂”的規(guī)則就是 binding。而你的請柬是 routing-key,這個(gè)后面說
  • ExchangeType 是 Exchange 的類型,這決定了這個(gè) Exchange 會(huì)執(zhí)行哪種規(guī)則 (binding) 將消息路由到不同的 queue。ExchangeType 分為多種,其中最為常用的就是 direct,fanout 和 topic。
  • 還是比如你去參加婚宴,婚宴上可能有很多服務(wù)員。服務(wù)員 A 是個(gè)耿直 boy,你請柬上寫著哪個(gè)包廂他就領(lǐng)你去哪個(gè)包廂,也就是你的請柬上的“302 包廂”和他腦中“302 包廂(302 包廂在 3 樓第二個(gè)包廂)”是相等的,那他就領(lǐng)著你去了,這個(gè)服務(wù)員就叫“direct”。
  • 而服務(wù)員 B 他負(fù)責(zé)領(lǐng)路到 4 樓的 401、402 包廂,另外他是個(gè)呆子但是會(huì)一點(diǎn)魔法,不管是誰找到他,他都會(huì)念一句魔法把你復(fù)制兩份(有幾個(gè)包廂就復(fù)制幾份),然后把你分別領(lǐng)到他負(fù)責(zé)的包廂去,401、402 包廂每一個(gè)都有一個(gè)你,這個(gè)服務(wù)員是“fanout”,你在這個(gè)例子中就是消息(意即如果消息發(fā)送給 type 是 fanout 的 exchange,exchange 會(huì)把消息發(fā)送給所有和他綁定的 queue 中)。
  • 最后有一個(gè)服務(wù)員 C 特別聰明,老板交給他的任務(wù)是如果找你的是男人,領(lǐng)到 501,如果是女人,領(lǐng)到 502,如果是小孩子,領(lǐng)到 503,即按照不同的規(guī)則(這里的規(guī)則更詳細(xì)的解釋可以解釋成:. 男人. 人路由到 501,. 女人. 人路由到 502,*. 小孩. 人路由到 503),這個(gè)服務(wù)員就叫“topic”,topic 的詳細(xì)規(guī)則是這樣的:

routing key 為一個(gè)句點(diǎn)號(hào)“. ”分隔的字符串(我們將被句點(diǎn)號(hào)“. ”分隔開的每一段獨(dú)立的字符串稱為一個(gè)單詞),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
binding key 與 routing key 一樣也是句點(diǎn)號(hào)“. ”分隔的字符串
binding key 中可以存在兩種特殊字符“”與“#”,用于做模糊匹配,其中“”用于匹配一個(gè)單詞,“#”用于匹配多個(gè)單詞(可以是零個(gè))


以上圖中的配置為例,routingKey=”quick.orange.rabbit”的消息會(huì)同時(shí)路由到 Q1 與 Q2,
routingKey=”lazy.orange.fox”的消息會(huì)路由到 Q1 與 Q2,
routingKey=”lazy.brown.fox”的消息會(huì)路由到 Q2,
routingKey=”lazy.pink.rabbit”的消息會(huì)路由到 Q2(只會(huì)投遞給 Q2 一次,雖然這個(gè) routingKey 與 Q2 的兩個(gè) bindingKey 都匹配);
routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息將會(huì)被丟棄,因?yàn)樗鼈儧]有匹配任何 bindingKey。
參考自:RabbitMQ 基礎(chǔ)概念詳細(xì)介紹

介紹 Producer 和 Consumer 的規(guī)則

上面在介紹 Producer 的時(shí)候都把規(guī)則給略過了,現(xiàn)在介紹完了 Exchange,可以再回過頭來介紹 Producer 的規(guī)則。

當(dāng)時(shí)說 Producer 會(huì)將消息發(fā)送給 queue,現(xiàn)在我們知道 Producer 是將消息發(fā)送給了 Exchange。Producer 在發(fā)送消息的時(shí)候需要指定 Exchange,也就是需要聲明 ExchangeName,同時(shí)根據(jù)指定的這個(gè) Exchange 的類型,來設(shè)置一個(gè) routing-key。我們知道 Exchange 和 queue 是通過一個(gè)叫 binding 的 key 來完成的,在 Exchange 中會(huì)記錄所有它連接的 queue 所對應(yīng)的 binding(暫且叫 bs 吧),消息通過設(shè)置的 ExchangeName 發(fā)送到指定的這個(gè) Exchange,同時(shí)攜帶過去的 routing-key 會(huì)與 bs 中的 binding 進(jìn)行匹配,匹配條件滿足就會(huì)將消息發(fā)送過去(匹配到幾個(gè)就發(fā)送幾個(gè))。

Consumer 連接 queue 的規(guī)則相對來說就簡單很多了,指定 queue 的名字,設(shè)置幾個(gè)連接參數(shù)就好了(比如說:exclusive、Auto-delete、Durable)。

exclusive:排他,該隊(duì)列僅對首次聲明它的連接可見,并在連接斷開時(shí)自動(dòng)刪除 Auto-delete: 自動(dòng)刪除,如果該隊(duì)列沒有任何訂閱的消費(fèi)者的話,該隊(duì)列會(huì)被自動(dòng)刪除。這種隊(duì)列適用于臨時(shí)隊(duì)列。 Durable: 持久化

另外對于 Producer、Exchange、Queue,和 Consumer 都還有一些可設(shè)置的參數(shù)用來個(gè)性化配置,這個(gè)自己去搜索一下吧。

一張圖來解釋整個(gè)過程


圖中 Producer 發(fā)送消息給 message,routing-key 未指定是指沒有固定一個(gè) routing-key,這個(gè) key 是動(dòng)態(tài)可變的。

而根據(jù) message 這個(gè) Exchange 的 type 可知,這個(gè) message 是那么聰明的服務(wù)員,會(huì)根據(jù)規(guī)則將發(fā)送來的消息“聰明”的交給相應(yīng)的 Queue 處理。

假如這個(gè) Producer 發(fā)送的 Message 所指定的 routing-key 是 order.log.phbj,那么根據(jù) Exchange 與 Queue 間對應(yīng)的橙色線,這條消息會(huì)被發(fā)送給 queue2, 并由 Consumer 接收處理。

如果這個(gè) Producer 發(fā)送的 Message 所指定的 routing-key 是 weixin.order.phbj,那么這個(gè)消息會(huì)被發(fā)送 queue,并由對應(yīng)注冊的 Consumer2 接收(注意這里的 Consumer 可能不止一個(gè),但是只會(huì)有一個(gè) Consumer 來接收)

過程大概就是這樣了,接下來配合 Spring 來使用。

Spring 中使用 RabbitMQ

pom 文件放在文章末尾,這里先來說 RabbitMQ 的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 http://www.springframework.org/schema/rabbit
 http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
 <!--配置 connection-factory,指定連接 rabbit server 參數(shù) -->
 <rabbit:connection-factory id="connectionFactory" username="admin" password="123456"
 host="192.168.1.198"
 port="5672"
 virtual-host="/"/>
 <bean id="rabbitConnectionFactory"
 class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
 <property name="host" value="192.168.1.198"/>
 <property name="username" value="admin"/>
 <property name="password" value="123456"/>
 <property name="port" value="5672"/>
 <property name="virtualHost" value="/"/>
 <property name="channelCacheSize" value="5"/>
 <property name="publisherConfirms" value="true"/>
 </bean>
 <!--
 這里是兩個(gè) queue 的定義
 exclusive:排他,該隊(duì)列僅對首次聲明它的連接可見,并在連接斷開時(shí)自動(dòng)刪除
 Auto-delete: 自動(dòng)刪除,如果該隊(duì)列沒有任何訂閱的消費(fèi)者的話,該隊(duì)列會(huì)被自動(dòng)刪除。這種隊(duì)列適用于臨時(shí)隊(duì)列。
 Durable: 持久化
 -->
 <rabbit:queue id="queue" name="queue" durable="true" auto-delete="false" exclusive="false"/>
 <rabbit:queue id="queue2" name="queue2" durable="true" auto-delete="false" exclusive="false"/>
 
 <!--RabbitMQ 的 Consumer-->
 <bean id="messageReceiver" class="net.sumile.consumer.MessageConsumer"></bean>
 <bean id="messageReceiver2" class="net.sumile.consumer.MessageConsumerQueue2"></bean>
 
 <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" concurrency="1"
 prefetch="5">
 <rabbit:listener queues="queue" ref="messageReceiver"/>
 <rabbit:listener queues="queue2" ref="messageReceiver2"/>
 </rabbit:listener-container>
 
 
 <!--通過指定下面的 admin 信息,當(dāng)前 producer 中的 exchange 和 queue 會(huì)在 rabbitmq 服務(wù)器上自動(dòng)生成 -->
 <rabbit:admin connection-factory="connectionFactory"/>
 
 
 <!-- 兩種 Exchange 的定義 -->
 <rabbit:direct-exchange name="order" durable="true" auto-delete="false">
 <rabbit:bindings>
 <rabbit:binding queue="queue" key="order"></rabbit:binding>
 </rabbit:bindings>
 </rabbit:direct-exchange>
 
 <rabbit:direct-exchange name="exchange" durable="true" auto-delete="false">
 <rabbit:bindings>
 <rabbit:binding queue="queue2" key="exchange"></rabbit:binding>
 </rabbit:bindings>
 </rabbit:direct-exchange>
 
 <rabbit:topic-exchange name="message" durable="true" auto-delete="false">
 <rabbit:bindings>
 <rabbit:binding queue="queue2" pattern="*.log.phbj"/>
 <rabbit:binding queue="queue" pattern="*.order.phbj"/>
 <rabbit:binding queue="queue" pattern="*.pay.phbj"/>
 </rabbit:bindings>
 </rabbit:topic-exchange>
 
 <!--定義是否使用序列化傳輸-->
 <!--<bean id="jsonMessageConverter"-->
 <!--class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"></bean>-->
 <!--message-converter="jsonMessageConverter"-->
 
 <!--定義 rabbit template 用于數(shù)據(jù)的發(fā)送 -->
 <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
 exchange="exchange" routing-key="exchange"/>
 <rabbit:template id="amqpTemplate2" connection-factory="connectionFactory"
 exchange="order" routing-key="order"/>
 <bean id="amqpTemplate3"
 class="org.springframework.amqp.rabbit.core.RabbitTemplate">
 <property name="connectionFactory" ref="rabbitConnectionFactory"/>
 <property name="confirmCallback" ref="confirmCallBackListener"/>
 <property name="returnCallback" ref="returnCallBackListener"/>
 <property name="mandatory" value="true"/>
 <!--只有在關(guān)閉事務(wù)的情況下 mandatory 才起作用-->
 <property name="channelTransacted" value="false"/>
 <property name="exchange" value="message"/>
 </bean>
 <!--發(fā)送確認(rèn)監(jiān)聽-->
 <bean id="confirmCallBackListener" class="net.sumile.producer.ConfirmCallBackListener"/>
 <bean id="returnCallBackListener" class="net.sumile.producer.ReturnCallBackListener"/>
</beans>

在程序啟動(dòng)之前,首先來確認(rèn)下 RabbitMQ 的狀態(tài)


圖中可以看出,現(xiàn)在沒有連接鏈接到 RabbitMQ,Exchange 和 Queue 也都是空的。
然后啟動(dòng)程序
RabbitMQStart

這是啟動(dòng)之后立刻就截的圖,可以看到,有一個(gè)連接鏈接上了 RabbitMQ,同時(shí)創(chuàng)建了三個(gè) Exchange 以及兩個(gè) queue。
然后打開每一個(gè) Exchange 來看看它的對應(yīng)規(guī)則是什么,著重看紅色框中的
RabbitMQExchangeBinding

可以看到,紅色框中的設(shè)置正是在 xml 中配置的那部分:

<rabbit:direct-exchange name="order" durable="true" auto-delete="false">
 <rabbit:bindings>
 <rabbit:binding queue="queue" key="order"></rabbit:binding>
 </rabbit:bindings>
</rabbit:direct-exchange>
 
<rabbit:direct-exchange name="exchange" durable="true" auto-delete="false">
 <rabbit:bindings>
 <rabbit:binding queue="queue2" key="exchange"></rabbit:binding>
 </rabbit:bindings>
</rabbit:direct-exchange>
 
<rabbit:topic-exchange name="message" durable="true" auto-delete="false">
 <rabbit:bindings>
 <rabbit:binding queue="queue2" pattern="*.log.phbj"/>
 <rabbit:binding queue="queue" pattern="*.order.phbj"/>
 <rabbit:binding queue="queue" pattern="*.pay.phbj"/>
 </rabbit:bindings>
</rabbit:topic-exchange>

其中配置文件中的“<rabbit:” 后面的 topic 和 direct 指該 Exchange 的類型。而綁定的 queue 則在 bindings 中,規(guī)則是通過 binding 配置的 key 或者 pattern。這些在圖中都有顯示。


圖中 Bindings 與上面的 Exchange 的配置有對應(yīng),而 Consumers 則對應(yīng)了 xml 中的以下配置:

<!--RabbitMQ 的 Consumer-->
<bean id="messageReceiver" class="net.sumile.consumer.MessageConsumer"></bean>
<bean id="messageReceiver2" class="net.sumile.consumer.MessageConsumerQueue2"></bean>
 
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" concurrency="1"
 prefetch="5">
 <rabbit:listener queues="queue" ref="messageReceiver"/>
 <rabbit:listener queues="queue2" ref="messageReceiver2"/>
</rabbit:listener-container>

Ack required 通過配置的 acknowledge 來配置,表示需要消息確認(rèn)
Prefetch count 通過配置中的 prefetch 標(biāo)簽配置,來限制 Queue 每次發(fā)送給每個(gè)消費(fèi)者的消息數(shù)。

如果有多個(gè)消費(fèi)者同時(shí)訂閱同一個(gè) Queue 中的消息,Queue 中的消息會(huì)被平攤給多個(gè)消費(fèi)者。這時(shí)如果每個(gè)消息的處理時(shí)間不同,就有可能會(huì)導(dǎo)致某些消費(fèi)者一直在忙,而另外一些消費(fèi)者很快就處理完手頭工作并一直空閑的情況。我們可以通過設(shè)置 prefetchCount 來限制 Queue 每次發(fā)送給每個(gè)消費(fèi)者的消息數(shù),比如我們設(shè)置 prefetchCount=1,則 Queue 每次給每個(gè)消費(fèi)者發(fā)送一條消息;消費(fèi)者處理完這條消息后 Queue 會(huì)再給該消費(fèi)者發(fā)送一條消息。參考:RabbitMQ 基礎(chǔ)概念詳細(xì)介紹

發(fā)送者的相關(guān)配置不會(huì)在上面的圖中體現(xiàn),他是通過代碼來調(diào)用運(yùn)行的。

<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
 exchange="exchange" routing-key="exchange"/>
<rabbit:template id="amqpTemplate2" connection-factory="connectionFactory"
 exchange="order" routing-key="order"/>
<bean id="amqpTemplate3"
 class="org.springframework.amqp.rabbit.core.RabbitTemplate">
 <property name="connectionFactory" ref="rabbitConnectionFactory"/>
 <property name="confirmCallback" ref="confirmCallBackListener"/>
 <property name="returnCallback" ref="returnCallBackListener"/>
 <property name="mandatory" value="true"/>
 <!--只有在關(guān)閉事務(wù)的情況下 mandatory 才起作用-->
 <property name="channelTransacted" value="false"/>
 <property name="exchange" value="message"/>
</bean>
<!--發(fā)送確認(rèn)監(jiān)聽-->
<bean id="confirmCallBackListener" class="net.sumile.producer.ConfirmCallBackListener"/>
<bean id="returnCallBackListener" class="net.sumile.producer.ReturnCallBackListener"/>

我們在配置文件中配置了三個(gè)發(fā)送消息的模板,其中 amqpTemplate 和 amqpTemplate2 是兩個(gè)普通的發(fā)送模板,它定義了 exchange 和 routing-key。
而 amqpTemplate3 比較特殊,它定義了 mandatory,這個(gè)用來標(biāo)識(shí)這個(gè)模板發(fā)送的消息是否需要回執(zhí)(當(dāng) mandatory 標(biāo)志位設(shè)置為 true 時(shí),如果 exchange 根據(jù)自身類型和消息 routeKey 無法找到一個(gè)符合條件的 queue,那么會(huì)調(diào)用 basic.return 方法將消息返還給生產(chǎn)者;當(dāng) mandatory 設(shè)為 false 時(shí),出現(xiàn)上述情形 broker 會(huì)直接將消息扔掉。
這就是為什么需要使用 bean 標(biāo)簽而不使用<rabbit:template 標(biāo)簽的原因。同時(shí)我們看到 amqpTemplate 和 amqpTemplate2 使用的 connection-factory 是 connectionFactory,而 amqpTemplate3 使用的是 rabbitConnectionFactory,這是因?yàn)槿绻枰貓?zhí)的話,需要在 Connection-Factory 中指定一個(gè)參數(shù) publisherConfirms,從下面兩個(gè) connection-factory 的配置就可以看出:

<rabbit:connection-factory id="connectionFactory" username="admin" password="123456"
 host="192.168.1.198"
 port="5672"
 virtual-host="/"/>
<bean id="rabbitConnectionFactory"
 class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
 <property name="host" value="192.168.1.198"/>
 <property name="username" value="admin"/>
 <property name="password" value="123456"/>
 <property name="port" value="5672"/>
 <property name="virtualHost" value="/"/>
 <property name="channelCacheSize" value="5"/>
 <property name="publisherConfirms" value="true"/>
</bean>

至此,配置文件與 RabbitMQ 的關(guān)系算是稍微的講完了,解下來,我們實(shí)測一下。
實(shí)測運(yùn)行
先放 Controler 文件以及其他一些文件 Controler

package net.sumile.controler;
 
import net.sumile.producer.MessageProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
 
 
/**
 * Created by Administrator on 2017/6/14 0014.
 */
@Controller
public class ControlerM {
 @Autowired
 MessageProducer messageProducer;
 
 @RequestMapping(value = "/home")
 public void send(@RequestParam(value = "type", defaultValue = "1") String type,
 @RequestParam(value = "routing_key", defaultValue = "default.order.phbj") String routing_key,
 @RequestParam(value = "message", defaultValue = "defaultMessage") String message) {
 if ("1".equals(type)) {
 messageProducer.sendMessage(message);
 } else if ("2".equals(type)) {
 messageProducer.sendMessage2(message);
 } else if ("3".equals(type)) {
 messageProducer.sendMessage3(routing_key, message);
 }
 }
}

以及 MessageProducer

package net.sumile.producer;
 
import javax.annotation.Resource;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
 
/**
 * 功能概要:消息產(chǎn)生, 提交到隊(duì)列中去
 */
@Service
public class MessageProducer {
 
 private Logger logger = LoggerFactory.getLogger(MessageProducer.class);
 
 @Resource
 private RabbitTemplate amqpTemplate;
 @Resource
 private RabbitTemplate amqpTemplate2;
 @Resource
 private RabbitTemplate amqpTemplate3;
 
 public void sendMessage(Object message) {
 System.out.println("1 發(fā)送:" + message);
 amqpTemplate.convertAndSend(message);
 }
 
 public void sendMessage2(Object message) {
 System.out.println("2 發(fā)送:" + message);
 amqpTemplate2.convertAndSend(message);
 }
 
 public void sendMessage3(final String routingKey, final Object message) {
 System.out.println("3 發(fā)送:[" + message + "] routing-key = [" + routingKey + "]");
 amqpTemplate3.convertAndSend(routingKey, message);
// for (int i = 1; i < 3000000; i++) {
// System.out.println("3 發(fā)送:[" + message + "] routing-key = [" + routingKey + "]" + i);
// amqpTemplate3.convertAndSend(routingKey, message);
// }
 }
}

以及兩個(gè)接收者中的一個(gè),另外一個(gè)類似,只是輸出的文字由 Queue 替換為了 Queue2 用來區(qū)分接收的是哪個(gè) queue 中的 message

package net.sumile.consumer;
 
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
 
import static java.lang.Thread.sleep;
 
/**
 * 功能概要:消費(fèi)接收
 *
 * @author sumile
 * @since 2017 年 6 月 23 日 15:38:07
 */
public class MessageConsumer implements ChannelAwareMessageListener {
 
 private Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
 
 @Override
 public void onMessage(Message message, Channel channel) throws Exception {
 System.out.println("接收到 Queue:" + new String(message.getBody()));
 try {
 sleep(3000);
 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
 } catch (InterruptedException e) {
 
 }
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
 }
}

最重要的就是這幾個(gè)文件,來捋一遍過程:首先通過瀏覽器訪問一個(gè)我們的服務(wù)器地址,進(jìn)入 controler,根據(jù)傳過來的 type 去調(diào)用不同的模板發(fā)送不同的消息,然后由對應(yīng)的 Consumer 接收并打印出來。

請求地址:http://localhost:8080/home?type=1&routing_key=myO.pay.phbj&message=k打印:
1 發(fā)送:k
接收到 Queue2:k

因?yàn)?template 對應(yīng)的 Exchange 是 exchange,而 exchange 對應(yīng)的 queue 是 queue2,所以打印出來的接受者是 queue2
請求地址:http://localhost:8080/home?type=2&routing_key=myO.pay.phbj&message=65打印:
2 發(fā)送:65
接收到 Queue:65

原理同上
請求地址:http://localhost:8080/home?type=3&routing_key=myO.pay.phbj&message=65打印:
2 發(fā)送:65
接收到 Queue:65

原理同上
請求地址:http://localhost:8080/home?type=3&routing_key=myO.pay.phbj&message=65打印:
3 發(fā)送:[65] routing-key = [myO.pay.phbj]
接收到 Queue:65

這里 routing_key 匹配到了*.pay.phbj,所以發(fā)送到 queue 中并由 queue 的 Consumer 接收
請求地址:http://localhost:8080/home?type=3&routing_key=myO.log.phbj&message=65打印:
3 發(fā)送:[65] routing-key = [myO.log.phbj]
接收到 Queue2:65

這里 routing_key 匹配到了*.log.phbj,所以發(fā)送到 queue2 中并由 queue2 的 Consumer 接收

confirmCallback 和 returnCallback

接下來我們來看一組請求:請求地址:http://localhost:8080/home?type=3&touting_key=myO.l2og.phbj&message=65看這組請求,我們知道是調(diào)用 amqpTemplate3 來發(fā)送的,但是并沒有 binding-key 與之對應(yīng),所以這個(gè) Message 發(fā)送到 Exchange 之后 Exchange 不知道該交給哪個(gè) Queue。但是由于我們設(shè)置了

<property name="returnCallback" ref="returnCallBackListener"/>

所以如果 Exchange 沒有找到匹配的 queue 的時(shí)候,就會(huì)進(jìn)入到這個(gè)類的方法中,由我們來處理這條迷路的 Message,不至于將這條消息丟失了。
那這個(gè)配置是做什么的呢?

<property name="confirmCallback" ref="confirmCallBackListener"/>

簡單來說,如果發(fā)送出去的消息找不到 Exchange,到 confirmCallback 中,如果找到了 Exchange 找不到 Queue,到 returnCallback 中。其余的可以參考這個(gè) RabbitMQ(四) 消息確認(rèn) (發(fā)送確認(rèn), 接收確認(rèn)),如果要測試的話我們可以不停的發(fā)送消息,然后手動(dòng)的將要路由到的 queue 刪掉,就會(huì)出現(xiàn)這種情況了。

Consumer 的回復(fù)

我們在 Consumer 的配置中設(shè)置了 acknowledge 參數(shù),表示需要手動(dòng)回復(fù)“已經(jīng)接收到該消息”然后 queue 才會(huì)刪除該 Message。下面來實(shí)測下如果不回復(fù)會(huì)怎么樣
我們將 MessageConsumer 中的回復(fù)代碼注釋掉,并請求地址:http://localhost:8080/home?type=2&routing_key=myO.pay.phbj&message=65。看看 RabbitMQ 中 queue 里面 Message 是怎么變化的

public class MessageConsumer implements ChannelAwareMessageListener {
 
 private Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
 
 @Override
 public void onMessage(Message message, Channel channel) throws Exception {
 System.out.println("接收到 Queue:" + new String(message.getBody()));
// try {
// sleep(3000);
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
// } catch (InterruptedException e) {
//
// }
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
 }
}

我們請求三遍上面的那個(gè)地址,然后刦看控制臺(tái),會(huì)看到回復(fù):

2 發(fā)送:65
接收到 Queue:65
2 發(fā)送:65
接收到 Queue:65
2 發(fā)送:65
接收到 Queue:65

接收到了。然后再去看 RabbitMQ 的網(wǎng)頁控制端:http://192.168.1.198:15672


queue 中累計(jì)了三條消息,而這三條消息已經(jīng)是處理過的,如果有消息不停的進(jìn)入,結(jié)果就是堆滿內(nèi)存
這是最需要注意的一點(diǎn)

都是自己在實(shí)際了解學(xué)習(xí)過程中遇到的一些問題以及感悟,看了很多博客,感謝各位大牛。有錯(cuò)誤請指出,望不吝賜教。

RabbitMQ-Demo

我的博客—sumile

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,992評(píng)論 19 139
  • 1. 歷史 RabbitMQ是一個(gè)由erlang開發(fā)的AMQP(Advanced Message Queue )的...
    高廣超閱讀 6,127評(píng)論 3 51
  • 來源 RabbitMQ是用Erlang實(shí)現(xiàn)的一個(gè)高并發(fā)高可靠AMQP消息隊(duì)列服務(wù)器。支持消息的持久化、事務(wù)、擁塞控...
    jiangmo閱讀 10,409評(píng)論 2 34
  • 什么叫消息隊(duì)列 消息(Message)是指在應(yīng)用間傳送的數(shù)據(jù)。消息可以非常簡單,比如只包含文本字符串,也可以更復(fù)雜...
    lijun_m閱讀 1,366評(píng)論 0 1
  • 1 RabbitMQ安裝部署 這里是ErLang環(huán)境的下載地址http://www.erlang.org/down...
    Bobby0322閱讀 2,273評(píng)論 0 11