RabbitMQ学习笔记(2)
1、工作队列Work Queues
由一个生产者进行生产,经由消息队列,被多个消费者消费。
发送端代码如下:
public class NewTask {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String message = String.join(" ",args);
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println(" [x] sent '"+ message + "'");
channel.close();
connection.close();
}
}
接收端代码如下:
public class Worker {
private final static String QUEUE_NAME = "work_queue";
private static void doWork(String task) throws InterruptedException {
for (char ch: task.toCharArray()) {
if (ch == '.') Thread.sleep(10000);
}
}
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("[*] waiting for messages To exit Press CTRL + c");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
}catch (InterruptedException e){
}finally {
System.out.println(" [x] Done");
}
};
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> { });
}
}
2、代码测试
- 启动两个work的main函数
- 通过编辑NewTask的main函数参数,多次启动NewTask函数
idea工具如下:
运行结果如下:
结果显示,MQ容器把消息平均的分配给两个消费者进行了消费。
3、消息回执
自此,很容易想到,当消息(任务)传递到Queue之后,它立马会由某一个Worker进行标记。可能会有这种情形,如果某一个Worker处理Task耗时太久,或者该Worker被我们kill掉了。那么由他标记的消息将会丢失。
这种情形我们不想丢失消息,RabbitMQ提供了一种消息回执(Message Acknowledgment)机制。
boolean autoAck = true;//表示关闭消息回执
4、消息持久化
当Worker 挂掉之后正在处理的该条消息仍然会丢失。那么这个时候我们可以通过消息的持久化来处理这种情形。
boolean durable = true;
channel.queueDeclare("task_queue", durable, false, false, null);
channel.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
*已经定义的队列不能更改他的属性为持久化,必须重新定义一个新的队列。
5、公平分发策略
由上面的打印图可以看出,MQ是把第n条消息轮流分给每个队列。如果每个队列处理消息的时间不一致,就会出现有的worker很闲,有的很忙。要充分利用能者多劳的机制。
加上:
int prefetchCount = 1;
channel.basicQos(prefetchCount);
最终代码:
public class NewTask {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
boolean durable = true ;
channel.queueDeclare(QUEUE_NAME,durable,false,false,null);
String message = String.join(" ",args);
channel.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
System.out.println(" [x] sent '"+ message + "'");
channel.close();
connection.close();
}
}
public class Worker {
private final static String QUEUE_NAME = "work_queue";
private static void doWork(String task) throws InterruptedException {
for (char ch: task.toCharArray()) {
if (ch == '.') Thread.sleep(10000);
}
}
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.basicQos(1);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println("[*] waiting for messages To exit Press CTRL + c");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
}catch (InterruptedException e){
}finally {
System.out.println(" [x] Done");
}
};
boolean autoAck = false;//true表示关闭消息回执
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> { });
}
}