RabbitMQ簡介以及與SpringBoot整合示例
什麼是訊息佇列
訊息(Message)是指在應用間傳送的資料。訊息可以非常簡單,比如只包含文字字串,也可以更復雜,可能包含嵌入物件。
訊息佇列(Message Queue)是一種應用間的通訊方式,訊息傳送後可以立即返回,由訊息系統來確保訊息的可靠傳遞。訊息釋出者只管把訊息釋出到 MQ 中而不用管誰來取,訊息使用者只管從 MQ 中取訊息而不管是誰釋出的。這樣釋出者和使用者都不用知道對方的存在。
RabbitMQ
簡介
RabbitMQ安裝
1:下載
下載 rabbitMQ :www.rabbitmq.com/download.ht…
安裝rabbitmq需要erlang,下載erlang:www.erlang.org/download.ht…
2:Windows安裝RabbitMQ
rabbitMQ安裝,檢視安裝文件:www.rabbitmq.com/install-win…
3:安裝Erlang
下載完成Erlang後,直接開啟檔案下一步就可以安裝完成了,安裝完成ERLANG後再回過來安裝RabbitMQ。
4、啟動RabbitMQ
如果是安裝版的直接在開始那裡找到安裝目錄點選啟動即可
5、安裝管理工具
參考官方文件:www.rabbitmq.com/management.…
操作起來很簡單,只需要在DOS下面,進入安裝目錄(F:\RabbitMQ Server\rabbitmq_server-3.5.3\sbin)執行如下命令就可以成功安裝。
rabbitmq-plugins enable rabbitmq_management
複製程式碼
可以通過訪問http://localhost:15672進行測試,預設的登陸賬號為:guest,密碼為:guest。
如果訪問成功了,恭喜,整個rabbitMQ安裝完成了。
RabbitMQ安裝就不細講了,大體就這樣子,有什麼問題可以具體檢視其他官網詳細的安裝文件。
RabbitMQ特點
RabbitMQ
是一個由 Erlang 語言開發的AMQP
的開源實現。
AMQP
:Advanced Message Queue,高階訊息佇列協議。它是應用層協議的一個開放標準,為面向訊息的中介軟體設計,基於此協議的客戶端與訊息中介軟體可傳遞訊息,並不受產品、開發語言等條件的限制。
RabbitMQ
最初起源於金融系統,用於在分散式系統中儲存轉發訊息,在易用性、擴充套件性、高可用性等方面表現不俗。具體特點包括:
1:可靠性(Reliability)
RabbitMQ
使用一些機制來保證可靠性,如持久化、傳輸確認、釋出確認。
2:靈活的路由(Flexible Routing)
在訊息進入佇列之前,通過 Exchange 來路由訊息的。對於典型的路由功能,RabbitMQ
已經提供了一些內建的 Exchange 來實現。針對更復雜的路由功能,可以將多個 Exchange 繫結在一起,也通過外掛機制實現自己的 Exchange 。
3:訊息叢集(Clustering)
多個 RabbitMQ
伺服器可以組成一個叢集,形成一個邏輯 Broker 。
4:高可用(Highly Available Queues)
佇列可以在叢集中的機器上進行映象,使得在部分節點出問題的情況下佇列仍然可用。
5:多種協議(Multi-protocol)
RabbitMQ
支援多種訊息佇列協議,比如 STOMP、MQTT
等等。
6:多語言客戶端(Many Clients)
RabbitMQ
幾乎支援所有常用語言,比如 Java、.NET、Ruby 等等。
7:管理介面(Management UI)
RabbitMQ
提供了一個易用的使用者介面,使得使用者可以監控和管理訊息 Broker 的許多方面。
8:跟蹤機制(Tracing)
如果訊息異常,RabbitMQ 提供了訊息跟蹤機制,使用者可以找出發生了什麼。
9:外掛機制(Plugin System)
RabbitMQ 提供了許多外掛,來從多方面進行擴充套件,也可以編寫自己的外掛。
訊息模型
所有MQ產品從模型抽象上來說都是一樣的過程:
消費者(consumer)訂閱某個佇列,生產者(producer)建立訊息,然後釋出到佇列(queue)中去,訊息將傳送到訂閱了該佇列的消費者。
RabbitMQ
基本概念
生產者(Producer) > 交換器(Exchange) > 佇列(Queue) > 消費者(Consumer)
1: Message
訊息,訊息是不具名的,它由訊息頭和訊息體組成。訊息體是不透明的,而訊息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對於其他訊息的優先權)、delivery-mode(指出該訊息可能需要永續性儲存)等。
2: Publisher
訊息的生產者,也是一個向交換器釋出訊息的客戶端應用程式。
3: Exchange
交換器,用來接收生產者傳送的訊息並將這些訊息路由給伺服器中的佇列。
4: Binding
繫結,用於訊息佇列和交換器之間的關聯。一個繫結就是基於路由鍵將交換器和訊息佇列連線起來的路由規則,所以可以將交換器理解成一個由繫結構成的路由表。
5: Queue
訊息佇列,用來儲存訊息直到傳送給消費者。它是訊息的容器,也是訊息的終點。一個訊息可投入一個或多個佇列。訊息一直在佇列裡面,等待消費者連線到這個佇列將其取走。
6: Connection
網路連線,比如一個TCP連線。
7: Channel
通道,多路複用連線中的一條獨立的雙向資料流通道。通道是建立在真實的TCP連線內地虛擬連線,AMQP
命令都是通過通道發出去的,不管是釋出訊息、訂閱佇列還是接收訊息,這些動作都是通過通道完成。因為對於作業系統來說建立和銷燬 TCP 都是非常昂貴的開銷,所以引入了通道的概念,以複用一條 TCP 連線。
8: Consumer
訊息的消費者,表示一個從訊息佇列中取得訊息的客戶端應用程式。
9: Virtual Host
虛擬主機,表示一批交換器、訊息佇列和相關物件。虛擬主機是共享相同的身份認證和加密環境的獨立伺服器域。每個 vhost 本質上就是一個 mini 版的 RabbitMQ 伺服器,擁有自己的佇列、交換器、繫結和許可權機制。vhost 是 AMQP 概念的基礎,必須在連線時指定,RabbitMQ 預設的 vhost 是 / 。
10: Broker
表示訊息佇列伺服器實體。
RabbitMQ三種模式
1: Direct
訊息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將訊息發到對應的佇列中。路由鍵與佇列名完全匹配,如果一個佇列繫結到交換機要求路由鍵為“dog”,則只轉發 routing key 標記為“dog”的訊息,不會轉發“dog.puppy”,也不會轉發“dog.guard”等等。它是完全匹配、單播的模式。
2: Fanout
每個發到 fanout 型別交換器的訊息都會分到所有繫結的佇列上去。fanout 交換器不處理路由鍵,只是簡單的將佇列繫結到交換器上,每個傳送到交換器的訊息都會被轉發到與該交換器繫結的所有佇列上。很像子網廣播,每臺子網內的主機都獲得了一份複製的訊息。fanout 型別轉發訊息是最快的。
3: Topic
topic 交換器通過模式匹配分配訊息的路由鍵屬性,將路由鍵和某個模式進行匹配,此時佇列需要繫結到一個模式上。它將路由鍵和繫結鍵的字串切分成單詞,這些單詞之間用點隔開。它同樣也會識別兩個萬用字元:符號“#”和符號“”。#匹配0個或多個單詞,匹配不多不少一個單詞。
4: headers
不常用,和direct功能接近,不討論。
上面只是簡單的介紹了一下RabbitMQ的三種模式,接下來結合程式碼例項來看看。
SpringBoot整合RabbitMQ
SpringBoot就不多做介紹了,用idea建立SpringBoot專案,pom檔案中匯入如下包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
複製程式碼
然後在applicaition.yml加入如下配置
spring:
rabbitmq:
host: localhost
port: 5672
username: magic
password: 123456
複製程式碼
這樣就算是引入RabbitMQ就算成功了,接下來講一下RabbitMQ三種模式結合SpringBoot。
Direct
Direct就是一對一模式,從上面可以知道,RabbitMQ有傳送者,交換機,佇列,接收者。Direct就是一個傳送者對應一個接收者。如果有多個,只會有一個接收到訊息。
先建立該模式的佇列(Queue)
@Configuration
public class OneByOneConfig {
@Bean
public Queue oneQueue(){
return new Queue("OneByOne");
}
}
複製程式碼
可以看到新建的佇列名字叫做OneByOne,接下來建立傳送者類。
@Component
public class OneByOneSender {
@Autowired
AmqpTemplate rabbitTemplate;
public void send() {
String context = "OneByOneSender" + new Date();
System.out.println("OneSender : " + context);
this.rabbitTemplate.convertAndSend("OneByOne", context);
}
}
複製程式碼
這裡面AmqpTemplate
時SpringBoot
包裝好的用來操作訊息佇列的。convertAndSend是裡面的一個方法,用來傳送者匹配交換機,佇列以及攜帶訊息的。Drect裡面只需要匹配佇列以及攜帶訊息即可。
接下來建立接收者
@Component
@RabbitListener(queues = "OneByOne")
public class OneByOneReceiver {
@RabbitHandler
public void receiver(String context){
System.out.println("OneByOne-Receiver::::"+context);
}
}
複製程式碼
@RabbitListener是用來繫結佇列的,該接收者繫結了OneByOne這個佇列,下面的@RabbitHandler註解是用來表示該方法是接收者接收訊息的方法。
接下來進行測試,SpringBoot有測試的test類
匯入OneByOneSender類
@Autowired
private OneByOneSender oneByOneSender;
@Test
public void oneByOneTest(){
oneByOneSender.send();
}
複製程式碼
執行可以看到結果輸出:
OneSender : OneByOneSenderThu Aug 22 17:00:56 CST 2019
OneByOne-Receiver::::OneByOneSenderThu Aug 22 17:00:56 CST 2019
複製程式碼
可以看到傳送者傳送列印的輸出,以及接收者接收到的訊息列印出來的結果。
Fanout
Fanout模式就是釋出訂閱模式,釋出者釋出了訊息時候順便繫結交換器,交換器又是跟佇列繫結的,那麼跟這個交換器繫結的所有佇列都會收到這個訊息,相應的繫結了這些佇列的所有接收者都會接收到傳送的訊息。
具體看程式碼再分析:
建立FanoutConfig類,然後建立佇列,交換器,以及繫結佇列與交換器。
@Configuration
public class FanoutConfig {
//佇列1
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.a");
}
//佇列2
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.b");
}
//佇列3
@Bean
public Queue fanoutQueue3(){
return new Queue("fanout.c");
}
//交換器
@Bean
FanoutExchange fanoutExchange(){
return new FanoutExchange("fanoutExchange");
}
//繫結交換器和佇列1
@Bean
Binding bindingFanout1(){
return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
}
//繫結交換器和佇列2
@Bean
Binding bindingFanout2(){
return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
}
//繫結交換器和佇列3
@Bean
Binding bindingFanout3(){
return BindingBuilder.bind(fanoutQueue3()).to(fanoutExchange());
}
}
複製程式碼
可以看到上面建立了三個佇列,到時候再建立三個接收者,那麼這三個接收者再Fanout模式下,只要釋出者繫結了該fanoutExchange
交換器,那麼他們就應該都可以收到訊息。
建立傳送者
@Component
public class FanoutSender {
@Autowired
AmqpTemplate rabbitTemplate;
public void fanSender1(){
Date date = new Date();
String dateString = new SimpleDateFormat("yyyy-mm-DD hh:MM:ss").format(date);
String message = "FanSender1111:"+dateString;
this.rabbitTemplate.convertAndSend("fanoutExchange","",message);
}
public void fanSender2(){
Date date = new Date();
String dateString = new SimpleDateFormat("yyyy-mm-DD hh:MM:ss").format(date);
String message = "FanSender2222:"+dateString;
this.rabbitTemplate.convertAndSend("fanoutExchange","",message);
}
}
複製程式碼
建立了兩個傳送者,分別繫結了fanoutExchange交換器,中間的是交換器選擇佇列是的條件routerKey,這個在後面的Topic模式中會用到,現在因為是所有佇列都會收到,所有就沒有條件。
建立三和接收者分別繫結三個佇列
@Component
@RabbitListener(queues = "fanout.a")
public class Fanout1Reciver {
@RabbitHandler
public void receiver(String message){
System.out.println("FanoutReceiver---fanout.a:"+message);
}
}
@Component
@RabbitListener(queues = "fanout.b")
public class Fanout2Reciver {
@RabbitHandler
public void receiver(String message){
System.out.println("FanoutReceiver---fanout.b:"+message);
}
}
@Component
@RabbitListener(queues = "fanout.c")
public class Fanout3Reciver {
@RabbitHandler
public void receiver(String message){
System.out.println("FanoutReceiver---fanout.c:"+message);
}
}
複製程式碼
可以看到接收者分別繫結了fanout.a/b/c三個佇列,接下來進行測試
執行下面程式碼:
@Autowired
FanoutSender fanoutSender;
@Test
public void fanoutSenderTest(){
fanoutSender.fanSender1();
fanoutSender.fanSender2();
}
複製程式碼
結果:
FanoutReceiver---fanout.b:FanSender1111:2019-40-234 05:08:32
FanoutReceiver---fanout.c:FanSender1111:2019-40-234 05:08:32
FanoutReceiver---fanout.a:FanSender1111:2019-40-234 05:08:32
FanoutReceiver---fanout.c:FanSender2222:2019-40-234 05:08:32
FanoutReceiver---fanout.b:FanSender2222:2019-40-234 05:08:32
FanoutReceiver---fanout.a:FanSender2222:2019-40-234 05:08:32
複製程式碼
因為我們的FanoutSender
傳送者沒有寫輸出,所以可以看到上面六條都是接收者的輸出,兩個傳送者分別傳送了一條訊息,三個接收者都收到了這兩個傳送者傳送的訊息。
Topic
Topic模式就相當於釋出訂閱模式交換機和佇列之間加上了一定的匹配規則。只有符合規則的訊息才能到這個佇列中去從而被接收者收到。看程式碼
建立TopicConfig:
@Configuration
public class TopicConfig {
@Bean
public Queue topicQueue1(){
return new Queue("topic.a");
}
@Bean
public Queue topicQueue2(){
return new Queue("topic.b");
}
@Bean
public Queue topicQueue3(){
return new Queue("topic.c");
}
@Bean
TopicExchange topicExchange(){
return new TopicExchange("topicExchange");
}
@Bean
public Binding binding1(){
return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("topic.msg");
}
@Bean
public Binding binding2(){
return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.#");
}
@Bean
public Binding binding3(){
return BindingBuilder.bind(topicQueue3()).to(topicExchange()).with("topic.*.z");
}
}
複製程式碼
可以看到建立了三個佇列和一個交換器,並且將交換器和佇列進行了繫結,在繫結的過程中多了一個條件with
,這是一種萬用字元方式的匹配,. 為分隔符,*代表一個,#表示0個或者多個,如上面的topic.#就可已匹配,topic,topic.z,topic.ma.z.z.z等,而topic.*.z就可以匹配topic.m.z,topic.z.z等,而topic.msg就只能匹配topic.msg條件的訊息。
建立傳送者:
@Component
public class TopicSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void topicSend1(){
Date date = new Date();
String dateString = new SimpleDateFormat("yyyy-mm-DD hh:MM:ss").format(date);
dateString = "[topic.msg] Send1 msg:" + dateString;
System.out.println(dateString);
this.rabbitTemplate.convertAndSend("topicExchange","topic.msg",dateString);
}
public void topicSend2(){
Date date = new Date();
String dateString = new SimpleDateFormat("yyyy-mm-DD hh:MM:ss").format(date);
dateString = "[topic.good.msg] Send2 msg:" + dateString;
System.out.println(dateString);
this.rabbitTemplate.convertAndSend("topicExchange","topic.good.msg",dateString);
}
public void topicSend3(){
Date date = new Date();
String dateString = new SimpleDateFormat("yyyy-mm-DD hh:MM:ss").format(date);
dateString = "[topic.m.z] Send3 msg:" + dateString;
System.out.println(dateString);
this.rabbitTemplate.convertAndSend("topicExchange","topic.m.z",dateString);
}
}
複製程式碼
其中
this.rabbitTemplate.convertAndSend("topicExchange",
"topic.good.msg", dateString);
複製程式碼
傳送者傳送訊息時,傳的三個引數,第一個時你要傳給的交換機,第二個是傳給交換機的條件,在Topic模式中,佇列與交換機會有一個匹配的條件,如果現在有三個佇列和交換機繫結,分別條件是:A: topic.# ,B: topic.msg, C:topic.*.z(#代表多個,*代表一個)。
則上面程式碼給的key時 topic.good.msg 就只能匹配到A佇列中去。如果時topic.msg,那麼就匹配到B佇列中了,如果是topic.good.z/topic.msg.z 那麼會匹配到A和C兩個佇列中去。
而同時,只要繫結了A,B,C的佇列的接收者,如果上面匹配成功,訊息就會被髮布到佇列中,相應的繫結了該佇列的接收者就會獲取到該訊息。
建立接收者:
@Component
@RabbitListener(queues = "topic.a")
public class Topic1Reciver {
@RabbitHandler
public void receiver(String message){
System.out.println("topic.A--Receiver::::"+message);
}
}
@Component
@RabbitListener(queues = "topic.b")
public class Topic2Reciver {
@RabbitHandler
public void receiver(String msg){
System.out.println("topic.B--Receiver::::"+msg);
}
}
@Component
@RabbitListener(queues = "topic.c")
public class Topic3Reciver {
@RabbitHandler
public void receiver(String msg){
System.out.println("topic.C--Receiver::::"+msg);
}
}
複製程式碼
三個接收者分別繫結三個佇列,看看測試以及結果
@Autowired
TopicSender topicSender;
@Test
public void topicSenderTest(){
topicSender.topicSend1();
topicSender.topicSend2();
topicSender.topicSend3();
}
複製程式碼
結果:
[topic.msg] Send1 msg:2019-00-234 06:08:00
[topic.good.msg] Send2 msg:2019-00-234 06:08:00
[topic.m.z] Send3 msg:2019-00-234 06:08:00
----------------------------------------------------
topic.B--Receiver::::[topic.msg] Send1 msg:2019-00-234 06:08:00
topic.C--Receiver::::[topic.m.z] Send3 msg:2019-00-234 06:08:00
topic.A--Receiver::::[topic.msg] Send1 msg:2019-00-234 06:08:00
topic.B--Receiver::::[topic.good.msg] Send2 msg:2019-00-234 06:08:00
topic.B--Receiver::::[topic.m.z] Send3 msg:2019-00-234 06:08:00
複製程式碼
分割線上面的是傳送者的輸出,三個傳送者分別傳送了一條訊息,根據傳送者傳入的key與交換器與佇列繫結的匹配規則進行匹配,最終匹配通過的將訊息從交換器發到佇列中,相應的繫結該佇列的接收者就可以獲取到這條訊息。
總結
在Java中,需要先建立queue佇列,接收訊息著繫結的是佇列,如果佇列中有了訊息,就會發給繫結它的接收者。然後交換機和佇列進行繫結,交換機是一個,所有佇列都在交換機上繫結著,傳送者傳送訊息時,把訊息給交換機,然後加上限制條件,topic是給滿足條件的佇列,fanout時給所有繫結交換機的佇列。普通模式就是Direct模式也就是1對1模式,傳送者直接繫結佇列,接收者也繫結佇列。互勉~
歡迎關注我的微信公眾號,一個喜歡程式碼和NBA的年輕人,主要用來分享技術收穫。