前言
原來公司項目的消息中間件一直在用RabbitMQ,今天抽出時間簡單總結梳理一下關於RabbitMQ的相關知識點。我們知道消息隊列在分佈式系統中應用的的地方有很多,它也有很多種類型,除了今天重點介紹的RabbitMQ,還有像ActiveMQ,阿里的RocketMQ,在大數據場景中經常用到的Kafka,還有一些其他的諸如ZeroMQ,MetaMQ等等。ActiveMQ在之前其實一直很火,許多公司的項目也都在使用,但近來它的社區變得不太活躍,因此工作中遇到相關問題的時候,對應的解決方案不是太好找,所以現在ActiveMQ慢慢變得不如從前那麼流行了,而RabbitMQ、Kafka等正在慢慢的佔據主流。
不廢話了,開始講解我們今天的重點RabbitMQ吧。今天入門主要講解下面三個部分:
目錄
一、RabbitMQ的下載安裝
RabbitMQ由Erlang語言開發的,所以我們要向在項目中用RabbitMQ,除了要安裝RabbitMQ外還需要安裝erlong語言以確保對RabbitMQ的環境支持。另外需要注意的一點是在安裝RabbitMQ和erlong的時候,要確保二者的版本相互匹配兼容,我用的RabbitMQ是3.7.10版本erlong是21.3版本。
1.下載erlong
erlong下載地址:http://www.erlang.org/downloads
下載好以後,以管理員方式運行此文件,安裝,除了安裝目錄可以自己選擇一下外,其他的一路下一步即可。
同java一樣,erlang安裝完成需要配置erlang的環境變量:
ERLANG_HOME=D:\others\erlong\erl10.3
在path中添加%ERLANG_HOME%\bin
2.下載RabbitMQ
RabbitMQ的下載地址:http://www.rabbitmq.com/download.html
下載完成後,以管理員方式運行此文件,安裝。
3.啓動RabbitMQ
安裝成功後會自動創建RabbitMQ服務並且啓動。我們可以到系統服務列表中找一下,如下圖已經啓動了
如果沒有啓動則進入安裝目錄下的sbin目錄手動啓動:
4.安裝RabbitMQ的管理插件
安裝rabbitMQ的管理插件,主要是方便我們在瀏覽器端輸入網址後進入RabbitMQ的後臺管理RabbitMQ。
在命令行下進入RabbitMQ安裝目錄的sbin目錄下,輸入指令:rabbitmq-plugins.bat enable rabbitmq_management
之後我們就可以通過http://localhost:15672進入到RabbitMQ的管理後臺了,如下(賬號密碼默認的均爲guest):
二、RabbitMQ的工作原理
上圖是RabbitMQ的基本結構,我們來對其中的各個部分做簡單說明:
- Producer :消息生產者,即生產方客戶端,生產方客戶端將消息發送到MQ。
- Consumer :消息消費者,即消費方客戶端,接收MQ轉發的消息。
- Broker :消息隊列服務進程,此進程包括兩個部分:Exchange(交換機)和Queue(隊列)。
- Exchange :消息隊列交換機,按一定的規則將消息路由轉發到某個隊列,對消息進行過慮。
- Queue:消息隊列,存儲消息的隊列,消息到達隊列並轉發給指定的消費方。
首先消息生產者想要發送消息給RabbitMQ必須先與其建立連接(connect)這個連接的底層是通過socket實現的,我們不必太過關心。在連接的內部其實有很多通道(Channel),具體的消息發送其實就是通過這些通道發送到RabbitMQ的。而消息的消費者一方要想拿到RabbitMQ中的消息也必須與其建立連接,這個連接(包括裏面的通道)和生產者一方是一樣的。對上方的幾個概念有大體的瞭解即可,我們會在接下來的部分結合實際案例來詳細說明各個部分的功能作用。
總結——消息發佈接收流程:
發送消息:
- 生產者和Broker建立TCP連接
- 生產者和Broker建立通道
- 生產者通過通道消息發送給Broker,由Exchange將消息進行轉發
- Exchange將消息轉發到指定的Queue(隊列)
接收消息:
- 消費者和Broker建立TCP連接
- 消費者和Broker建立通道
- 消費者監聽指定的Queue(隊列)
- 當有消息到達Queue時Broker默認將消息推送給消費者
- 消費者接收到消息
三、RabbitMQ入門測試
進過第一步的RabbitMQ安裝和第二步RabbitMQ原理的簡單介紹,現在我們簡單的寫個測試,讓消息生產端發送一個“hello world”到MQ,然後由MQ轉到消息消費端。這裏需要注意一點:不管是消息的生產者和還是消費者,相對於RabbitMQ它們二者都屬於客戶端。所以在創建對應的對應時要添加RabbitMQ的客戶端依賴,我們這裏先用 rabbitMQ官方提供的java client測試,目的是對RabbitMQ的交互過程有個清晰的認識。
1.創建maven工程
創建生產者工程和消費者工程,分別加入RabbitMQ客戶端 java client的依賴。
test-rabbitmq-producer:生產者工程
test-rabbitmq-consumer:消費者工程
二者的pom.xml文件相同,都爲如下內容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--每一個springboot工程都必須有這個依賴-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-rabbitmq-producer</artifactId>
<dependencies>
<!--RabbitMQ的客戶端依賴-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.0</version>
</dependency>
<!--記錄日誌-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
</project>
2.生產者Producer01
在生產者工程下的test中創建測試類如下:
這裏是作爲入門程序爲了讓大家對RabbitMQ的工作過程有大體瞭解,並對每個參數大體有個印象,所以代碼有點多。其實在實際項目中通過在springboot中引入別的客戶端依賴,我們只需要寫很少的代碼就可以完成下面同樣的功能。
public class Producer01 {
//申明隊列
private static final String QUEUE = "helloworld";
public static void main(String[] args) {
//第一步:生產者和Broker建立TCP連接。
//通過連接工廠創建新的連接和mq建立連接
ConnectionFactory connectionFactory = new ConnectionFactory();
//設置ip地址,我們在本地所以直接是127.0.0.1
connectionFactory.setHost("127.0.0.1");
//設置端口,RabbitMQ默認端口即爲5672
connectionFactory.setPort(5672);
//設置賬號和密碼
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設置虛擬機,一個mq服務可以設置多個虛擬機,每個虛擬機就相當於一個獨立的mq。這裏不理解可以暫時略過
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
//建立新連接
connection = connectionFactory.newConnection();
//第二步:生產者和Broker建立通道。
//創建會話通道,生產者和mq服務所有通信都在channel通道中完成
channel = connection.createChannel();
//聲明隊列,如果隊列在mq中已經存在則使用已有的,如果沒有則創建新的
/**
* 參數明細:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* 1、queue 隊列名稱
* 2、durable 是否持久化,如果持久化,mq重啓後隊列還在
* 3、exclusive 是否獨佔連接,隊列只允許在該連接中訪問,如果connection連接關閉隊列則自動刪除,如果將此參數設置true可用於臨時隊列的創建
* 4、autoDelete 自動刪除,隊列不再使用時是否自動刪除此隊列,如果將此參數和exclusive參數設置爲true就可以實現臨時隊列(隊列不用了就自動刪除)
* 5、arguments 參數,可以設置一個隊列的擴展參數,比如:可設置存活時間等
*/
channel.queueDeclare(QUEUE,true,false,false,null);
//第三步:生產者通過通道消息發送給Broker,由Exchange將消息進行轉發。
/**
* 參數明細:String exchange, String routingKey, BasicProperties props, byte[] body
* 1、exchange,交換機,如果不指定將使用mq的默認交換機(設置爲"")
* 2、routingKey,路由key,交換機根據路由key來將消息轉發到指定的隊列,如果使用默認交換機,routingKey設置爲隊列的名稱
* 3、props,消息的屬性
* 4、body,消息內容
*/
//消息內容
String message = "hello world!";
channel.basicPublish("",QUEUE,null,message.getBytes());
System.out.println("send to mq "+message);
} catch (Exception e) {
e.printStackTrace();
} finally {
//關閉連接
//先關閉通道
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
//再關閉連接
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.生產者Consumer01
在消費者工程下的test中創建測試類如下:
public class Consumer01 {
//申明隊列,要與生產者的保持一致
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
//第一步:消費者和Broker建立TCP連接
//通過連接工廠創建新的連接和mq建立連接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設置虛擬機,一個mq服務可以設置多個虛擬機,每個虛擬機就相當於一個獨立的mq
connectionFactory.setVirtualHost("/");
//建立新連接
Connection connection = connectionFactory.newConnection();
//第二步:消費者和Broker建立通道。
//創建會話通道,消費者和mq服務所有通信都在channel通道中完成
Channel channel = connection.createChannel();
//第三步:消費者監聽指定的Queue(隊列)
//聲明隊列,如果隊列在mq中已經存在則使用已有的,如果沒有則創建新的
/**
* 參數明細:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* 1、queue 隊列名稱
* 2、durable 是否持久化,如果持久化,mq重啓後隊列還在
* 3、exclusive 是否獨佔連接,隊列只允許在該連接中訪問,如果connection連接關閉隊列則自動刪除,如果將此參數設置true可用於臨時隊列的創建
* 4、autoDelete 自動刪除,隊列不再使用時是否自動刪除此隊列,如果將此參數和exclusive參數設置爲true就可以實現臨時隊列(隊列不用了就自動刪除)
* 5、arguments 參數,可以設置一個隊列的擴展參數,比如:可設置存活時間
*/
channel.queueDeclare(QUEUE,true,false,false,null);
//第四步:實現消費方法,當有消息到達Queue時Broker默認將消息推送給消費者。
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 當接收到消息後此方法將被調用,
* @param consumerTag 消費者標籤,用來標識消費者的,在監聽隊列時設置channel.basicConsume
* @param envelope 信封,通過envelope
* @param properties 消息屬性
* @param body 消息內容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交換機
String exchange = envelope.getExchange();
//消息id,mq在channel中用來標識消息的id,可用於確認消息已接收
long deliveryTag = envelope.getDeliveryTag();
//消息內容
String message= new String(body,"utf-8");
System.out.println("receive message:"+message);
}
};
//監聽隊列
/**
* 參數明細:String queue, boolean autoAck, Consumer callback
* 1、queue 隊列名稱
* 2、autoAck 自動回覆,當消費者接收到消息後要告訴mq消息已接收,如果將此參數設置爲tru表示會自動回覆mq,如果設置爲false要通過編程實現回覆
* 3、callback,消費方法,當消費者接收到消息要執行的方法
*/
channel.basicConsume(QUEUE,true,defaultConsumer);
}
}
4.運行測試
現在我們先把Producer01類運行起來,然後通過瀏覽器登錄到RabbitMQ的後臺,可以看到總共有一條待發送的消息,如下:
這時我們再把Consumer01這個類啓動起來,在控制檯看到如下日誌,說明消費端啓動起來以後,mq就把其隊列中從producer01接收到的消息發送給了Consumer01。再到RabbitMQ的後臺看一看發現現在的待發送消息已經變爲了0條
receive message:hello world!
5.流程總結
通過上面的代碼編寫,我們簡單的總結一下生產端和消費端的操作流程。
生產端:
- 創建連接
- 創建通道
- 聲明隊列
- 發送消息
消費端:
- 創建連接
- 創建通道
- 聲明隊列
- 監聽隊列
- 接收消息
- ack回覆