總結上文章經驗
1、publish/subscribe與work queues有什麼區別。
區別:
1)work queues不用定義交換機,而publish/subscribe需要定義交換機。
2)publish/subscribe的生產方是面向交換機傳送訊息,workqueues的生產方是面向佇列傳送訊息(底層使用預設交換機)。
3)publish/subscribe需要設定佇列和交換機的繫結,work queues不需要設定,實質上work queues會將佇列繫結到預設的交換機 。
相同點: 所以兩者實現的釋出/訂閱的效果是一樣的,多個消費端監聽同一個佇列不會重複消費訊息。
Routing
工作模式
路由模式:
1、每個消費者監聽自己的佇列,並且設定routingkey。
2、生產者將訊息發給交換機,由交換機根據routingkey來轉發訊息到指定的佇列。
程式碼
1、生產者 宣告exchange_routing_inform交換機。 宣告兩個佇列並且繫結到此交換機,繫結時需要指定routingkey 傳送訊息時需要指定routingkey
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer03_routing {
//佇列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//建立一個與MQ的連線
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");//rabbitmq預設虛擬機器名稱為“/”,虛擬機器相當於一個獨立的mq服務
器
//建立一個連線
connection = factory.newConnection();
//建立與交換機的通道,每個通道代表一個會話
channel = connection.createChannel();
//宣告交換機 String exchange, BuiltinExchangeType type
/**
* 引數明細
* 1、交換機名稱
* 2、交換機型別,fanout、topic、direct、headers
*/
channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
//宣告佇列
// channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean
autoDelete, Map<String, Object> arguments)
/**
* 引數明細:
* 1、佇列名稱
* 2、是否持久化
* 3、是否獨佔此佇列
* 4、佇列不用是否自動刪除
* 5、引數
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//交換機和佇列繫結String queue, String exchange, String routingKey
/**
* 引數明細
* 1、佇列名稱
* 2、交換機名稱
* 3、路由key
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_SMS);
//傳送郵件訊息
for (int i=0;i<10;i++){
String message = "email inform to user"+i;
//向交換機傳送訊息 String exchange, String routingKey, BasicProperties props,
byte[] body
/**
* 引數明細
* 1、交換機名稱,不指令使用預設交換機名稱 Default Exchange
* 2、routingKey(路由key),根據key名稱將訊息轉發到具體的佇列,這裡填寫佇列名稱表示消
息將發到此佇列
* 3、訊息屬性
* 4、訊息內容
*/
channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL, null,
message.getBytes());
System.out.println("Send Message is:'" + message + "'");
}
//傳送簡訊訊息
for (int i=0;i<10;i++){
String message = "sms inform to user"+i;
//向交換機傳送訊息 String exchange, String routingKey, BasicProperties props,
byte[] body
channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS, null,
message.getBytes());
System.out.println("Send Message is:'" + message + "'");
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally{
if(channel!=null){
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
複製程式碼
郵件傳送消費者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer03_routing_email {
//佇列名稱
private static final String QUEUE_INFORM_EMAIL = "inform_queue_email";
private static final String EXCHANGE_ROUTING_INFORM="inform_exchange_routing";
public static void main(String[] args) throws IOException, TimeoutException {
//建立一個與MQ的連線
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");//rabbitmq預設虛擬機器名稱為“/”,虛擬機器相當於一個獨立的mq伺服器
//建立一個連線
Connection connection = factory.newConnection();
//建立與交換機的通道,每個通道代表一個會話
Channel channel = connection.createChannel();
//宣告交換機 String exchange, BuiltinExchangeType type
/**
* 引數明細
* 1、交換機名稱
* 2、交換機型別,fanout、topic、direct、headers
*/
channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
//宣告佇列
// channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean
autoDelete, Map<String, Object> arguments)
/**
* 引數明細:
* 1、佇列名稱
* 2、是否持久化
* 3、是否獨佔此佇列
* 4、佇列不用是否自動刪除
* 5、引數
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
//交換機和佇列繫結String queue, String exchange, String routingKey
/**
* 引數明細
* 1、佇列名稱
* 2、交換機名稱
* 3、路由key
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL);
//定義消費方法
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);
}
};
/**
* 監聽佇列String queue, boolean autoAck,Consumer callback
* 引數明細
* 1、佇列名稱
* 2、是否自動回覆,設定為true為表示訊息接收到自動向mq回覆接收到了,mq接收到回覆會刪除訊息,設定
為false則需要手動回覆
* 3、消費訊息的方法,消費者接收到訊息後呼叫此方法
*/
channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
}
}
複製程式碼
3、簡訊傳送消費者 參考郵件傳送消費者的程式碼流程,編寫簡訊通知的程式碼。
測試
開啟RabbitMQ的管理介面,觀察交換機繫結情況:
Topics
路由模式:1、每個消費者監聽自己的佇列,並且設定帶統配符的routingkey。
2、生產者將訊息發給broker,由交換機根據routingkey來轉發訊息到指定的佇列。
程式碼
1、生產者 宣告交換機,指定topic型別:
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer04_topics {
//佇列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//建立一個與MQ的連線
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");//rabbitmq預設虛擬機器名稱為“/”,虛擬機器相當於一個獨立的mq服務
器
//建立一個連線
connection = factory.newConnection();
//建立與交換機的通道,每個通道代表一個會話
channel = connection.createChannel();
//宣告交換機 String exchange, BuiltinExchangeType type
/**
* 引數明細
* 1、交換機名稱
* 2、交換機型別,fanout、topic、direct、headers
*/
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//宣告佇列
/**
* 引數明細:
* 1、佇列名稱
* 2、是否持久化
* 3、是否獨佔此佇列
* 4、佇列不用是否自動刪除
* 5、引數
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//傳送郵件訊息
for (int i=0;i<10;i++){
String message = "email inform to user"+i;
//向交換機傳送訊息 String exchange, String routingKey, BasicProperties props,
byte[] body
/**
* 引數明細
* 1、交換機名稱,不指令使用預設交換機名稱 Default Exchange
* 2、routingKey(路由key),根據key名稱將訊息轉發到具體的佇列,這裡填寫佇列名稱表示消
息將發到此佇列
* 3、訊息屬性
* 4、訊息內容
*/
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email", null,
message.getBytes());
System.out.println("Send Message is:'" + message + "'");
}
//傳送簡訊訊息
for (int i=0;i<10;i++){
String message = "sms inform to user"+i;
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms", null,
message.getBytes());
System.out.println("Send Message is:'" + message + "'");
}
//傳送簡訊和郵件訊息
for (int i=0;i<10;i++){
String message = "sms and email inform to user"+i;
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms.email", null,
message.getBytes());
System.out.println("Send Message is:'" + message + "'");
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally{
if(channel!=null){
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
複製程式碼
消費端
佇列繫結交換機指定萬用字元: 統配符規則: 中間以“.”分隔。 符號#可以匹配多個詞,符號*可以匹配一個詞語。
//宣告佇列
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//宣告交換機
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//繫結email通知佇列
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,"inform.#.email.#");
//繫結sms通知佇列
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,"inform.#.sms.#");
複製程式碼
測試
Header模式
header模式與routing不同的地方在於,header模式取消routingkey,使用header中的 key/value(鍵值對)匹配 佇列。
案例: 根據使用者的通知設定去通知使用者,設定接收Email的使用者只接收Email,設定接收sms的使用者只接收sms,設定兩種 通知型別都接收的則兩種通知都有效。 程式碼:
1)生產者 佇列與交換機繫結的程式碼與之前不同,如下:
Map<String, Object> headers_email = new Hashtable<String, Object>();
headers_email.put("inform_type", "email");
Map<String, Object> headers_sms = new Hashtable<String, Object>();
headers_sms.put("inform_type", "sms");
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADERS_INFORM,"",headers_email);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_HEADERS_INFORM,"",headers_sms);
複製程式碼
通知:
String message = "email inform to user"+i;
Map<String,Object> headers = new Hashtable<String, Object>();
headers.put("inform_type", "email");//匹配email通知消費者繫結的header
//headers.put("inform_type", "sms");//匹配sms通知消費者繫結的header
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder();
properties.headers(headers);
//Email通知
channel.basicPublish(EXCHANGE_HEADERS_INFORM, "", properties.build(), message.getBytes());
複製程式碼
2)傳送郵件消費者
channel.exchangeDeclare(EXCHANGE_HEADERS_INFORM, BuiltinExchangeType.HEADERS);
Map<String, Object> headers_email = new Hashtable<String, Object>();
headers_email.put("inform_email", "email");
//交換機和佇列繫結
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADERS_INFORM,"",headers_email);
//指定消費佇列
channel.basicConsume(QUEUE_INFORM_EMAIL, true, consumer);
複製程式碼
3)測試
RPC
RPC即客戶端遠端呼叫服務端的方法,使用MQ可以實現RPC的非同步呼叫,基於Direct交換機實現,流程如下:
1、客戶端即是生產者就是消費者,向RPC請求佇列傳送RPC呼叫訊息,同時監聽RPC響應佇列。
2、服務端監聽RPC請求佇列的訊息,收到訊息後執行服務端的方法,得到方法返回的結果
3、服務端將RPC方法 的結果傳送到RPC響應佇列
4、客戶端(RPC呼叫方)監聽RPC響應佇列,接收到RPC呼叫結果。
- GitHub程式碼地址
- 消費者
- github.com/flagest/onl…
- 生產者
- github.com/flagest/onl…