一、工作原理
組成部分說明如下: Broker:消息隊列服務進程,此進程包括兩個部分:Exchange和Queue。 Exchange:消息隊列交換機,按一定的規則將消息路由轉發到某個隊列,對消息進行過慮。
Queue:消息隊列,存儲消息的隊列,消息到達隊列並轉發給指定的消費方。 Producer:消息生產者,即生產方客戶端,生產方客戶端將消息發送到MQ。 Consumer:消息消費者,即消費方客戶端,接收MQ轉發的消息。
消息發佈接收流程:
-----發送消息-----
1、生產者和Broker建立TCP連接。
2、生產者和Broker建立通道。
3、生產者通過通道消息發送給Broker,由Exchange將消息進行轉發。
4、Exchange將消息轉發到指定的Queue(隊列)
----接收消息-----
1、消費者和Broker建立TCP連接
2、消費者和Broker建立通道
3、消費者監聽指定的Queue(隊列)
4、當有消息到達Queue時Broker默認將消息推送給消費者。
5、消費者接收到消息。
二、工作模式
work queue
1.兩個或多個消費者消費同一個隊列中的消息
2.生產者多次發送消息,消費端採用輪詢的方式接收
3.消費者在處理完某條消息之後,纔會收到下一條消息
代碼步驟:
1.創建通道
2.聲明隊列
無需使用交換機
Publish/Subscribe
1.每個消費者監聽自己的隊列
2.生產者發送消息,由交換機將消息轉發到綁定此交換機的每個隊列,每個綁定到交換機的隊列都能接收消息
代碼步驟
1.創建通道
2.聲明交換機
3.聲明隊列(交換機類型爲fanout)
4.隊列綁定到交換機(綁定的時候routingkey設爲"“即可)
5.發送消息(發送消息時,routingkey設爲”")
Routing
1.每個消費者監聽自己的隊列,並且設置routingkey
2.生產者將消息發給交換機,由交換機根據routingkey來轉發消息到指定的隊列
代碼步驟:
1.創建通道
2.聲明交換機
3.聲明隊列(交換機類型爲direct)
4.隊列綁定到交換機上(綁定的時候設置routingkey的值)
5.發送消息(發送消息的時候,指定要發送的routingkey)
Topic
1.每個消費者監聽自己的隊列,並且設置帶有通配符的routingkey
2.生產者將消息發送給broker(exchange+queue),由交換機根絕routingkey來轉發到指定的隊列
案例:
根據用戶的通知設置去通知用戶,設置接收Email的用戶只接收Email,設置接收sms的用戶只接收sms,設置兩種 通知類型都接收的則兩種通知都有效。
代碼步驟:
1.創建通道
2.聲明交換機(指定topic類型)
3.聲明隊列
4.生產端無需交換機綁定隊列,發送消息的時候指定routingkey
channel.basicPublish(EXCHANGR_TOPIC_INFORM, "sms.email", null, message.getBytes());
5.消費端隊列綁定到交換機上,使用通配符
注意問題
1.在隊列綁定交換機的時候,可使用
channel.queueBind(String queue,String exchange,String routingKey);
也可以使用
channel.exchangeBind(String exchange,String queue,String routingKey);
參數順序要注意!!
2.工作模式還有Header、RPC兩種模式
總結
Work queue 相當於多個消費者共同監聽一個個隊列,消息輪詢消費
Publish/Describe多個消費者有監聽各自的隊列,消息發送到各個隊列,一個消息被多個消費者消費
Routing多個消費者監聽各自的隊列,交換機根據路由轉發到指定的某個隊列
Topic多個消費者監聽各自的隊列,交換機根據路由轉發到指定的某些隊列
/**隊列綁定交換機指定通配符: 統配符規則: 中間以“.”分隔。 符號#可以匹配多個詞,
符號*可以匹配一個詞語
*/
channel.exchangeBind(QUEUE_INFORM_EMAIL, EXCHANGR_TOPIC_INFORM, "inform.#.email.#");
//僞代碼
--生產者
/*1.定義隊列名、交換機名*/
/*2.通過連接工廠建立Connection,創建Channel(即一個會話)*/
/*3.通過通道聲明交換機*/
/**
* 參數明細
* 1.交換機名稱
* 2.交換機類型 fanout、topic、direct、headers
*/
channel.exchangeDeclare(String exchange, BuiltinExchangeType type);
/*4.通過通道聲明隊列*/
/**
* 參數明細
* 1.隊列名稱
* 2.是否持久化
* 3.是否獨佔此隊列
* 4.隊列是否自動刪除
* 5.參數
*/
channel.queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments);
/*5.交換機和隊列進行綁定*/
/**
* 參數明細
* 1.隊列名稱
* 2.交換機名稱
* 3.routingKey
*/
channel.queueBind(String queue, String exchange, String routingKey);
/*6.發送消息*/
/**
* 參數明細
* 1.交換機名稱
* 2.routingKey根據key名稱將消息轉發到具體的隊列,表示消息將發到此隊列
* 3.消息屬性
* 4.消息體
*/
channel.basicPublish(String exchange, String routingKey,
BasicProperties props,byte[] body);
---消費者
/*1.定義隊列名、交換機名*/
/*2.通過連接工廠建立Connection,創建Channel(即一個會話)*/
/*3.通過通道聲明交換機*/
/**
* 參數明細
* 1.交換機名稱
* 2.交換機類型 fanout、topic、direct、headers
*/
channel.exchangeDeclare(String exchange, BuiltinExchangeType type);
/*4.通過通道聲明隊列*/
/**
* 參數明細
* 1.隊列名稱
* 2.是否持久化
* 3.是否獨佔此隊列
* 4.隊列是否自動刪除
* 5.參數
*/
channel.queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments);
/*5.交換機和隊列進行綁定*/
/**
* 參數明細
* 1.隊列名稱
* 2.交換機名稱
* 3.routingKey
*/
channel.queueBind(String queue, String exchange, String routingKey);
/*6.定義消費消息的方法*/
//定義消費方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
long deliveryTag = envelope.getDeliveryTag();
String exchange = envelope.getExchange();
//接收到的消息內容
String message = new String(body,"utf-8");
System.out.println(message);
}
};
/*7.監聽隊列,接收消息*/
/** 參數明細
* 1.監聽的隊列名稱
* 2.是否自動回覆,設置爲true爲表示消息接收到自動向mq回覆接收到了,
* mq接收到回覆會刪除消息,設置 爲false則需要手動回覆
* 3.消費者接收到消息後調用的方法
*/
channel.basicConsume(String queue, boolean autoAck,Consumer callback)
//消息生產者
public class Producer01 {
//隊列名稱
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");//rabbitmq默認虛擬機名稱爲“/”,虛擬機相當於一個獨立的mq服務器
//創建與rabbitmq服務的tcp連接
connection = factory.newConnection();
//創建Exchange通道,每個連接可以創建多個通道,每個通道代表一個會話
channel = connection.createChannel();
/**
* 聲明隊列,如果rabbitmq中沒有此隊列將自動創建
* param1:隊列名稱
* param2:是否持久化
* param3:隊列是否獨佔此連接
* param4:隊列不再使用時是否自動刪除此隊列
* param5:隊列參數
*/
channel.queueDeclare(QUEUE, true, false, false, null);
String message = "helloworld小明" + System.currentTimeMillis();
/**
* 消息發佈
* param1:Exchange名稱,如果沒有指定交換機,消息將會發給默認的交換機,每個隊列也會綁定那個默認的交換機,
* 但是不能顯示綁定或者解除綁定
* param2:routingKey,消息的路由key,用於Exchange將消息轉發到指定的隊列
* 使用默認交換機時,routingKey使用
* param3:消息包含的屬性
* param4:消息體
*/
channel.basicPublish("", QUEUE, null, message.getBytes());
System.out.println("send Message is '" + message +"'");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}
}
}
//消費者
public class Consumer01 {
//隊列名稱
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE, true, false, false, null);
//自定義消費方法 繼承DefaultConsumer
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 消費者接受消息調用此方法
* @param consumerTag 消費者的標籤,在channel.basicConsume()去指定
* @param envelope 消息包的內容,可從中獲取消息id,消息routingKey,交換機,消息和
* 重傳表示(收到消息失敗後是否需要重新發送)
* @param properties
* @param body 消息體
*/
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
//交換機
String exchange = envelope.getExchange();
//路由key
String routingKey = envelope.getRoutingKey();
//消息id
long deliveryTag = envelope.getDeliveryTag();
//消息內容
String msg = new String(body,"utf-8");
System.out.println("receive message.."+ msg);
}
};
/**
* 監聽隊列String queue,boolean autoAsk,Consumer callback
* 參數明細
* 1.隊列名稱
* 2.是否自動回覆,設置爲true表示消息接收到自動向mq回覆接收到了,mq接收到回覆消息會刪除消息,
* 設置爲false則需要手動回覆
* 3.消費者接收消息後調用此方法
*/
channel.basicConsume(QUEUE, true,consumer);
}
}
先啓動消息的生產者,發送一條消息到mq中,然後再啓動消費者,監聽並拿到這條消息,此時消費者仍會持續監聽,再使用生產者發送一條消息,消費者會立馬接收到