RabbitMQ入門
[toc]
一:入門
1.安裝Erlang
2.安裝RabbitMQ
3.配置
啟用 RabbitMQ's Management Plugin
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>rabbitmq-plugins.bat enable rabbitmq
Error: The following plugins could not be found:
rabbitmq
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>rabbitmq-plugins.bat enable rabbitmq_management
The following plugins have been enabled:
amqp_client
cowlib
cowboy
rabbitmq_web_dispatch
rabbitmq_management_agent
rabbitmq_management
Applying plugin configuration to rabbit@DESKTOP-BugYang... started 6 plugins.
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>net stop RabbitMQ && net start RabbitMQ
RabbitMQ 服務正在停止......
RabbitMQ 服務已成功停止。
RabbitMQ 服務正在啟動 .
RabbitMQ 服務已經啟動成功。
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>rabbitmqctl.bat list_users
Listing users
guest [administrator]
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>
http://localhost:15672/
賬號密碼:guest
4.下載maven
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.1.0</version>
</dependency>
5.建立傳送者
public class Send {
//佇列名稱
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws java.io.IOException, TimeoutException {
/**
* 建立連線連線到MabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
//設定MabbitMQ所在主機ip或者主機名
factory.setHost("localhost");
//建立一個連線
Connection connection = factory.newConnection();
//建立一個頻道
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();
}
}
列印
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[x] Sent 'hello world!'
Process finished with exit code 0
6.建立接受者
public class Rec {
//佇列名稱
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException, TimeoutException {
//開啟連線和建立頻道,與傳送端一樣
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//宣告佇列,主要為了防止訊息接收者先執行此程式,佇列還不存在時建立佇列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//建立佇列消費者
QueueingConsumer consumer = new QueueingConsumer(channel);
//指定消費佇列
channel.basicConsume(QUEUE_NAME, true, consumer);
while (true)
{
//nextDelivery是一個阻塞方法(內部實現其實是阻塞佇列的take方法)
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
}
列印
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'hello world!'
二:工作佇列
1.傳送訊息
public class NewTask
{
//佇列名稱
private final static String QUEUE_NAME = "workqueue";
public static void main(String[] args) throws IOException, TimeoutException {
//建立連工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立佇列
Channel channel = connection.createChannel();
//宣告佇列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//傳送10條訊息,依次在訊息後面附加1-10個點
for (int i = 0; i < 10; i++)
{
String dots = "";
for (int j = 0; j <= i; j++)
{
dots += ".";
}
//拼資料
String message = "helloworld" + dots+dots.length();
//推送到rabbitmq中
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
//推送完成,列印結束語句
System.out.println(" [x] Sent '" + message + "'");
}
//關閉佇列
channel.close();
//關閉訊息
connection.close();
}
}
[x] Sent 'helloworld.1'
[x] Sent 'helloworld..2'
[x] Sent 'helloworld...3'
[x] Sent 'helloworld....4'
[x] Sent 'helloworld.....5'
[x] Sent 'helloworld......6'
[x] Sent 'helloworld.......7'
[x] Sent 'helloworld........8'
[x] Sent 'helloworld.........9'
[x] Sent 'helloworld..........10'
2.接收訊息
執行兩個Work類
public class Work
{
//佇列名稱
private final static String QUEUE_NAME = "workqueue";
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException, TimeoutException {
//區分不同工作程式的輸出
int hashCode = Work.class.hashCode();
//建立連線工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立佇列
Channel channel = connection.createChannel();
//宣告佇列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(hashCode
+ " [*] Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定消費佇列
//關閉應答機制,會丟失訊息
channel.basicConsume(QUEUE_NAME, true, consumer);
//開啟應答機制,不會丟失訊息
channel.basicConsume(QUEUE_NAME, false, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(hashCode + " [x] Received '" + message + "'");
// doWork(message);
System.out.println(hashCode + " [x] Done");
}
}
/**
* 每個點耗時1s
* @param task
* @throws InterruptedException
*/
private static void doWork(String task) throws InterruptedException
{
for (char ch : task.toCharArray())
{
if (ch == '.')
Thread.sleep(1000);
}
}
}
746292446 [x] Received 'helloworld.1'
746292446 [x] Done
746292446 [x] Received 'helloworld...3'
746292446 [x] Done
746292446 [x] Received 'helloworld.....5'
746292446 [x] Done
746292446 [x] Received 'helloworld.......7'
746292446 [x] Done
746292446 [x] Received 'helloworld.........9'
746292446 [x] Done
242131142 [x] Received 'helloworld..2'
242131142 [x] Done
242131142 [x] Received 'helloworld....4'
242131142 [x] Done
242131142 [x] Received 'helloworld......6'
242131142 [x] Done
242131142 [x] Received 'helloworld........8'
242131142 [x] Done
242131142 [x] Received 'helloworld..........10'
242131142 [x] Done
可以看到,預設的,RabbitMQ會一個一個的傳送資訊給下一個消費者(consumer),而不考慮每個任務的時長等等,且是一次性分配,並非一個一個分配。平均的每個消費者將會獲得相等數量的訊息。這樣分發訊息的方式叫做round-robin。
3.訊息應答(message acknowledgments)
我們首先開啟兩個任務,然後執行傳送任務的程式碼(NewTask.java),然後立即關閉第二個任務,兩個加起來列印出來的資料會有缺失
一旦RabbItMQ交付了一個資訊給消費者,會馬上從記憶體中移除這個資訊。在這種情況下,如果殺死正在執行任務的某個工作者,我們會丟失它正在處理的資訊。我們也會丟失已經轉發給這個工作者且它還未執行的訊息。
為了保證訊息永遠不會丟失,RabbitMQ支援訊息應答(message acknowledgments)。
- 消費者傳送應答給RabbitMQ,告訴它資訊已經被接收和處理,然後RabbitMQ可以自由的進行資訊刪除。
- 如果消費者被殺死而沒有傳送應答,RabbitMQ會認為該資訊沒有被完全的處理,然後將會重新轉發給別的消費者。通過這種方式,你可以確認資訊不會被丟失,即使消者偶爾被殺死。
- 這種機制並沒有超時時間這麼一說,RabbitMQ只有在消費者連線斷開是重新轉發此資訊。如果消費者處理一個資訊需要耗費特別特別長的時間是允許的。
訊息應答預設是開啟的。上面的程式碼中我們通過顯示的設定autoAsk=true關閉了這種機制。
boolean ack = false ; //開啟應答機制
channel.basicConsume(QUEUE_NAME, ack, consumer);
//另外需要在每次處理完成一個訊息後,手動傳送一次應答。
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
public class Work
{
//佇列名稱
private final static String QUEUE_NAME = "workqueue";
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException
{
//區分不同工作程式的輸出
int hashCode = Work.class.hashCode();
//建立連線和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//宣告佇列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(hashCode
+ " [*] Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定消費佇列
boolean ack = false ; //開啟應答機制
channel.basicConsume(QUEUE_NAME, ack, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(hashCode + " [x] Received '" + message + "'");
doWork(message);
System.out.println(hashCode + " [x] Done");
//傳送應答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
4.訊息持久化(Message durability)
我們已經學習了即使消費者被殺死,訊息也不會被丟失。但是如果此時RabbitMQ服務被停止,我們的訊息仍然會丟失
當RabbitMQ退出或者異常退出,將會丟失所有的佇列和資訊,除非你告訴它不要丟失。
我們需要做兩件事來確保資訊不會被丟失:我們需要給所有的佇列
和訊息
設定持久化的標誌。
- 第一, 我們需要確認RabbitMQ永遠不會丟失我們的佇列。為了這樣,我們需要宣告它為持久化的。
boolean durable = true;
channel.queueDeclare("task_queue", durable, false, false, null);
注:RabbitMQ不允許使用不同的引數重新定義一個佇列,所以已經存在的佇列,我們無法修改其屬性。
- 第二, 我們需要標識我們的資訊為持久化的。通過設定MessageProperties(implements BasicProperties)值為PERSISTENT_TEXT_PLAIN。
channel.basicPublish("", "task_queue",MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
現在你可以執行一個傳送訊息的程式,然後關閉服務,再重新啟動服務,執行消費者程式做下實驗。
5.公平轉發(Fair dispatch)
對於兩個消費者,有一系列的任務,奇數任務特別耗時,而偶數任務卻很輕鬆,這樣造成一個消費者一直繁忙,另一個消費者卻很快執行完任務後等待。
造成這樣的原因是因為RabbitMQ僅僅是當訊息到達佇列進行轉發訊息。並不在乎有多少任務消費者並未傳遞一個應答給RabbitMQ。僅僅盲目轉發所有的奇數給一個消費者,偶數給另一個消費者。
int prefetchCount = 1;
channel.basicQos(prefetchCount);
public class NewTask
{
// 佇列名稱
private final static String QUEUE_NAME = "workqueue_persistence";
public static void main(String[] args) throws IOException
{
// 建立連線和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 宣告佇列
boolean durable = true;// 1、設定佇列持久化
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
// 傳送10條訊息,依次在訊息後面附加1-10個點
for (int i = 5; i > 0; i--)
{
String dots = "";
for (int j = 0; j <= i; j++)
{
dots += ".";
}
String message = "helloworld" + dots + dots.length();
// MessageProperties 2、設定訊息持久化
channel.basicPublish("", QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
}
// 關閉頻道和資源
channel.close();
connection.close();
}
}
public class Work
{
// 佇列名稱
private final static String QUEUE_NAME = "workqueue_persistence";
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException
{
// 區分不同工作程式的輸出
int hashCode = Work.class.hashCode();
// 建立連線和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 宣告佇列
boolean durable = true;
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
System.out.println(hashCode
+ " [*] Waiting for messages. To exit press CTRL+C");
//設定最大服務轉發訊息數量
int prefetchCount = 1;
channel.basicQos(prefetchCount);
QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定消費佇列
boolean ack = false; // 開啟應答機制
channel.basicConsume(QUEUE_NAME, ack, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(hashCode + " [x] Received '" + message + "'");
doWork(message);
System.out.println(hashCode + " [x] Done");
//channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
/**
* 每個點耗時1s
*
* @param task
* @throws InterruptedException
*/
private static void doWork(String task) throws InterruptedException
{
for (char ch : task.toCharArray())
{
if (ch == '.')
Thread.sleep(1000);
}
}
}
三:釋出/訂閱
工作佇列中的一個任務只會發給一個工作者
就是把一個訊息發給多個消費者,這種模式稱之為釋出/訂閱(類似觀察者模式)。
為了驗證這種模式,我們準備構建一個簡單的日誌系統。這個系統包含兩類程式,
一類程式發動日誌,另一類程式接收和處理日誌。
我們實現,一個接收者將接收到的資料寫到硬碟上,與此同時,另一個接收者把接收到的訊息展現在螢幕上。
1:轉發器(Exchanges)
RabbitMQ訊息模型的核心理念是生產者永遠不會直接傳送任何訊息給佇列,一般的情況生產者甚至不知道訊息應該傳送到哪些佇列。
相反的,生產者只能傳送訊息給轉發器(Exchange)。轉發器是非常簡單的,一邊接收從生產者發來的訊息,另一邊把訊息推送到佇列中。轉發器必須清楚的知道訊息如何處理它收到的每一條訊息。是否應該追加到一個指定的佇列?是否應該追加到多個佇列?或者是否應該丟棄?這些規則通過轉發器的型別進行定義。
可用的轉發器型別:
- Direct
- Topic
- Headers
- Fanout
宣告轉發器型別的程式碼:
channel.exchangeDeclare("logs","fanout");
fanout型別轉發器特別簡單,把所有它介紹到的訊息,廣播到所有它所知道的佇列。不過這正是我們前述的日誌系統所需要的
2、匿名轉發器(nameless exchange)
前面說到生產者只能傳送訊息給轉發器(Exchange),但是我們前兩篇部落格中的例子並沒有使用到轉發器,我們仍然可以傳送和接收訊息。
這是因為我們使用了一個預設的轉發器,它的識別符號為””。之前傳送訊息的程式碼:
channel.basicPublish("", QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
第一個引數為轉發器的名稱,我們設定為”” : 如果存在routingKey(第二個引數),訊息由routingKey決定傳送到哪個佇列。
現在我們可以指定訊息傳送到的轉發器:
channel.basicPublish( "logs","", null, message.getBytes());
3、臨時佇列(Temporary queues)
前面的部落格中我們都為佇列指定了一個特定的名稱。能夠為佇列命名對我們來說是很關鍵的,我們需要指定消費者為某個佇列。當我們希望在生產者和消費者間共享佇列時,為佇列命名是很重要的。
不過,對於我們的日誌系統我們並不關心佇列的名稱。我們想要接收到所有的訊息,而且我們也只對當前正在傳遞的資料的感興趣。為了滿足我們的需求,需要做兩件事:
第一, 無論什麼時間連線到Rabbit我們都需要一個新的空的佇列。為了實現,我們可以使用隨機數建立佇列,或者更好的,讓伺服器給我們提供一個隨機的名稱。
第二, 一旦消費者與Rabbit斷開,消費者所接收的那個佇列應該被自動刪除。
Java中我們可以使用queueDeclare()
方法,不傳遞任何引數,來建立一個非持久的、唯一的、自動刪除的佇列且佇列名稱由伺服器隨機產生。
String queueName = channel.queueDeclare().getQueue();
一般情況這個名稱與amq.gen-JzTY20BRgKO-HjmUJj0wLg 類似
4、繫結(Bindings)
我們已經建立了一個fanout轉發器和佇列,我們現在需要通過binding告訴轉發器把訊息傳送給我們的佇列。
channel.queueBind(queueName, “logs”, ””)
引數1:佇列名稱 ;引數2:轉發器名稱
5、完整的例子
1.建立傳送器
public class EmitLog
{
private final static String EXCHANGE_NAME = "ex_log";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立連線工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立頻道
Channel channel = connection.createChannel();
// 宣告轉發器和型別
channel.exchangeDeclare(EXCHANGE_NAME, "fanout" );
//建立傳送的資料
String message = new Date().toLocaleString()+" : log something";
// 往轉發器上傳送訊息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
2.建立接收器,資料寫進檔案裡
public class ReceiveLogsToSave
{
private final static String EXCHANGE_NAME = "ex_log";
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException, TimeoutException {
// 建立連線工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立頻道
Channel channel = connection.createChannel();
// 宣告轉發器和型別
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 建立一個非持久的、唯一的且自動刪除的佇列,臨時佇列
String queueName = channel.queueDeclare().getQueue();
// 為轉發器指定佇列,設定binding,繫結
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定接收者,第二個引數為自動應答,無需手動應答
channel.basicConsume(queueName, true, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
print2File(message);
}
}
private static void print2File(String msg)
{
try
{
String dir = ReceiveLogsToSave.class.getClassLoader().getResource("").getPath();
String logFileName = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
File file = new File(dir, logFileName+".txt");
FileOutputStream fos = new FileOutputStream(file, true);
fos.write((msg + "\r\n").getBytes());
fos.flush();
fos.close();
} catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
3.建立接收器,列印出資訊
public class ReceiveLogsToConsole
{
private final static String EXCHANGE_NAME = "ex_log";
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException, TimeoutException {
// 建立連線工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立頻道
Channel channel = connection.createChannel();
// 宣告轉發器和型別
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 建立一個非持久的、唯一的且自動刪除的佇列
String queueName = channel.queueDeclare().getQueue();
// 為轉發器指定佇列,設定binding
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定接收者,第二個引數為自動應答,無需手動應答
channel.basicConsume(queueName, true, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
}
四:路由(Routing)
需求:本篇部落格我們準備給日誌系統新增新的特性,讓日誌接收者能夠訂閱部分訊息。例如,我們可以僅僅將致命的錯誤寫入日誌檔案,然而仍然在控制皮膚上列印出所有的其他型別的日誌訊息。
1、繫結(Bindings)
在上一篇部落格中我們已經使用過繫結
channel.queueBind(queueName, EXCHANGE_NAME, "");
繫結表示轉發器與佇列之間的關係。
我們也可以簡單的認為:佇列對該轉發器上的訊息感興趣。
繫結可以附帶一個額外的引數routingKey
。為了與避免basicPublish
方法(釋出訊息的方法)的引數混淆,我們準備把它稱作繫結鍵(binding key
)。下面展示如何使用繫結鍵(binding key)來建立一個繫結:
channel.queueBind(queueName, EXCHANGE_NAME, "black");
繫結鍵的意義依賴於轉發器的型別。對於fanout型別,忽略此引數。
2、直接轉發(Direct exchange)
上一篇的日誌系統廣播所有的訊息給所有的消費者。
現在想:可能希望把致命型別的錯誤寫入硬碟,而不把硬碟空間浪費在警告或者訊息型別的日誌上。
之前我們使用fanout
型別的轉發器,但是並沒有給我們帶來更多的靈活性:僅僅可以愚蠢的轉發。
我們將會使用direct
型別的轉發器進行替代。direct
型別的轉發器背後的路由轉發演算法很簡單:
訊息會被推送至繫結鍵(binding key
)和訊息釋出附帶的選擇鍵(routing key
)完全匹配的佇列。
圖解:
我們可以看到direct型別的轉發器與兩個佇列繫結。
第一個佇列與繫結鍵orange繫結
第二個佇列與轉發器間有兩個繫結,一個與繫結鍵black繫結,另一個與green繫結鍵繫結。
這樣的話,當一個訊息附帶一個選擇鍵(routing key) orange釋出至轉發器將會被導向到佇列Q1。訊息附帶一個選擇鍵(routing key)black或者green將會被導向到Q2.所有的其他的訊息將會被丟棄。
3、多重繫結(multiple bindings)
使用一個繫結鍵(binding key)繫結多個佇列是完全合法的。如上圖,一個附帶選擇鍵(routing key)的訊息將會被轉發到Q1和Q2。
4、傳送日誌(Emittinglogs)
我們將訊息傳送到direct
型別的轉發器而不是fanout
型別。我們將把日誌的嚴重性作為選擇鍵(routing key
)。這樣的話,接收程式可以根據嚴重性來選擇接收。我們首先關注傳送日誌的程式碼:
像以前一樣,我們需要先建立一個轉發器:
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
然後我們準備傳送一條訊息:
channel.basicPublish(EXCHANGE_NAME,severity, null, message.getBytes());
為了簡化程式碼,我們假定‘severity’是‘info’,‘warning’,‘error’中的一個。
5、訂閱
接收訊息的程式碼和前面的部落格的中類似,只有一點不同:我們給我們所感興趣的嚴重性型別的日誌建立一個繫結。
StringqueueName = channel.queueDeclare().getQueue();
for(Stringseverity : argv){
channel.queueBind(queueName, EXCHANGE_NAME, severity);
}
6、完整的例項
1.建立傳送者
public class EmitLogDirect
{
private static final String EXCHANGE_NAME = "ex_logs_direct";
private static final String[] SEVERITIES = { "info", "warning", "error" };
public static void main(String[] argv) throws java.io.IOException, TimeoutException {
// 建立連線工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立頻道
Channel channel = connection.createChannel();
// 宣告轉發器的型別
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//傳送6條訊息
for (int i = 0; i < 6; i++)
{
String severity = getSeverity();
String message = severity + "_log :" + UUID.randomUUID().toString();
// 釋出訊息至轉發器,指定routingkey
channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
}
channel.close();
connection.close();
}
/**
* 隨機產生一種日誌型別
*
* @return
*/
private static String getSeverity()
{
Random random = new Random();
int ranVal = random.nextInt(3);
return SEVERITIES[ranVal];
}
}
2.建立接受者
public class ReceiveLogsDirect
{
private static final String EXCHANGE_NAME = "ex_logs_direct";
private static final String[] SEVERITIES = { "info", "warning", "error" };
public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException, TimeoutException {
// 建立連線工廠
ConnectionFactory factory = new ConnectionFactory();
//設定ip
factory.setHost("localhost");
//建立連線
Connection connection = factory.newConnection();
//建立頻道
Channel channel = connection.createChannel();
// 宣告direct型別轉發器
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 建立一個非持久的、唯一的且自動刪除的佇列,臨時佇列
String queueName = channel.queueDeclare().getQueue();
String severity = getSeverity();
// 指定binding_key
channel.queueBind(queueName, EXCHANGE_NAME, severity);
System.out.println(" [*] Waiting for "+severity+" logs. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
/**
* 隨機產生一種日誌型別
*
* @return
*/
private static String getSeverity()
{
Random random = new Random();
int ranVal = random.nextInt(3);
return SEVERITIES[ranVal];
}
}
3.總結:
傳送訊息時可以設定routing_key,接收佇列與轉發器間可以設定binding_key,接收者接收與binding_key與routing_key相同的訊息。
五:主題
1、 主題轉發(Topic Exchange)
發往主題型別的轉發器的訊息不能隨意的設定選擇鍵(routing_key),必須是由點隔開的一系列的識別符號組成。識別符號可以是任何東西,但是一般都與訊息的某些特性相關。一些合法的選擇鍵的例子:"stock.usd.nyse", "nyse.vmw","quick.orange.rabbit".你可以定義任何數量的識別符號,上限為255個位元組。
2.
3.完整例子
1.傳送
public class EmitLogTopic
{
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception
{
// 建立連線和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//指定topic的轉發器
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String[] routing_keys = new String[] { "kernal.info", "cron.warning","auth.info", "kernel.critical" };
for (String routing_key : routing_keys)
{
String msg = UUID.randomUUID().toString();
channel.basicPublish(EXCHANGE_NAME, routing_key, null, msg.getBytes());
System.out.println(" [x] Sent routingKey = "+routing_key+" ,msg = " + msg + ".");
}
channel.close();
connection.close();
}
}
2.接收1
public class ReceiveLogsTopicForCritical
{
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception
{
// 建立連線和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 宣告topic轉發器
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 隨機生成一個佇列
String queueName = channel.queueDeclare().getQueue();
// 接收所有與kernel相關的訊息
channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
String routingKey = delivery.getEnvelope().getRoutingKey();
System.out.println(" [x] Received routingKey = " + routingKey + ",msg = " + message + ".");
}
}
}
2.接收2
public class ReceiveLogsTopicForKernel
{
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception
{
// 建立連線和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 宣告topic轉發器
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 隨機生成一個佇列
String queueName = channel.queueDeclare().getQueue();
//接收所有與kernel相關的訊息
channel.queueBind(queueName, EXCHANGE_NAME, "kernel.*");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
String routingKey = delivery.getEnvelope().getRoutingKey();
System.out.println(" [x] Received routingKey = " + routingKey + ",msg = " + message + ".");
}
}
}
六:參考
http://blog.csdn.net/lmj623565791/article/details/37706355
http://blog.csdn.net/lmj623565791/article/details/37669573
http://blog.csdn.net/lmj623565791/article/details/37657225
相關文章
- RabbitMQ(一):RabbitMQ快速入門MQ
- RabbitMQ快速入門MQ
- RabbitMQ入門教程MQ
- RabbitMQ入門指南MQ
- RabbitMQ 入門 - 路由MQ路由
- RabbitMQ入門案例MQ
- RabbitMQ各功能入門MQ
- RabbitMQ基礎入門MQ
- RabbitMQ 入門 - 話題MQ
- RabbitMQ 入門 - Hello WorldMQ
- rabbitmq(一)-基礎入門MQ
- RabbitMQ 入門 - 工作佇列MQ佇列
- RabbitMQ 入門案例 - fanout 模式MQ模式
- RabbitMQ入門到進階(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)MQSpring Boot
- RabbitMQ入門 -- 阿里雲伺服器安裝RabbitMQMQ阿里伺服器
- SpringBoot整合RabbitMQ(一)快速入門Spring BootMQ
- RabbitMQ 入門之基礎概念MQ
- java基礎(六):RabbitMQ 入門JavaMQ
- RabbitMQ 從入門到精通 (一)MQ
- RabbitMQ 入門 - 遠端呼叫 (RPC)MQRPC
- RabbitMQ 入門 - 釋出 / 訂閱MQ
- RabbitMQ .NET訊息佇列使用入門(五)【RabbitMQ例子】MQ佇列
- 訊息中介軟體RabbitMQ_RabbitMQ快速入門3MQ
- RabbitMQ .NET訊息佇列使用入門(四)【RabbitMQ用法大全】MQ佇列
- 1.RabbitMQ入門-概念、安裝、配置MQ
- RabbitMQ入門,我是動了心的MQ
- RabbitMQ 入門案例 - Work 模式 - 輪詢模式MQ模式
- RabbitMQ 入門(三)SpringAMQP訊息轉換器MQSpringGAM
- RabbitMQ由淺入深入門全總結(一)MQ
- RabbitMQ由淺入深入門全總結(二)MQ
- RabbitMQ 入門(二)基本結構和訊息模型MQ模型
- RabbitMQ 入門(一)同步通訊和非同步通訊MQ非同步
- 什麼是Rabbitmq訊息佇列? (安裝Rabbitmq,透過Rabbitmq實現RPC全面瞭解,從入門到精通)MQ佇列RPC
- rabbitmq入坑之路MQ
- 入門RabbitMQ訊息佇列,看這篇文章就夠了MQ佇列
- spring-boot快速入門學習筆記-整合JPA mybatis rabbitmq mongodb redisSpringboot筆記MyBatisMQMongoDBRedis
- 入門入門入門 MySQL命名行MySql
- 萬字長文:從 C# 入門學會 RabbitMQ 訊息佇列程式設計C#MQ佇列程式設計