基本消息模型
官方介紹:
RabbitMQ是一個消息代理:它接受和轉發消息。 你可以把它想象成一個郵局:當你把郵件放在郵箱裏時,你可以確定郵差先生最終會把郵件發送給你的收件人。 在這個比喻中,RabbitMQ是郵政信箱,郵局和郵遞員。
RabbitMQ與郵局的主要區別是它不處理紙張,而是接受,存儲和轉發數據消息的二進制數據塊。
P(producer/ publisher):生產者,一個發送消息的用戶應用程序。
C(consumer):消費者,消費和接收有類似的意思,消費者是一個主要用來等待接收消息的用戶應用程序
隊列(紅色區域):rabbitmq內部類似於郵箱的一個概念。雖然消息流經rabbitmq和你的應用程序,但是它們只能存儲在隊列中。隊列只受主機的內存和磁盤限制,實質上是一個大的消息緩衝區。許多生產者可以發送消息到一個隊列,許多消費者可以嘗試從一個隊列接收數據。
總之:
生產者將消息發送到隊列,消費者從隊列中獲取消息,隊列是存儲消息的緩衝區。
我們將用Java編寫兩個程序;發送單個消息的生產者,以及接收消息並將其打印出來的消費者。我們將詳細介紹Java API中的一些細節,這是一個消息傳遞的“Hello World”。
我們將調用我們的消息發佈者(發送者)Send和我們的消息消費者(接收者)Recv。發佈者將連接到RabbitMQ,發送一條消息,然後退出。
1.1.1.生產者發送消息
public class Send {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
// 從連接中創建通道,這是完成大部分API的地方。
Channel channel = connection.createChannel();
// 聲明(創建)隊列,必須聲明隊列才能夠發送消息,我們可以把消息發送到隊列中。
// 聲明一個隊列是冪等的 - 只有當它不存在時纔會被創建
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息內容
String message = "Hello World!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
//關閉通道和連接
channel.close();
connection.close();
}
}
控制檯:
1.1.2.管理工具中查看消息
進入隊列頁面,可以看到新建了一個隊列:simple_queue
點擊隊列名稱,進入詳情頁,可以查看消息:
在控制檯查看消息並不會將消息消費,所以消息還在。
1.1.3.消費者獲取消息
public class Recv {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 獲取到連接
Connection connection = ConnectionUtil.getConnection();
// 創建通道
Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定義隊列的消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息體
String msg = new String(body);
System.out.println(" [x] received : " + msg + "!");
}
};
// 監聽隊列,第二個參數:是否自動進行消息確認。
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
控制檯:
這個時候,隊列中的消息就沒了:
我們發現,消費者已經獲取了消息,但是程序沒有停止,一直在監聽隊列中是否有新的消息。一旦有新的消息進入隊列,就會立即打印.
1.1.4.消息確認機制(ACK)
通過剛纔的案例可以看出,消息一旦被消費者接收,隊列中的消息就會被刪除。
那麼問題來了:RabbitMQ怎麼知道消息被接收了呢?
如果消費者領取消息後,還沒執行操作就掛掉了呢?或者拋出了異常?消息消費失敗,但是RabbitMQ無從得知,這樣消息就丟失了!
因此,RabbitMQ有一個ACK機制。當消費者獲取消息後,會向RabbitMQ發送回執ACK,告知消息已經被接收。不過這種回執ACK分兩種情況:
- 自動ACK:消息一旦被接收,消費者自動發送ACK
- 手動ACK:消息接收後,不會發送ACK,需要手動調用
大家覺得哪種更好呢?
這需要看消息的重要性:
- 如果消息不太重要,丟失也沒有影響,那麼自動ACK會比較方便
- 如果消息非常重要,不容丟失。那麼最好在消費完成後手動ACK,否則接收消息後就自動ACK,RabbitMQ就會把消息從隊列中刪除。如果此時消費者宕機,那麼消息就丟失了。
我們之前的測試都是自動ACK的,如果要手動ACK,需要改動我們的代碼:
public class Recv2 {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 獲取到連接
Connection connection = ConnectionUtil.getConnection();
// 創建通道
final Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定義隊列的消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息體
String msg = new String(body);
System.out.println(" [x] received : " + msg + "!");
// 手動進行ACK
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 監聽隊列,第二個參數false,手動進行ACK
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
注意到最後一行代碼:
channel.basicConsume(QUEUE_NAME, false, consumer);
如果第二個參數爲true,則會自動進行ACK;如果爲false,則需要手動ACK。方法的聲明:
1.1.4.1.自動ACK存在的問題
修改消費者,添加異常,如下:
生產者不做任何修改,直接運行,消息發送成功:
運行消費者,程序拋出異常。但是消息依然被消費:
管理界面:
1.1.4.2.演示手動ACK
修改消費者,把自動改成手動(去掉之前製造的異常)
生產者不變,再次運行:
運行消費者
但是,查看管理界面,發現:
停掉消費者的程序,發現:
這是因爲雖然我們設置了手動ACK,但是代碼中並沒有進行消息確認!所以消息並未被真正消費掉。
當我們關掉這個消費者,消息的狀態再次稱爲Ready
修改代碼手動ACK:
執行:
消息消費成功!