(一)RabbitMQ工作隊列模型結構
生產者提供消息到消息隊列中,消費者可以去獲取隊列中的消息。在工作隊列中默認採用輪詢分發的方式將消息分發給消費者。所謂輪詢分發,就是指不管消費者處理消息的速度是快是慢,都按照順序輪流把消息發給消費者。
(二)工作隊列實踐(輪詢分發)
2.1 創建工具類
創建工具類的代碼在前一篇博客中已經講到了,這裏直接貼上代碼:
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
//定義一個連接工廠
ConnectionFactory factory=new ConnectionFactory();
//設置服務地址
factory.setHost("127.0.0.1");
//設置AMQP端口
factory.setPort(5672);
//設置VHOSTS
factory.setVirtualHost("/vhosts_sdxb");
//設置用戶名
factory.setUsername("user_sdxb");
factory.setPassword("123456");
return factory.newConnection();
}
}
2.2 創建生產者
public class Send {
private static final String QUEUE_NAME="work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//1.獲取連接
Connection connection = ConnectionUtil.getConnection();
//2.創建通道
Channel channel = connection.createChannel();
//3.創建隊列聲明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i=0;i<50;i++){
String msg="i="+i;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
channel.close();
connection.close();
}
}
2.3 創建消費者一
爲了體現消費者處理消息的快慢,我在兩個消費者中分別設置線程休眠1s和2s
public class Receive1 {
private static final String QUEUE_NAME="work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtil.getConnection();
//創建通道
Channel channel = connection.createChannel();
//創建隊列聲明
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//創建消費者監聽方法
Consumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println(msg);
try {
//設置睡眠實踐1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//監聽隊列
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
2.4 創建消費者二
public class Receive2 {
private static final String QUEUE_NAME="work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Consumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println(msg);
try {
//設置睡眠時間2s
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
分別將兩個消費者運行起來,然後運行生產者發送50條消息,可以發現雖然兩個消費者處理消息的能力有快有慢,但是得到的消息都是25條,下面展示消費者1獲取的消息部分截圖。
(三)公平分發(Fair dispatch)
在某些場景下輪詢分發是不合理的,因此工作隊列還有公平分發的方式,所謂公平分發,就是能者多勞,處理消息快的人獲得消息多,處理消息慢的人獲得消息少。公平分發的實現只需要對代碼做一些修改:
3.1 修改生產者
對於生產者,只需要對通道增加一條限制,限制通道發送給同一個消費者不得超過一條消息,也就是隻有當消費者處理完一條消息以後纔會發第二條消息給它。使用channel.basicQos();方法,設置參數爲1表示限制一次不超過1條消息。
public class Send {
private static final String QUEUE_NAME="work_queue_fair";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//限制通道發送給同一個消費者不得超過一條消息
int prefenchCount=1;
channel.basicQos(prefenchCount);
for (int i=0;i<50;i++){
String msg="i="+i;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
channel.close();
connection.close();
}
}
3.2 修改消費者
對於消費者,需要修改三處地方,第一處和生產者一樣修改通道的限制信息;第二處關閉消費者的自動應答;第三處設置手動回執,即處理完一條消息後手動發送處理完成的指令給隊列。
//保證一次只分發一次
channel.basicQos(1);
//設置手動回執
channel.basicAck(envelope.getDeliveryTag(),false);
//關閉自動應答
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
public class Receive1 {
private static final String QUEUE_NAME="work_queue_fair";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//保證一次只分發一次
channel.basicQos(1);
Consumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println(msg);
//設置手動回執
channel.basicAck(envelope.getDeliveryTag(),false);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//關閉自動應答
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
3.3 關於自動應答
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
當autoAck=true時,表示開啓自動應答,一旦rabbitmq將隊列中的消息發送給消費者,這個消息就會從隊列中消失。但是如果此時消費者掛掉了,那麼這條消息也就徹底消失了。
當autoAck=false時,關閉自動應答,rabbitmq將隊列中的消息發送給消費者,只有當消費者返回確認之後,隊列中的消息纔會被刪除。