工作隊列
包含一個生產者和多個消費者的MQ。生產者發送消息非常輕鬆,消費者和業務結合,需要花費時間。
下面介紹一種輪詢方式的工作隊列:
首先定義生產者:
public class Send {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i<50; i++){
String message = "work+"+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
Thread.sleep(i*20);
}
}
}
生產50條消息,給下面的兩個消費者進行食用:
消費者One(這裏我們消費一條消息就sleep 1s)
public class ReceiveOne {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [ConsumerOne is] Received '" + message + "'");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
消費者Two (sleep 2s)
public class ReceiveTwo {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [ConsumerTwo is] Received '" + message + "'");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
下面我們看兩個控制檯的輸出:
ReceiveOne
ReceiveTwo
事實證明,設置兩個不同的sleep時間,並沒有影響兩個消費者輪流消費,這種方式稱之爲輪詢。
這樣看起來不太合理,消費快的消費者理應多消費一些,所謂“能者多勞”,爲了解決這個問題,下面我們引入了公平分發:
公平分發,它是由消費者主動發送ACK應答,告訴生產者:“我已經消費完了,快點給我下一個”,之前的都是,自動發送ACK,並非手動的。下面我們實現一下手動的。
只需要在前面的代碼修改部分即可:
發送者:
public class Send {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消費者發送確認消息(ACK)之前,只發送一個消息給你
channel.basicQos(1);
for(int i = 0; i<50; i++){
String message = "work+"+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
Thread.sleep(i*20);
}
}
}
消費者1:
public class ReceiveOne {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1); //需要修改
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [ConsumerOne is] Received '" + message + "'");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false); //這裏是手動應答
}
};
boolean autoAck = false; //這裏需要修改至手動應答
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> { });
}
}
同理2也是一樣的,這裏就不再重複了。
很明顯,消費者1的處理速度比消費者2要快,因爲消費者1的sleep時間較短,這就和上面的輪詢立判高下了,體現了能者多勞。
下面我們有一個問題,我們設置 boolean autoAck = false;
這個是有隱患的,假設我們我的消費者1掛了,而消息已經發送過去了,這時候,我們該怎麼辦?這時候這個消息是丟失的。爲了應對這種情況,我們可以設置autoack = true
,能解決問題。但是我們又想公平分發,這就有點麻煩了,怎麼解決這個問題,暫時沒有想法,容我繼續學習。
還有一個問題,如果RabbitMQ掛了,這時候怎麼辦?這裏我們就要設置另外一個參數了,那就是durable
,表示可持久化。
在聲明隊列的時候,就要設置好,等隊列已經存在了再設置的話,就會報錯。這點注意,如果已經報錯了,就刪除隊列重新聲明。
boolean durable = false; //聲明數據可以持久化
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);