目錄
jar包
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
消息類型
簡單模型Hello Word
簡單隊列 生產消費一一對應,耦合性高.
生產消息快,消費消息慢.如果很多容易堵
功能:一個生產者P發送消息到隊列Q,一個消費者C接收
生產者實現思路:
創建連接工廠ConnectionFactory,設置服務地址127.0.0.1,端口號5672,設置用戶名、密碼、virtual host,從連接工 廠中獲取連接connection,使用連接創建通道channel,使用通道channel創建隊列queue,使用通道channel向隊列中發 送 消息,關閉通道和連接。
創建工廠
package rabbitMQ.com.rabbitMQ.util;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
public class ConnectionUtils {
public static Connection getConnection() throws IOException, TimeoutException {
// 定義連接工廠
ConnectionFactory factory = new ConnectionFactory();
// 設置服務地址
factory.setHost("127.0.0.1");
// 端口
factory.setPort(5672);// amqp協議 端口 類似與mysql的3306
// 設置賬號信息,用戶名、密碼、vhost
factory.setVirtualHost("/user");
factory.setUsername("user");
factory.setPassword("123");
// 通過工程獲取連接
Connection connection = factory.newConnection();
return connection;
}
}
生產者
package rabbitMQ.com.rabbitMQ.simple;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private static final String QUEUE_NAME = "QUEUE_simple";
/*
* P----->|QUEUE |
*/
public static void main(String[] args) throws IOException, TimeoutException {
// 獲取一個連接
Connection connection = ConnectionUtils.getConnection();
//從連接中創建通道
Channel channel = connection.createChannel();
// 創建隊列 (聲明) 因爲我們要往隊列裏面發送消息
boolean durable = false;//是否持久化
boolean exclusive = false;//獨佔的queue
boolean autoDelete = false;//不使用時是否自動刪除
channel.queueDeclare(QUEUE_NAME, durable, exclusive, autoDelete, null);// 如果這個隊列不存在,其實這句話是不需要的
String msg = "Hello Simple QUEUE !";
// 第一個參數是exchangeName(默認情況下代理服務器端是存在一個""名字的exchange的,
// 因此如果不創建exchange的話我們可以直接將該參數設置成"",如果創建了exchange的話
// 我們需要將該參數設置成創建的exchange的名字),第二個參數是路由鍵
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println("---------send ms :" + msg);
channel.close();
connection.close();
}
}
消費者
package rabbitMQ.com.rabbitMQ.simple;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Customer {
private static final String QUEUE_NAME = "QUEUE_simple";
public static void main(String[] args) throws Exception {
/* 獲取一個連接 */
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// 聲明隊列 如果能確定是哪一個隊列 這邊可以刪掉,不去掉 這裏會忽略創建
// channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 獲取到達的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
// 監聽隊列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
工作隊列模式Work Queue
爲什麼 會出現 work queues?
前提: 使用 simple 隊列 的時候
我們應用程序在是使用消息系統的時候,一般生產者 P 生產消息是毫不費力的(發送消息即可),而消費者接收完消息
後的需要處理,會耗費一定的時間,這時候,就有可能導致很多消息堆積在隊列裏面,一個消費者有可能不夠用
那麼怎麼讓消費者同事處理多個消息呢?
在同一個隊列上創建多個消費者,讓他們相互競爭,這樣消費者就可以同時處理多條消息了
使用任務隊列的優點之一就是可以輕易的並行工作。如果我們積壓了好多工作,我們可以通過增加工作者(消費者)
來解決這一問題,使得系統的伸縮性更加容易。
Round-robin (輪詢分發)
生產者
package rabbitMQ.com.rabbitMQ.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 50; i++) {
// 消息內容
String message = "." + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
Thread.sleep(i * 10);
}
channel.close();
connection.close();
}
}
消費者1
package rabbitMQ.com.rabbitMQ.work;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv1 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列,主要爲了防止消息接收者先運行此程序,隊列還不存在時創建隊列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定義一個消息的消費者
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [1] Received '" + message + "'");
try {
doWork(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
}
}
};
boolean autoAck = true; // 消息的確認模式自動應答
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
private static void doWork(String task) throws InterruptedException {
Thread.sleep(1000);
}
}
消費者2
package rabbitMQ.com.rabbitMQ.work;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列,主要爲了防止消息接收者先運行此程序,隊列還不存在時創建隊列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定義一個消息的消費者
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [2] Received '" + message + "'");
try {
doWork(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
}
}
};
boolean autoAck = true; // 消息的確認模式自動應答
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
private static void doWork(String task) throws InterruptedException {
Thread.sleep(2000);
}
}
測試結果
備註:消費者 1 我們處理時間是 1s ;而消費者 2 中處理時間是 2s;
但是我們看到的現象並不是 1 處理的多 消費者 2 處理的少,
消費者 1 中將偶數部分處理掉了
[1] Received '.0'
[x] Done
[1] Received '.2'
[x] Done
[1] Received '.4'
[x] Done
[1] Received '.6'
消費者 2 中將偶數部分處理掉了
[2] Received '.1'
[x] Done
[2] Received '.3'
[x] Done
[2] Received '.5'
[x] Done
…… .. . . .
1.消費者 1 和消費者 2 獲取到的消息內容是不同的,同一個消息只能被一個消費者獲取
2.消費者 1 和消費者 2 貨到的消息數量是一樣的 一個奇數一個偶數
按道理消費者 1 獲取的比消費者 2 要多
這種方式叫做 輪詢分發 結果就是不管誰忙或清閒,都不會給誰多一個任務或少一個任務,任務總是你一個我一個
的分
Fair dispatch (公平分發)
雖然上面的分配法方式也還行,但是有個問題就是:比如:現在有 2 個消費者,所有的偶數的消息都是繁忙的,而奇數則是輕鬆的。按照輪詢的方式,偶數的任務交給了第一個消費者,所以一直在忙個不停。奇數的任務交給另一個消費者,則立即完成任務,然後閒得不行。而 RabbitMQ 則是不瞭解這些的。他是不知道你消費者的消費能力的,這是因爲當消息進入隊列RabbitMQ 就會分派消息。而 rabbitmq 只是盲目的將消息輪詢的發給消費者。你一個我一個的這樣發送.爲了解決這個問題,我們使用basicQos( prefetchCount = 1)方法,來限制 RabbitMQ 只發不超過 1 條的消息給同一個消費者。當消息處理完畢後,有了反饋 ack纔會進行第二次發送。(也就是說需要手動反饋給 Rabbitmq )
生產者
package rabbitMQ.com.rabbitMQ.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
// 創建一個頻道
Channel channel = connection.createChannel();
// 指定一個隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
int prefetchCount = 1;
// 每個消費者發送確認信號之前,消息隊列不發送下一個消息過來,一次只處理一個消息
// 限制發給同一個消費者不得超過1條消息
channel.basicQos(prefetchCount);
// 發送的消息
for (int i = 0; i < 50; i++) {
String message = "." + i;
// 往隊列中發出一條消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
Thread.sleep(i * 10);
}
// 關閉頻道和連接
channel.close();
connection.close();
}
}
消費者1
package rabbitMQ.com.rabbitMQ.work.fair;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv1 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列,主要爲了防止消息接收者先運行此程序,隊列還不存在時創建隊列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);// 保證一次只分發一個
// 定義一個消息的消費者
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [1] Received '" + message + "'");
try {
doWork(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false; // 手動確認消息
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
private static void doWork(String task) throws InterruptedException {
Thread.sleep(1000);
}
}
消費者2
package rabbitMQ.com.rabbitMQ.work.fair;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列,主要爲了防止消息接收者先運行此程序,隊列還不存在時創建隊列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);// 保證一次只分發一個
// 定義一個消息的消費者
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [2] Received '" + message + "'");
try {
doWork(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false; // 手動確認消息
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
private static void doWork(String task) throws InterruptedException {
Thread.sleep(2000);
}
}
測試結果
這時候 現象就是 消費者 者 1 速度 大於消費者 2
boolean autoAck = true ;( 自動 確認 模式) 一旦 RabbitMQ 將消息分發給了消費者,就會從內存中刪除。在這種情況下,如果殺死正在執行任務的消費者,會丟失正在處理的消息,也會丟失已經分發給這個消費者但尚未處理的消息。
boolean autoAck = false ; (手動確認模式) 我們不想丟失任何任務,如果有一個消費者掛掉了,那麼我們應該將分發給它的任務交付給另一個消費者去處理。 爲了確保消息不會丟失,RabbitMQ 支持消息應答。消費者發送一個消息應答,告訴 RabbitMQ 這個消息已經接收並且處理完畢了。RabbitMQ 可以刪除它了。
消息應答是默認打開的。也就是 boolean autoAck = false
訂閱 模式 Publish/Subscribe
解讀:
1、1 個生產者,多個消費者
2、每一個消費者都有自己的一個隊列
3、生產者沒有將消息直接發送到隊列,而是發送到了交換機(轉發器)
4、每個隊列都要綁定到交換機
5、生產者發送的消息,經過交換機,到達隊列,實現,一個消息被多個消費者獲取的目的
6、使用通道channel創建交換機並指定交換機類型爲fanout
生產者
package rabbitMQ.com.rabbitMQ.publish;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// 聲明exchange 交換機 轉發器
channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // fanout 分裂
// 消息內容
String message = "Hello PB";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
消費者1
package rabbitMQ.com.rabbitMQ.publish;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv1 {
private final static String QUEUE_NAME = "test_queue_fanout_1";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// ------------下面邏輯和work模式一樣-----
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義一個消費者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1] Recv msg:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done ");
// 手動回執
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
消費者2
package rabbitMQ.com.rabbitMQ.publish;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_fanout_2";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// ------------下面邏輯和work模式一樣-----
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義一個消費者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2] Recv msg:" + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done ");
// 手動回執
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
測試結果
一個消息 可以被多個消費者獲取
路由模式
說明:生產者發送消息到交換機並且要指定路由key,消費者將隊列綁定到交換機時需要指定路由key
使用通道channel創建交換機並指定交換機類型爲direct
生產者
package rabbitMQ.com.rabbitMQ.route;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// 聲明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 消息內容
String message = "id=1001的商品刪除了";
channel.basicPublish(EXCHANGE_NAME, "delete", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
消費者1
package rabbitMQ.com.rabbitMQ.route;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv1 {
private final static String QUEUE_NAME = "test_queue_direct_1";
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "update");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
// 消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1] Recv msg:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done ");
// 手動回執
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
消費者2
package rabbitMQ.com.rabbitMQ.route;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_direct_2";
private final static String EXCHANGE_NAME = "order-exchanges";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "add");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
// 消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2] Recv msg:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done ");
// 手動回執
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
通配符模式Topic
說明:生產者P發送消息到交換機X,type=topic,交換機根據綁定隊列的routing key的值進行通配符匹配;
符號#:匹配一個或者多個詞 lazy.# 可以匹配 lazy.irs或者lazy.irs.cor
符號*:只能匹配一個詞 lazy.* 可以匹配 lazy.irs或者lazy.cor使用通道channel創建交換機並指定交換機類型爲topic
生產者
package rabbitMQ.com.rabbitMQ.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// 聲明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 消息內容
String message = "id=1001";
channel.basicPublish(EXCHANGE_NAME, "item.delete", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
消費者
package rabbitMQ.com.rabbitMQ.topic;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class Recv1 {
private final static String QUEUE_NAME = "test_queue_topic_1";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.*");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義隊列的消費者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1] Recv msg:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done ");
// 手動回執
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
RabbitMQ 之消息確認機制(事務+Confirm )
概述
在 Rabbitmq 中我們可以通過持久化來解決因爲服務器異常而導致丟失的問題,除此之外我們還會遇到一個問題:生產者將消息發送出去之後,消息到底有沒有正確到達 Rabbit 服務器呢?如果不錯得數處理,我們是不知道的,(即 Rabbit 服務器不會反饋任何消息給生產者),也就是默認的情況下是不知道消息有沒有正確到達;
導致 的問題:消息到達服務器之前丟失,那麼持久化也不能解決此問題,因爲消息根本就沒有到達 Rabbit 服務器!
RabbitMQ 爲我們 提供了兩種方式:
1. 通過 AMQP 事務機制實現,這也是 AMQP 協議層面提供的解決方案;
2. 通過將 channel 設置成 confirm 模式來實現;
事務機制
RabbitMQ 中與事務機制有關的方法有三個:txSelect(), txCommit()以及 txRollback(), txSelect 用於將當前 channel 設置成 transaction 模式,txCommit 用於提交事務,txRollback 用於回滾事務,在通過 txSelect 開啓事務之後,我們便可以發佈消息給 broker 代理服務器了,如果 txCommit 提交成功了,則消息一定到達了 broker 了,如果在 txCommit執行之前 broker 異常崩潰或者由於其他原因拋出異常,這個時候我們便可以捕獲異常通過 txRollback 回滾事務了。
關鍵代碼
channel.txSelect();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
channel.txCommit();
生產者
package rabbitMQ.com.rabbitMQ.transaction;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendMQ {
private static final String QUEUE_NAME = "QUEUE_simple";
public static void main(String[] args) throws IOException, TimeoutException {
/* 獲取一個連接 */
Connection connection = ConnectionUtils.getConnection();
/* 從連接中創建通道 */
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String msg = "Hello Simple QUEUE !";
try {
channel.txSelect();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
channel.txCommit();
} catch (Exception e) {
channel.txRollback();
System.out.println("----msg rollabck ");
} finally {
System.out.println("---------send msg over:" + msg);
}
channel.close();
connection.close();
}
}
此種模式還是很耗時的,採用這種方式 降低了 Rabbitmq 的消息吞吐量
Confirm 模式
概述
上面我們介紹了 RabbitMQ 可能會遇到的一個問題,即生成者不知道消息是否真正到達 broker,隨後通過 AMQP 協議層面爲我們提供了事務機制解決了這個問題, 但是採用事務機制實現會降低RabbitMQ 的消息吞吐量,那麼有沒有更加高效的解決方式呢?答案是採用 Confirm 模式。
producer 端 confirm 模式的實現原理
生產者將信道設置成 confirm 模式,一旦信道進入 confirm 模式,所有在該信道上面發佈的消息都會被指派一個唯一的 ID(從 1 開始),一旦消息被投遞到所有匹配的隊列之後,broker 就會發送一個確認給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了,如果消息和隊列是可持久化的,那麼確認消息會將消息寫入磁盤之後發出,broker 回傳給生產者的確認消息中 deliver-tag 域包含了確認消息的序列號,此外 broker 也可以設置 basic.ack 的 multiple 域,表示到這個序列號之前的所有消息都已經得到了處理。confirm 模式最大的好處在於他是異步的,一旦發佈一條消息,生產者應用程序就可以在等信道返回確認的同時繼續發送下一條消息,當消息最終得到確認之後,生產者應用便可以通過回調方法來處理該確認消息,如果RabbitMQ 因爲自身內部錯誤導致消息丟失,就會發送一條 nack 消息,生產者應用程序同樣可以在回調方法中處理該 nack 消息。
開啓 confirm 模式的方法
已經在 transaction 事務模式的 channel 是不能再設置成 confirm 模式的,即這兩種模式是不能共存的。
生產者通過調用 channel 的 confirmSelect 方法將 channel 設置爲 confirm 模式
核心代碼:
//生產者通過調用channel的confirmSelect方法將channel設置爲confirm模式
channel.confirmSelect();
編程 模式
1. 普通 confirm 模式:每發送一條消息後,調用 waitForConfirms()方法,等待服務器端confirm。實際上是一種串行 confirm 了。
2. 批量 confirm 模式:每發送一批消息後,調用 waitForConfirms()方法,等待服務器端confirm。
3. 異步 confirm 模式:提供一個回調方法,服務端 confirm 了一條或者多條消息後Client 端會回調這個方法。
普通 confirm
package rabbitMQ.com.rabbitMQ.confirm;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendConfirm {
private static final String QUEUE_NAME = "QUEUE_simple_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
/* 獲取一個連接 */
Connection connection = ConnectionUtils.getConnection();
/* 從連接中創建通道 */
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 生產者通過調用channel的confirmSelect方法將channel設置爲confirm模式
channel.confirmSelect();
String msg = "Hello QUEUE !";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
if (!channel.waitForConfirms()) {
System.out.println("send message failed.");
} else {
System.out.println(" send messgae ok ...");
}
channel.close();
connection.close();
}
}
批量 confirm 模式
批量 confirm 模式稍微複雜一點,客戶端程序需要定期(每隔多少秒)或者定量(達到多少條)或者兩則結合起來publish 消息,然後等待服務器端 confirm, 相比普通 confirm 模式,批量極大提升 confirm 效率,但是問題在於一旦出現 confirm 返回 false 或者超時的情況時,客戶端需要將這一批次的消息全部重發,這會帶來明顯的重複消息數量,並且,當消息經常丟失時,批量 confirm 性能應該是不升反降的
package rabbitMQ.com.rabbitMQ.confirm;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendbatchConfirm {
private static final String QUEUE_NAME = "QUEUE_simple_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
/* 獲取一個連接 */
Connection connection = ConnectionUtils.getConnection();
/* 從連接中創建通道 */
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 生產者通過調用channel的confirmSelect方法將channel設置爲confirm模式
channel.confirmSelect();
String msg = "Hello QUEUE !";
for (int i = 0; i < 10; i++) {
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
}
if (!channel.waitForConfirms()) {
System.out.println("send message failed.");
} else {
System.out.println(" send messgae ok ...");
}
channel.close();
connection.close();
}
}
異步 confirm 模式
Channel 對象提供的 ConfirmListener()回調方法只包含 deliveryTag(當前 Chanel 發出的消息序號),我們需要自己爲每一個 Channel 維護一個 unconfirm 的消息序號集合,每 publish 一條數據,集合中元素加 1,每回調一次 handleAck方法,unconfirm 集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm 集合最好採用有序集合 SortedSet 存儲結構。實際上,SDK 中的 waitForConfirms()方法也是通過 SortedSet維護消息序號的
package rabbitMQ.com.rabbitMQ.confirm;
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import rabbitMQ.com.rabbitMQ.util.ConnectionUtils;
public class SendAync {
private static final String QUEUE_NAME = "QUEUE_simple_confirm_aync";
public static void main(String[] args) throws IOException, TimeoutException {
/* 獲取一個連接 */
Connection connection = ConnectionUtils.getConnection();
/* 從連接中創建通道 */
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 生產者通過調用channel的confirmSelect方法將channel設置爲confirm模式
channel.confirmSelect();
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
channel.addConfirmListener(new ConfirmListener() {
// 每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple) {
System.out.println("--multiple--");
confirmSet.headSet(deliveryTag + 1).clear();// 用一個SortedSet, 返回此有序集合中小於end的所有元素。
} else {
System.out.println("--multiple false--");
confirmSet.remove(deliveryTag);
}
}
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple:" + multiple);
if (multiple) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag);
}
}
});
String msg = "Hello QUEUE !";
while (true) {
long nextSeqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
confirmSet.add(nextSeqNo);
}
}
}