入门教程1 我们学到了 P——>队列——>C 这种单一的模式。一个生产者对应一个消费者。那么在实际中可能存在一个生产者对应多个消费者,如在车间里面的生产线,一个流水线生产的部件可能供应对应多个工人小费。那么就引入了今天所讨论的知识。
工作队列
我们通过Hello World的例子,从生产者发送一条消息到RabbitMQ,然后消费者接收到这条消息并打印出来。这次我们模拟一个工厂流水线的场景,由工厂任务安排者(生产者P)向流水线(RabbitMQ的队列hello)放入半成品,然后由多个工人(消费者C1和C2)从流水线获取半成品进行处理。
源码
目录结构:
ConnectionUtil是连接rabbitMq的工具类,当前是一个生产者 两个消费者甚至多个消费者。
生产者代码如下:
package wxtest.rabbitMq.workQueue;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import wxtest.rabbitMq.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class produce {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建一个通道
Channel channel = ConnectionUtil.getConnection().createChannel();
// 指定一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 发送消息
String message = null;
//我们来发送五个消息到消息队列中
for (int i = 0; i < 5; i++) {
message = "Hello World! " + i;
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
}
// 关闭频道和连接
channel.close();
ConnectionUtil.getConnection().close();
}
}
消费者1:
package wxtest.rabbitMq.workQueue;
import com.rabbitmq.client.*;
import wxtest.rabbitMq.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class consumer1 {
//与生产者的队列名相同
public static String queue_name = "hello";
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);
System.out.println("consumer1 [*] Waiting for messages. To exit press CTRL+C");
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "Utf-8");
System.out.println(" consumer1 [x] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println("consumer1 [x] Done");
// 消息处理完成确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(queue_name, false, consumer);
}
private static void doWork(String message) {
try {
Thread.sleep(1000); // 暂停1秒钟
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
消费者2:
package wxtest.rabbitMq.workQueue;
import com.rabbitmq.client.*;
import wxtest.rabbitMq.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class consumer2 {
//与生产者的队列名相同
public static String queue_name = "hello";
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);
System.out.println("consumer2 [*] Waiting for messages. To exit press CTRL+C");
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "Utf-8");
System.out.println(" [consumer2] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println("consumer2 [x] Done");
// 消息处理完成确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(queue_name, false, consumer);
}
private static void doWork(String message) {
try {
Thread.sleep(1000); // 暂停1秒钟
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
输出结果 消费者1为
consumer1 [*] Waiting for messages. To exit press CTRL+C
consumer1 [x] Received 'Hello World! 1'
consumer1 [x] Done
consumer1 [x] Received 'Hello World! 3'
consumer1 [x] Done
输出结果 消费者2为
consumer2 [*] Waiting for messages. To exit press CTRL+C
[consumer2] Received 'Hello World! 0'
consumer2 [x] Done
[consumer2] Received 'Hello World! 2'
consumer2 [x] Done
[consumer2] Received 'Hello World! 4'
consumer2 [x] Done
忘记确认
忘记通过basicAck返回确认信息是常见的错误。这个错误非常严重,将导致消费者客户端退出或者关闭后,消息会被退回RabbitMQ服务器,这会使RabbitMQ服务器内存爆满,而且RabbitMQ也不会主动删除这些被退回的消息。
如果要监控这种错误,可以使用rabbitmqctl messages_unacknowledged命令打印出出相关的信息。