在RabbitMQ入門(一)裏我們講到exchange有三種最主要的類型:direct、fanout和topic。
這裏我們先來看看最簡單的direct交換器的使用。
下面是測試代碼:
package com.jaeger.exchange.direct; import java.io.IOException; import java.util.concurrent.TimeoutException; import org.junit.Test; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class Producer { private static final String MY_EXCHANGE_NAME = "MyExchange"; private static final String MY_ROUTING_KEY = "MyRoutingKey"; private static final String MY_QUEUE_NAME = "MyQueue"; private static final String DIRECT = "direct"; private static final String HOST = "172.19.64.21"; private static final String USER = "jaeger"; private static final String PASSWORD = "root"; private static final int PORT = 5672; @Test public void createExchangeAndQueue() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(HOST); connectionFactory.setUsername(USER); connectionFactory.setPassword(PASSWORD); connectionFactory.setPort(PORT); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); // 創建一個direct類型的exchange channel.exchangeDeclare(MY_EXCHANGE_NAME, DIRECT); // 創建一個queue channel.queueDeclare(MY_QUEUE_NAME, false, false, false, null); // 創建一個routing key,把exchange和queue綁定到一起 channel.queueBind(MY_QUEUE_NAME, MY_EXCHANGE_NAME, MY_ROUTING_KEY); channel.close(); connection.close(); } @Test public void produce() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(HOST); connectionFactory.setUsername(USER); connectionFactory.setPassword(PASSWORD); connectionFactory.setPort(PORT); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); String message = "Hello 世界!"; /* 向RabbitMQ發送消息。我們這裏指定了exchange和routing key的名稱,RabbitMQ會去找有沒有叫這個名稱的exchange, 如果找到了,就會再查看在該exchange上是否綁定一個跟我們指定名稱一樣的routing key,找到了就把消息放到routing key 對應的queue裏面 */ channel.basicPublish(MY_EXCHANGE_NAME, MY_ROUTING_KEY, null, message.getBytes("utf-8")); System.out.println("Sent '" + message + "'"); channel.close(); connection.close(); } @Test public void consume() throws IOException, TimeoutException, InterruptedException{ ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(HOST); connectionFactory.setUsername(USER); connectionFactory.setPassword(PASSWORD); connectionFactory.setPort(PORT); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); System.out.println("Received '" + message + "'"); } }; channel.basicConsume(MY_QUEUE_NAME, true, consumer); Thread.sleep(1000); } }
在講direct交換器之前先簡單介紹下RabbitMQ的connection和channel。
客戶端到RabbitMQ的connection是通過TCP建立的,爲什麼不像數據庫一樣直接使用connection來操作呢?因爲TCP的建立和銷燬都非常消耗資源,對於一個消息服務器來說,它的任務就是處理海量的消息,如果每個消息的產生和消費都建立TCP連接的話顯然不合適。
爲了解決這個問題,引入了channel,channel相當於一個TCP連接內的虛擬連接。消息的產生和消費都是通過channel完成的,每個channel都會被指定一個唯一的ID用於區分不同的channel,以避免互相干擾。就好像光纖網線一樣,網線中的每條光纖束都可以用來傳遞消息,而不會出現互相干擾
下面我們就來看看direct交換器的效果,先執行createExchangeAndQueue方法:
從後臺可以看到,我們指定的exchange、routing key和queue都正確創建了,並且名稱都是我們指定的名稱。
接下來我們在運行produce方法向RabbitMQ發送消息:
可以看到MyQueue隊列裏面已經添加了一條消息。最後運行consume方法去消費這條消息:
對於消費端來說,只用知道queue的名稱就可以了。而對於發送端,則需要知道exchange和routing key的名稱,相對而言queue的名稱就不那麼重要了。
最後我們來分析下RabbitMQ入門(一)裏面的send方法,該方法裏面貌似並沒有指定exchange和routing key的名稱,也沒有進行queue和exchange的綁定操作,爲什麼也能發送成功呢?下面是代碼片段:
public void send() { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(HOST); connectionFactory.setUsername(USER); connectionFactory.setPassword(PASSWORD); connectionFactory.setPort(PORT); try { Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); //A String message = "Hello 世界!"; channel.basicPublish("", QUEUE_NAME, null, message.getBytes("utf-8")); //B System.out.println("Sent '" + message + "'"); channel.close(); connection.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
在A處我們創建了一個名稱爲QUEUE_NAME的queue,沒有讓其和任何的routing key對應,也沒有聲明任何exchange。
在B處我們發送消息的時候,指定的exchange是一個空字符串,routing key居然是queue的名稱,不是說exchange是根據routing key來決定放入哪個queue的麼,這裏怎麼用queue的名稱?
對於上面的問題,下面我們來說明下:
RabbitMQ裏面有一個默認的exchange,他的名稱就是一個空字符串。我們創建的每一個queue都會跟這個exchange進行綁定(所以我們在通過自定義的routing key綁定exchange和queue時,實際上這個queue還偷偷綁定到了這個默認的exchange),而中間的routing key的名稱就是queue的名稱。所以上面B處的方法中的exchange就是這個默認的exchange,而routing key看起來好像就是queue的名稱,實際上是因爲routing key的名稱跟queue名稱一樣而已。我們把上面produce方法修改下:
@Test public void produce() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(HOST); connectionFactory.setUsername(USER); connectionFactory.setPassword(PASSWORD); connectionFactory.setPort(PORT); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); String message = "Hello 世界!"; // 這裏我們把消息發送給默認的exchange,routing key名稱就是queue的名稱 channel.basicPublish("", MY_QUEUE_NAME, null, message.getBytes("utf-8")); System.out.println("Sent '" + message + "'"); channel.close(); connection.close(); }
消息成功通過默認的exchange和routing key放到了MyQueue隊列裏面。