介紹
首先來段官方文檔
In the previous tutorial we created a work queue. The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we’ll do something completely different – we’ll deliver a message to multiple consumers. This pattern is known as “publish/subscribe”.
The core idea in the messaging model in RabbitMQ is that the producer never sends any messages directly to a queue. Actually, quite often the producer doesn’t even know if a message will be delivered to any queue at all.
Instead, the producer can only send messages to an exchange. An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type.
簡言之,之前的模式都是一個生產者,一個隊列,一個或者多個消費者。然而發佈訂閱模式引入了交換機的概念。有了交換機,生產者不會將消息直接發到隊列中,而是發送到交換機,由交換機來決定將消息傳送到具體哪一個或者多個隊列中。其模型如下:
生產者實現
其基本實現邏輯和第一節講的無異,關鍵是多了交換機和交換機與隊列之間的聯繫。因此在代碼上,僅僅添加了聲明交換機,和綁定隊列。注意,這裏聲明瞭兩個隊列,綁定時也是綁定了兩個隊列。
聲明交換機代碼如下:
//聲明一個交換機
//參數:String exchange, String type
/**
* 參數明細:
* exchange:交換機名稱
* type:交換機類型
* fanout:對應額rabbitmq工作模式是publish/subscribe
* direct:對應額rabbitmq工作模式是routing路由模式
* topic:對應額rabbitmq工作模式是topics通配符
* headers:對應額rabbitmq工作模式是headers轉發器
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFO, BuiltinExchangeType.FANOUT);
綁定隊列代碼如下:
//進行交換機和隊列綁定
//參數:String queue, String exchange, String routingKey
/**
* 參數明細:
* queue:隊列名稱
* exchange:交換機名稱
* routingKey:路由key,作用是交換機根據路由key將消息發佈到指定隊列中。在發佈訂閱模式設爲空串
*/
channel.queueBind(QUEUENAME01,EXCHANGE_FANOUT_INFO,"");
完整代碼如下:
public class Producer01 {
private static final String QUEUENAME01 = "queue01";
private static final String QUEUENAME02 = "queue02";
private static final String EXCHANGE_FANOUT_INFO = "exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//通過連接工廠創建新的連接和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 = null;
connection = connectionFactory.newConnection();
//創建會話通道,生產者和mq服務所有的通信都在channel裏完成。
Channel channel = connection.createChannel();
//聲明隊列:如果隊列在mq中沒有,則創建
//參數String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
/**
* 參數明細:
* queue:隊列名稱
* durable:持久化,如果持久化,重啓mq服務隊列還在
* exclusive:是否獨佔連接,隊列只允許在該連接中訪問,如果連接關閉則隊列刪除,設置爲true,可用於臨時隊列。
* autoDelete:自動刪除,設置爲true,連接不使用則刪除隊列
* arguments:指定一些擴展參數。例如存活時間等等
*/
channel.queueDeclare(QUEUENAME01,true,false,false,null);
channel.queueDeclare(QUEUENAME02,true,false,false,null);
//聲明一個交換機
//參數:String exchange, String type
/**
* 參數明細:
* exchange:交換機名稱
* type:交換機類型
* fanout:對應額rabbitmq工作模式是publish/subscribe
* direct:對應額rabbitmq工作模式是routing路由模式
* topic:對應額rabbitmq工作模式是topics通配符
* headers:對應額rabbitmq工作模式是headers轉發器
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFO, BuiltinExchangeType.FANOUT);
//進行交換機和隊列綁定
//參數:String queue, String exchange, String routingKey
/**
* 參數明細:
* queue:隊列名稱
* exchange:交換機名稱
* routingKey:路由key,作用是交換機根據路由key將消息發佈到指定隊列中。在發佈訂閱模式設爲空串
*/
channel.queueBind(QUEUENAME01,EXCHANGE_FANOUT_INFO,"");
channel.queueBind(QUEUENAME02,EXCHANGE_FANOUT_INFO,"");
//發送消息
//參數:String exchange, String routingKey, BasicProperties props, byte[] body
/**
* 參數明細:
* exchange:交換機,不指定的話爲默認交換機
* routingKey:交換機根據路由key,將消息轉發到指定隊列,如果使用默認交換機,routingKey設置爲隊列的名稱
* props:消息的屬性
* body:消息內容,字節數組形式
*/
channel.basicPublish(EXCHANGE_FANOUT_INFO,"",null,"現在是發佈訂閱模式".getBytes());
System.out.println("send to mq");
//關閉通道
channel.close();
//關閉連接
connection.close();
}
}
消費者實現
消費者實現同樣也要聲明交換機和綁定隊列,這次只需要綁定一次(對應的)隊列即可。
public class Consumer01 {
private static final String QUEUENAME01 = "queue01";
private static final String EXCHANGE_FANOUT_INFO = "exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//通過連接工廠創建新的連接和mq建立連接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設置虛擬機,一個mq的服務可以設置多個虛擬機,每個mq相當於一個獨立的mq。
connectionFactory.setVirtualHost("/");
//建立新連接
Connection connection = null;
connection = connectionFactory.newConnection();
//創建會話通道,生產者和mq服務所有的通信都在channel裏完成。
Channel channel = connection.createChannel();
//聲明隊列:如果隊列在mq中沒有,則創建
channel.queueDeclare(QUEUENAME01,true,false,false,null);
channel.exchangeDeclare(EXCHANGE_FANOUT_INFO, BuiltinExchangeType.FANOUT);
channel.queueBind(QUEUENAME01,EXCHANGE_FANOUT_INFO,"");
//實現消費方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 當接收到消息後,此方法執行
* @param consumerTag 消費者標籤,用來標識消費者,在監聽隊列時也可設置channel.basicConsume
* @param envelope 信封,可用信封獲取一些信息,例如交換機、消息id等
* @param properties 消息屬性,發送消息時設置的消息屬性可在這裏獲取
* @param body 消息內容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//拿到交換機
String exchange = envelope.getExchange();
//消息id,mq在channel中用來表示消息的id,可用於確認消息已接收。
long deliveryTag = envelope.getDeliveryTag();
String message = new String(body,"UTF-8");
System.out.println("交換機:"+exchange+" 消息id"+deliveryTag+" 消息內容"+message);
}
};
//監聽隊列
//參數 String queue, boolean autoAck, Consumer callback
/**
* 參數明細:
* queue:隊列名稱
* autoAck:自動回覆,消費者接受到消息會自動告訴mq服務消息已接收此參數設置爲true
* 會自動回覆mq,如果設置爲false要通過編程回覆,不回覆的話消息一直在隊列裏。
* callback:回調方法,當消費者接收到消息要執行的方法。
*/
channel.basicConsume(QUEUENAME01,true,defaultConsumer);
}
}