一、模型图
Work queues,也被称为(Task queues),任务模型。
当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。
此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。
队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的
- P:生产者
- C1:消费者1,获取消息,处理任务,假设速度较慢
- C2:消费者2,获取消息,处理任务,假设速度快
二、rabbitmq的消息确认机制(ACK)
- 默认情况下,RabbitMQ 会按顺序将每条消息发送给下一个消费者。平均而言,每个消费者都会收到相同数量的消息。
- 消息一旦被消费者接收,无论是否消费成功,队列中的消息都会被删除。如果其中一个消费者处理遇到异常,会导致未被处理的消息丢失
- 针对上述情况,rabbitMQ有一个消息确认机制。消费者获取消息后,会向rebbitMQ发送ACK,告知消息已经被接收
消息确认机制(ACK)分两种:
- 自动ACK:消息一旦被接受,消费者自动ACK
- 手动ACK:消息接收后,不会发送ACK,需要手动确认
由于消费者消费能力不同,希望能者多劳
三、代码实现
1、工具类
package com.java.study.rabbitmq.utils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitMQUtils {
private static ConnectionFactory connectionFactory;
static{
//1.创建连接mq的连接工厂对象
connectionFactory = new ConnectionFactory();
//2.设置连接rabbitmq主机
connectionFactory.setHost("192.168.8.222");
//3.设置端口号
connectionFactory.setPort(5672);
//4.设置连接那个虚拟主机
connectionFactory.setVirtualHost("/ems");
//5.设置访问虚拟主机的用户名和密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
}
public static Connection getConnections(){
try{
//6.获取连接
return connectionFactory.newConnection();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void closeConnectionAndChannel(Channel channel,Connection connection){
try {
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、生产者
package com.java.study.rabbitmq.workqueue;
import com.java.study.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
/**
* 生产者 发送 消息
*/
public class Producer {
private final static String QUEUE_NAME = "work";
@Test
public void sendMessages() throws Exception {
//6.获取连接
Connection connection = RabbitMQUtils.getConnections();
//7.获取通道
Channel channel = connection.createChannel();
//8.通道绑定对应的消息队列
//参数1:queue 队列名称,在队列不存在的情况下,自动创建
//参数2:durable 用来定义队列的特性是否要持久化(消息不会持久化) true:持久化 false:不持久化
//参数3:exclusive 是否独占队列 true:独占队列,false:不独占队列
//参数4:autoDelete 是否在消费彻底完成(消费者停掉)后自动删除队列 true:自动删除,false:不自动删除
//参数5:arguments 额外附加参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
String message = "hello work queue";
//9.发布消息
//参数1:exchange 交换机名称
//参数2:routingKey 队列名称(由这里具体指定发送到具体哪个队列中)
//参数3:props 传递消息额外设置(由这里设置消息是否持久化,MessageProperties.PERSISTENT_TEXT_PLAIN:重启不会消失)
//参数4:body 消息的具体内容
for (int i = 1; i <= 10; i++) {
channel.basicPublish("", QUEUE_NAME, null, (message+"====>:我是消息"+i).getBytes());
}
//关闭连接
RabbitMQUtils.closeConnectionAndChannel(channel, connection);
}
}
3、消费者1
package com.java.study.rabbitmq.workqueue;
import com.java.study.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
private final static String QUEUE_NAME = "work";
public static void main(String[] args) throws Exception {
//6.获取连接
Connection connection = RabbitMQUtils.getConnections();
//7.获取通道
Channel channel = connection.createChannel();
//每个通道只能消费一个消息,在手动ACK下生效
channel.basicQos(1);
//8.通道绑定对应的消息队列
//参数1:queue 队列名称,在队列不存在的情况下,自动创建
//参数2:durable 用来定义队列的特性是否要持久化(消息不会持久化) true:持久化 false:不持久化
//参数3:exclusive 是否独占队列 true:独占队列,false:不独占队列
//参数4:autoDelete 是否在消费彻底完成(消费者停掉)后自动删除队列 true:自动删除,false:不自动删除
//参数5:arguments 额外附加参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//9.消费消息
//参数1:队列名称
//参数2:开启消息的自动确认机制
//参数3:消费消息时的回调接口
channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {
//参数1:consumerTag 消费者标签,用来标识消费者的,在
//参数2:envelope 信封
//参数3:properties 消息属性
//参数4:body 消息队列中取出的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1:" + new String(body));
//参数1:手动确认消息标识
//参数2:是否开启多个消息同时确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
4、消费者2
package com.java.study.rabbitmq.workqueue;
import com.java.study.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
private final static String QUEUE_NAME = "work";
public static void main(String[] args) throws Exception {
//6.获取连接
Connection connection = RabbitMQUtils.getConnections();
//7.获取通道
Channel channel = connection.createChannel();
//每个通道只能消费一个消息,在手动ACK下生效
channel.basicQos(1);
//8.通道绑定对应的消息队列
//参数1:queue 队列名称,在队列不存在的情况下,自动创建
//参数2:durable 用来定义队列的特性是否要持久化(消息不会持久化) true:持久化 false:不持久化
//参数3:exclusive 是否独占队列 true:独占队列,false:不独占队列
//参数4:autoDelete 是否在消费彻底完成(消费者停掉)后自动删除队列 true:自动删除,false:不自动删除
//参数5:arguments 额外附加参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//9.消费消息
//参数1:队列名称
//参数2:消息的自动确认机制 true:消费者自动向rabbitmq确认消息消费(拿到即确认,遇到异常消息丢失) false:手动确认
//参数3:消费消息时的回调接口
channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {
//参数1:consumerTag 消费者标签,用来标识消费者的,在
//参数2:envelope 信封
//参数3:properties 消息属性
//参数4:body 消息队列中取出的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2:" + new String(body));
//参数1:手动确认消息标识
//参数2:是否开启多个消息同时确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}