1.消息應答模式(手動、自動)
1.1應答模式
爲了確保消息不會丟失,RabbitMQ支持消息應答。消費者發送一個消息應答,告訴RabbitMQ這個消息已經接收並且處理完畢了。RabbitMQ就可以刪除它了。
如果一個消費者掛掉卻沒有發送應答,RabbitMQ會理解爲這個消息沒有處理完全,然後交給另一個消費者去重新處理。這樣,你就可以確認即使消費者偶爾掛掉也不會丟失任何消息了。 沒有任何消息超時限制;只有當消費者掛掉時,RabbitMQ纔會重新投遞。即使處理一條消息會花費很長的時間。
消息應答是默認打開的。我們通過顯示的設置autoAsk=true關閉這種機制。現即自動應答開,一旦我們完成任務,消費者會自動發送應答。通知RabbitMQ消息已被處理,可以從內存刪除。如果消費者因宕機或鏈接失敗等原因沒有發送ACK(不同於ActiveMQ,在RabbitMQ裏,消息沒有過期的概念),則RabbitMQ會將消息重新發送給其他監聽在隊列的下一個消費者
.1.2自動應答
// 4.設置應答模式,true的時候爲自動應答,false爲手動應答,需要處理
channel.basicConsume(QUEUE_EMAIL, true, defaultConsumer);
生產者
public class Producer {
private static String EXCHANGE = "sms_email";
public static void main(String[] args) throws IOException, TimeoutException {
//1.創建連接
Connection connection = ConnectionUtils.newConnection();
// 2.創建通道
Channel channel = connection.createChannel();
//3.綁定的交換機 參數1交互機名稱 參數2 exchange類型
channel.exchangeDeclare(EXCHANGE, "fanout");
String msg = "測試生產消息" + new Date();
try {
// channel.txSelect();
// 4.生產消息
channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
// int i = 1 / 0;
System.out.println("生產者生產消息:" + msg);
// channel.txCommit();
} catch (IOException e) {
e.printStackTrace();
// channel.txRollback();
} finally {
channel.close();
connection.close();
}
}
}
短信消費者
public class SmsConsumer {
private static String QUEUE_SMS = "sms";
private static String EXCHANGE = "sms_email";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
System.out.println("短信消費者");
//1.創建連接
Connection connection = ConnectionUtils.newConnection();
// 2.創建通道
final Channel channel = connection.createChannel();
// 3.聲明隊列
/**
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* queue:這沒什麼好說的,隊列名
*
* durable:是否持久化,那麼問題來了,這是什麼意思?持久化,指的是隊列持久化到數據庫中。在之前的博文中也說過,如果RabbitMQ服務掛了怎麼辦,隊列丟失了自然是不希望發生的。持久化設置爲true的話,即使服務崩潰也不會丟失隊列
* exclusive:是否排外,what? 這又是什麼呢。設置了排外爲true的隊列只可以在本次的連接中被訪問,也就是說在當前連接創建多少個channel訪問都沒有關係,但是如果是一個新的連接來訪問,對不起,不可以,下面是我嘗試訪問了一個排外的queue報的錯。還有一個需要說一下的是,排外的queue在當前連接被斷開的時候會自動消失(清除)無論是否設置了持久化
* autoDelete:這個就很簡單了,是否自動刪除。也就是說queue會清理自己。但是是在最後一個connection斷開的時候
* 設置隊列的其他一些參數
*/
channel.queueDeclare(QUEUE_SMS, false, false, false, null);
/**
* String queue, String exchange, String routingKey
*/
channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msgString = new String(body, "UTF-8");
System.out.println("短信消費者開始獲取消息.........." );
int i = 1 / 0;
// 手動應答消息
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("短信消費者結束獲取消息:" + msgString);
}
};
// 4.設置應答模式,true的時候爲自動應答,false爲手動應答,需要處理
channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
}
}
郵件消費者
public class SmsConsumer {
private static String QUEUE_SMS = "sms";
private static String EXCHANGE = "sms_email";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
System.out.println("短信消費者");
//1.創建連接
Connection connection = ConnectionUtils.newConnection();
// 2.創建通道
final Channel channel = connection.createChannel();
// 3.聲明隊列
/**
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* queue:這沒什麼好說的,隊列名
*
* durable:是否持久化,那麼問題來了,這是什麼意思?持久化,指的是隊列持久化到數據庫中。在之前的博文中也說過,如果RabbitMQ服務掛了怎麼辦,隊列丟失了自然是不希望發生的。持久化設置爲true的話,即使服務崩潰也不會丟失隊列
* exclusive:是否排外,what? 這又是什麼呢。設置了排外爲true的隊列只可以在本次的連接中被訪問,也就是說在當前連接創建多少個channel訪問都沒有關係,但是如果是一個新的連接來訪問,對不起,不可以,下面是我嘗試訪問了一個排外的queue報的錯。還有一個需要說一下的是,排外的queue在當前連接被斷開的時候會自動消失(清除)無論是否設置了持久化
* autoDelete:這個就很簡單了,是否自動刪除。也就是說queue會清理自己。但是是在最後一個connection斷開的時候
* 設置隊列的其他一些參數
*/
channel.queueDeclare(QUEUE_SMS, false, false, false, null);
/**
* String queue, String exchange, String routingKey
*/
channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msgString = new String(body, "UTF-8");
System.out.println("短信消費者開始獲取消息.........." );
int i = 1 / 0;
// 手動應答消息
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("短信消費者結束獲取消息:" + msgString);
}
};
// 4.設置應答模式,true的時候爲自動應答,false爲手動應答,需要處理
channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
}
}
異常狀態:
int i = 1 / 0,我們在郵件和短信消費者代碼裏面加入,在手動或者自動應答之前拋出異常,就會導致消費都沒被消費,而如果在應答之後拋出異常,只能用事務來處理,使用的消息隊列是sms和email.
看到消息都是出於等待被消費的狀態
正常
註釋掉int i = 1/0,
手動應答(隊列sms):通過debug可以看到,執行完1,消費還沒被確認
直到2執行完成後,才被確認
自動應答(隊列email):執行完成後就被確認了
1.消息事務
事務的實現主要是對信道(Channel)的設置,主要的方法有三個:
channel.txSelect()聲明啓動事務模式;
channel.txComment()提交事務;
channel.txRollback()回滾事務;
生產者回滾:消費隊列中看不到任何消息
channel.txSelect();
// 4.生產消息
channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
int i = 1 / 0;
channel.txCommit();
System.out.println("生產者生產消息:" + msg);
} catch (IOException e) {
e.printStackTrace();
channel.txRollback();
}
消費者回滾:消息沒有被消費,進行了回滾
try {
channel.txSelect();
System.out.println("短信消費者開始獲取消息.........." );
// 手動應答消息
channel.basicAck(envelope.getDeliveryTag(), false);
int i = 1 / 0;
channel.txCommit();
System.out.println("短信消費者結束獲取消息:" + msgString);
} catch (IOException e) {
channel.txRollback();
}
3.公平轉發
目前消息轉發機制是平均分配,這樣就會出現倆個消費者,奇數的任務很耗時,偶數的任何工作量很小,造成的原因就是近當消息到達隊列進行轉發消息。並不在乎有多少任務消費者並未傳遞一個應答給RabbitMQ。僅僅盲目轉發所有的奇數給一個消費者,偶數給另一個消費者。 爲了解決這樣的問題,我們可以使用basicQos方法,傳遞參數爲prefetchCount= 1。這樣告訴RabbitMQ不要在同一時間給一個消費者超過一條消息。 換句話說,只有在消費者空閒的時候會發送下一條信息。調度分發消息的方式,也就是告訴RabbitMQ每次只給消費者處理一條消息,也就是等待消費者處理完畢並自己對剛剛處理的消息進行確認之後,才發送下一條消息,防止消費者太過於忙碌,也防止它太過去清閒。
**通過 設置channel.basicQos(1);** 我們生產了10條消息, 01消費者,休眠時間比較短,消費了9條消息消費者02,休眠時間較長,最後只消費了1條信息
總結:也就是通過參數設置,我們可以選擇讓空閒的消費者多去消費消息,而忙碌的消費者,則等空閒再去消費消息