RabbitMQ------简单队列模式以及工作队列模式以及消息应答的方式(三)
创建maven项目,导入依赖
<!-- 指定jdk编译版本-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.8</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- rabbitmq依赖客户端 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
<!--操作文件流的一个依赖-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
通过信道连接交换机、再连接队列。
简单模式,可以采用默认交换机,省略选择交换机,直接连接队列。
生产者代码:
1.首先需要创建连接工厂,ConnectionFactory
2.配置IP、用户名、密码
3.创建连接
4.获取信道、或者说创建信道
5.获取队列,或者说产生队列
6.发送消息
/**
* 生产者
*/
public class Producer {
//队列名称
public static final String QUEUE_NAME = "hello";
//发消息,使用main函数
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//工厂IP,连接RabbitMQ队列
connectionFactory.setHost("192.168.200.129");
//设置用户名
connectionFactory.setUsername("admin");
//设置密码
connectionFactory.setPassword("123");
//创建连接
Connection connection = connectionFactory.newConnection();
//一个连接中有多个信道,信道才是发消息的
//获取信道
Channel channel = connection.createChannel();
//产生一个队列 声明一个队列
//参数说明
// 1.队列名称,
// 2.队列是否需要保存消息(持久化存储到磁盘),默认存储在内存中,不持久化,
// 3.是否需要排他(只共一个消费者消费),是否进行消息共享,true可以多个消费者,false不允许多个消费者消费
// 4.是否自动删除,最后一个消费者断开连接之后,该队列是否自动删除,true自动删除,fasle不自动删除
// 5.传递一些队列的参数(延迟消息,死信消息)
// (String queue, boolean durable, boolean exclusive, boolean autoDelete,
// Map<String, Object> arguments) throws IOException;
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发消息
String message = "hello world";
//void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
//1.发送到哪个交换机
//2.路由的key值是哪个,本质上是队列名称
//3.其他参数信息
//4.发送的消息的消息体 byte
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
}
}
其中队列的配置参数说明:
//参数说明
// 1.队列名称,
// 2.队列是否需要保存消息(持久化存储到磁盘),默认存储在内存中,不持久化,
// 3.是否需要排他(只共一个消费者消费),是否进行消息共享,true可以多个消费者,false不允许多个消费者消费
// 4.是否自动删除,最后一个消费者断开连接之后,该队列是否自动删除,true自动删除,fasle不自动删除
// 5.传递一些队列的参数(延迟消息,死信消息),没有就为null
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) throws IOException;
以及发消息的参数说明
/void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
//1.发送到哪个交换机
//2.路由的key值是哪个,本质上是队列名称
//3.其他参数信息
//4.发送的消息的消息体 byte类型
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
消费者消费消息:
1.首先需要创建连接工厂,ConnectionFactory
2.配置IP、用户名、密码
3.创建连接
4.获取信道
5.获取消息
/**
* 消费者
*/
public class Consumer {
//队列名称
public static final String QUEUE_NAME = "hello";
//发消息,使用main函数
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//工厂IP,连接RabbitMQ队列
connectionFactory.setHost("192.168.200.129");
//设置用户名
connectionFactory.setUsername("admin");
//设置密码
connectionFactory.setPassword("123");
//创建连接
Connection connection = connectionFactory.newConnection();
//一个连接中有多个信道,信道才是发消息的
//获取信道
Channel channel = connection.createChannel();
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功后,是否要自动应答 true,false手动应答
* 3.消费者成功消费的回调
* 4.消费者取消消费的回调
*/
//消息消费的时候如何处理消息
DeliverCallback deliverCallback = (consumerTag,message)->{
//消息有消息头、消息体
System.out.println(message.getBody());
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag->{
System.out.println("消息消费被终断");
};
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
// String consume = channel.basicConsume(QUEUE_NAME,true,
// (consumerTag,message)->{
// System.out.println(message);
// },
// consumerTag->{
// System.out.println(consumerTag);
// });
}
}
工作队列又成任务队列,主要思想:避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。把任务封装为消息并将其发送到队列,在后台运行的工作进程将弹出任务,并最终执行作业。当有工作线程时,这些工作线程(多个消费者)将一起处理这些任务。
大致意思就是:生产者发送大量消息到消息队列中,多个消费者轮询消费消息。
注意:这种模式,一个消息只能被消费一次,不能被消费多次,消费多次,造成重复消费。
特点:轮询分发消息。此时多个消费者是竞争关系。
将建立连接、获取信道,抽取成工具类。
public class RabbitMqUtiles {
public static Channel getChannel() throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//工厂IP,连接RabbitMQ队列
connectionFactory.setHost("192.168.200.129");
//设置用户名
connectionFactory.setUsername("admin");
//设置密码
connectionFactory.setPassword("123");
//创建连接
Connection connection = connectionFactory.newConnection();
//一个连接中有多个信道,信道才是发消息的
//获取信道
Channel channel = connection.createChannel();
return channel;
}
}
IDEA中允许一段代码多个线程运转。只需要将allow parallel run 打钩即可。
消费者:
将work1运行。
/**
* 这是一个工作线程,相当于一个消费者
*/
public class Wrok1 {
//队列名称
public static final String QUEUE_NAME = "hello";
//发消息,使用main函数
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtiles.getChannel();
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功后,是否要自动应答 true,false手动应答
* 3.消费者成功消费的回调
* 4.消费者取消消费的回调
*/
//消息消费的时候如何处理消息
DeliverCallback deliverCallback = (consumerTag, message)->{
//消息有消息头、消息体
System.out.println("接收到的消息"+message.getBody());
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag->{
System.out.println("消息消费被终断");
};
System.out.println("C1等待接受消息....");
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
将C1等待接受消息改为C2,再次运行。模拟多线程的情况。
生产者:
public class Task {
//队列名称
public static final String QUEUE_NAME = "hello";
//发消息,使用main函数
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtiles.getChannel();
//发消息
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
StringBuffer message = new StringBuffer("hello world");
for (int i = 0 ;i<100;i++){
message.append(i);
channel.basicPublish("", QUEUE_NAME, null, message.toString().getBytes());
System.out.println("消息发送完毕"+message);
}
}
}
结果,消息被work1消费,就不会被work2消费,会轮询消费
如果消费者接受到一个消息,需要进行长任务处理,但是完成部分后挂了,此时就会消息丢失,以及后续发送给这个消费者消息,因为它也无法接收到。
为了解决这一问题,rabbitmq引入消费者应答机制。
意味着:消费者在接收到消息并且处理该消息后,告诉rabbitmq该消息已被处理,rabbitmq才会将该消息删除。
应答又分为手动应答和否定应答。
消息发送后立即被认为已经传送成功,这种模式需要高吞吐量和数据传输安全性方面做权衡,自动应答可能会出现消息丢失的情况。因此仅限于各个方面可靠性都很高的情况下使用。
应答分为:肯定应答和否定应答
手动应答的好处可以批量应答并且减少网络阻塞。
//void basicAck(long deliveryTag, boolean multiple) throws IOException;
channel.basicAck(deliveryTag,true);
multiple的true和false代表不同意思,建议false,不进行批量应答。
true:代表批量应答channel上未应答的消息
比如:channel上有传送tag的消息5,6,7,8,当前tag是8,那么此时5-8的这些还未应答的消息都会被确认消息应答。
false:则代表不批量应答
只会应答tag=8的消息,5,6,7的这三个消息依然会被确认收到消息应答。
如果消费者由于意外导致,消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并对其重新排队。如果此时其他消费者处理,它会很快被重新分发给另一个消费者。这样即使消费者偶尔宕机,也确保消息不会丢失。
手动应答代码:
变动点:
1.将自动应答标记设为false
2.在消费消费时候处理消息时,增加手动应答
public class Wrok1 {
//队列名称
public static final String QUEUE_NAME = "hello";
//发消息,使用main函数
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtiles.getChannel();
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功后,是否要自动应答 true,false手动应答
* 3.消费者未成功消费的回调
* 4.消费者取消消费的回调
*/
//消息消费的时候如何处理消息
DeliverCallback deliverCallback = (consumerTag, message)->{
//消息有消息头、消息体
System.out.println("接收到的消息"+message.getBody());
//1.消费标记Tag
//2.是否批量应答未应答的消息,fasle只应答当前消息
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag->{
System.out.println("消息消费被终断");
};
System.out.println("C1等待接受消息....");
//设置手动应答
Boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
}
}
在消费者1、和消费者2中,分别增加线程休眠1s,和线程休眠30s。
代码结果:消费者1消费第一条信息,消费者2消费第二条信息,由于消费者2有30s的休眠时间,此时将消费者2代码停止运行,能看到结果,消费者1会马上消费第2条信息。