上一篇,我们介绍了rabbitmq中最简单的一种模型——收发模型,收、发都只有一方,即一个应用,并且不涉及到exchange (实际上这种模型中也是有exchange的,exchange的类型为direct,且名称为空字符串"",只不过这里的exchange是透明的,故常被我们所忽略了,好像收、发之间是直接连通的)。
本篇我们介绍一下rabbitmq中的另一种模型——工作队列模型,也叫单发多收模型,它是收发模型的延伸。它支持一方发送消息,由多方共同接收处理,并且支持给多个消费方设置不同的QOS 。 如图:
例如,一个大系统中每天都与产生大量的任务(消息),需要发布给各个子系统去共同处理,这时候就可以采用工作队列模型 。
示例代码:
任务生产(发布)者:
package com.tingcream.rabbitmq.workQueues;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* (AMQP DEFAULT)
* @author jelly
*
*/
public class NewTask {
private final static String QUEUE_NAME = "direct_task_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//主机 端口 vhost 用户名 密码
factory.setHost("192.168.9.102");
factory.setUsername("rabbitmq");
factory.setPassword("rabbitmq123");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
boolean durable=true ;
channel.queueDeclare(QUEUE_NAME, durable, false,false, null);
//String message = "Hello rabbitmq";
//exchagne名称默认为 (AMQP DEFAULT) direct
//channel.basicPublish(exchange, routingKey, props, body);
//channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
//System.out.println(" [x] Sent '" + message + "'");
for(int i=0;i<20;i++) {
String message="hello direct task message E "+i;
//发布消息
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
}
//关闭连接
channel.close();
connection.close();
}
}
任务处理(消费)者A:
package com.tingcream.rabbitmq.workQueues;
import java.io.IOException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
public class WorkA {
private final static String QUEUE_NAME = "direct_task_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//主机 端口 vhost 用户名 密码
factory.setHost("192.168.9.102");
factory.setUsername("rabbitmq");
factory.setPassword("rabbitmq123");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare(queue, durable, exclusive, autoDelete, arguments)
boolean durable=true ;
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);//channel向服务器声明一个队列,设置durable为true,则当rabbitmq 服务器重启时,队列不会丢失
System.out.println("WorkerA Waiting for messages");
//每次从队列获取的message的数量
channel.basicQos(1);
// prefetchCount maximum number of messages that the server will deliver, 0 if unlimited
final 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("WorkerA Received :" + message );
try {
doWork(message);
}catch (Exception e){
channel.abort();
}finally {
System.out.println("WorkerA Done");
channel.basicAck(envelope.getDeliveryTag(),false);//当消息处理完毕后 在finally中 回复一个ack 手动ack
}
}
};
boolean autoAck=false;
//消息消费完成确认 不自动 ack 非自动ack
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
private static void doWork(String task) {
try {
Thread.sleep(5000);// 暂停2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
任务处理(消费)者B:
package com.tingcream.rabbitmq.workQueues;
import java.io.IOException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
public class WorkB {
private final static String QUEUE_NAME = "direct_task_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//主机 端口 vhost 用户名 密码
factory.setHost("192.168.9.102");
factory.setUsername("rabbitmq");
factory.setPassword("rabbitmq123");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare(queue, durable, exclusive, autoDelete, arguments)
boolean durable=true ;//channel向服务器声明一个队列,设置durable为true,则当rabbitmq 服务器重启时,队列不会丢失
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
System.out.println("WorkerB Waiting for messages");
//每次从队列获取的message数量
channel.basicQos(2);
// prefetchCount maximum number of messages that the server will deliver, 0 if unlimited
final 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("WorkerB Received :" + message );
try {
doWork(message);
}catch (Exception e){
channel.abort();
}finally {
System.out.println("WorkerB Done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck=false;
//消息消费完成确认 不自动 ack 非自动ack
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
private static void doWork(String task) {
try {
Thread.sleep(3000);// 暂停2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意:
1 、任务消费者A和B 可以共同消费处理任务生产者(NewTask)发布的消息。消费者A和B的qos可以根据服务器性能的不同设置不同的qos,例如本例中消费者A的qos设置为了1,而消费者B的qos设置为了2 。
2 、consumer中有个重要的动作就是设置autoAck为false (默认为true),表示需要消费者程序手动ack。注意一旦consumer将autoAck设置为false之后,一定要记得处理完消息之后,向服务器发送确认消息,否则服务器将会一直转发该消息。当设置了autoAck为false,若消费者A意外宕机,处理的消息还没有ack回去,则rabbitmq服务器会将消息转给消费者B。
3、生产者发布消息时,设置了一个参数durable为true。这样做的好处是,当这样设置之后,服务器收到消息后就会立刻将消息写入到硬盘,就可以防止突然服务器挂掉,而引起的数据丢失了。 但是服务器如果刚收到消息,还没来得及写入到硬盘,就挂掉了,这样还是无法避免消息的丢失。
4、 在本例(工作队列模型)中,我们还是未看到exchange的身影,这一点同上一种模型——收发模型是一样的。 实际上,在本例(工作队列模型)中,是存在exchange的,exchange的名称为空字符串"",类型为direct ,同样对我们是透明的,也被我们所忽略了。
实际上,我们介绍rabbitmq的收发模型和工作队列模型所采用的exchange就是图中缩圈出的这种。
关于exchange的更多内容会在我们后面的篇章中讲到。