目录
引入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
简单模式
模型
一个生产者,一个消费者

- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息
生产者代码
//创建连接工厂
//创建连接mq的连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost("10.30.126.252");
//设置端口号
connectionFactory.setPort(5672);
//设置访问虚拟主机的用户名和密码
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
//设置连接那个虚拟主机
connectionFactory.setVirtualHost("/");
//获取连接对象
Connection connection = connectionFactory.newConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//通道绑定对应消息队列
//参数1: 队列名称 如果队列不存在自动创建
//参数2: 用来定义队列特性是否要持久化 true 持久化队列 false 不持久化
//参数3: exclusive 是否独占队列 true 独占队列 false 不独占
//参数4: autoDelete: 是否在消费完成后自动删除队列 true 自动删除 false 不自动删除
//参数5: 额外附加参数
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
String msg = "My first message";
//发布消息
//参数1: 交换机名称(简单模式下为空) 参数2:队列名称 参数3:传递息额外设置 参数4:消息的具体内容
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
channel.close();
connection.close();

消费者代码
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("10.30.126.252");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
//创建通道
Channel channel = connection.createChannel();
//通道绑定队列:与生产端一致
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("取出消息:===>" + new String(body));
}
});
}
}
结果

work queues模式
模型
Work queues,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

- P:生产者:任务的发布者
- C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
- C2:消费者-2:领取任务并完成任务,假设完成速度快
生产者代码
public class Producer {
public static final String QUEUE_NAME = "my_second_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
//创建连接mq的连接工厂对象
Connection connection = RabbitMQUtils.getConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//通道绑定对应消息队列
//参数1: 队列名称 如果队列不存在自动创建
//参数2: 用来定义队列特性是否要持久化 true 持久化队列 false 不持久化
//参数3: exclusive 是否独占队列 true 独占队列 false 不独占
//参数4: autoDelete: 是否在消费完成后自动删除队列 true 自动删除 false 不自动删除
//参数5: 额外附加参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
String msg = "My second message";
//发布消息
//参数1: 交换机名称(简单模式下为空) 参数2:队列名称 参数3:传递息额外设置 参数4:消息的具体内容
for (int i = 0; i < 20; i++) {
channel.basicPublish("", QUEUE_NAME, null, (i+1+"--"+msg).getBytes());
}
channel.close();
connection.close();
}
}
消费者代码
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//通道绑定队列:与生产端一致
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1取出消息:===>" + new String(body));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//通道绑定队列:与生产端一致
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
@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("消费者2取出消息:===>" + new String(body));
}
});
}
}
结果
消费者1

消费者2

总结:默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。
如何实现快的多消费 慢的少消费???
消息的自动确认机制
如果希望实现上面需求即快的多消费,慢的少消费,则需要设置rabbitmq的自动确认机制。
ack定义:如果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持消息确定-ACK。
ACK机制是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。 如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中。如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他消费者。这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务。消息永远不会从RabbitMQ中删除,只有当消费者正确发送ACK反馈,RabbitMQ确认收到后,消息才会从RabbitMQ服务器的数据中删除。
消息的ACK确认机制默认是打开的。
模型图
要解决上面问题需要解决下面问题
-
消费者不能一次拿到多个消息(默认情况下,消息是一次性投递的channel中的)
对消费者作为改变
//会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉!!!!!
channel.basicQos(1);
//关闭自动确认消费,执行手动确认
channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1取出消息:===>" + new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
上面的basicQos告诉mq不要一次性分派多个消息,每次一个一个的给,如果消费者某个消息没有ack(确认),自己进行阻塞。基于这点我们需要确认机制改为“手动确认”,即保每次只分发一个的同时,消费快的先“ack”,再取下一个。
结果如下


-
疑问1,如果不设置channel.basicQos(1),直接设置手动会怎么样,带着这样的疑问我们尝试一下,


可以看到依然是平均分派。
-
疑问2:如果设置channel.basicQos(1),但是采用自动确认会怎么样


可以看到情况同上,虽然channel每次从队列中获取一个消息,但是一旦获取之后自动ack,则消费者1 和消费者2 有同样的竞争机会。
发布订阅模式
模型
fanout又称广播

在广播模式下,消息发送流程是这样的:
-
可以有多个消费者
-
每个消费者有自己的queue(队列)
-
每个队列都要绑定到Exchange(交换机)
-
生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
-
交换机把消息发送给绑定过的所有队列
-
队列的消费者都能拿到消息。实现一条消息被多个消费者消费
生产者代码
public class Producer {
public static final String EXCHANGE_NAME = "fanoutExchange";
public static final String EXCHANGE_TYPE = "fanout";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(fanout广播模式)
channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
String msg = "My third message----fanout";
//发布消息
//参数1: 交换机名称 参数2:队列名称 参数3:传递息额外设置 参数4:消息的具体内容
channel.basicPublish(EXCHANGE_NAME, "", null, (msg).getBytes());
channel.close();
connection.close();
}
}
消费者代码
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(fanout广播模式)
channel.exchangeDeclare(Producer.EXCHANGE_NAME,Producer.EXCHANGE_TYPE);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机 [参数1:临时队列,参数2:交换机,参数3:路由]
channel.queueBind(queue, Producer.EXCHANGE_NAME, "");
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1取出消息:===>" + new String(body));
}
});
}
}
消费者1生成的临时队列

消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(fanout广播模式)
channel.exchangeDeclare(Producer.EXCHANGE_NAME, Producer.EXCHANGE_TYPE);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机 [参数1:临时队列,参数2:交换机,参数3:路由]
channel.queueBind(queue, Producer.EXCHANGE_NAME, "");
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2取出消息:===>" + new String(body));
}
});
}
}
消费者2创建的临时队列

结果


路由模式
Routing 之订阅模型-Direct(直连)
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
-
队列与交换机的绑定,不能是任意绑定了,而是要指定一个
RoutingKey(路由key) -
消息的发送方在 向 Exchange发送消息时,也必须指定消息的
RoutingKey。 -
Exchange不再把消息交给每一个绑定的队列,而是根据消息的
Routing Key进行判断,只有队列的Routingkey与消息的Routing key完全一致,才会接收到消息
模型

图解:
-
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
-
X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
-
C1:消费者,其所在队列指定了需要routing key 为 error 的消息
-
C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
生产者代码
public class Producer {
public static final String EXCHANGE_NAME = "routeExchange";
public static final String EXCHANGE_TYPE = "direct";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(路由模式)
channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
String msg = "My fourth message----route";
//发布消息[参数1:交换机名字,参数2:路由名字,参数3:消息内容]
String key = "error";
channel.basicPublish(EXCHANGE_NAME, key, null, ("发送给指定路由" + key + "的消息").getBytes());
channel.close();
connection.close();
}
}
消费者代码
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(fanout广播模式)
channel.exchangeDeclare(Producer.EXCHANGE_NAME,Producer.EXCHANGE_TYPE);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
channel.queueBind(queue, Producer.EXCHANGE_NAME, "info");
channel.queueBind(queue, Producer.EXCHANGE_NAME, "error");
channel.queueBind(queue, Producer.EXCHANGE_NAME, "warn");
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1取出消息:===>" + new String(body));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(route广播模式)
channel.exchangeDeclare(Producer.EXCHANGE_NAME, Producer.EXCHANGE_TYPE);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
channel.queueBind(queue, Producer.EXCHANGE_NAME, "error");
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2取出消息:===>" + new String(body));
}
});
}
}
结果


主题模式(Topic)
Routing 之订阅模型-Topic
Topic 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列
只不过Topic类型 Exchange 可以让队列在绑定Routing key 的时候使用通配符!
这种模型 Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

# 统配符
* (star) can substitute for exactly one word. 匹配不多不少恰好1个词
# (hash) can substitute for zero or more words. 匹配一个或多个词
# 如:
audit.# 匹配audit.irs.corporate或者 audit.irs 等
audit.* 只能匹配 audit.irs
生产者代码
public class Producer {
public static final String EXCHANGE_NAME = "topicExchange";
public static final String EXCHANGE_TYPE = "topic";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(路由模式)
channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
String msg = "My fifth message----topic";
//发布消息[参数1:交换机名字,参数2:路由名字,参数3:消息内容]
//使用动态路由
String key = "user.save";
channel.basicPublish(EXCHANGE_NAME, key, null, ("发送给指定路由" + key + "的消息:" + msg).getBytes());
channel.close();
connection.close();
}
}
消费者代码
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(fanout广播模式)
channel.exchangeDeclare(Producer.EXCHANGE_NAME,Producer.EXCHANGE_TYPE);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
channel.queueBind(queue, Producer.EXCHANGE_NAME, "user.*");
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1取出消息:===>" + new String(body));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
Connection connection = RabbitMQUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
//参数1: 交换机名称
//参数2: 交换机类型(route广播模式)
channel.exchangeDeclare(Producer.EXCHANGE_NAME, Producer.EXCHANGE_TYPE);
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
channel.queueBind(queue, Producer.EXCHANGE_NAME, "user.#");
//获取消息
//参数1: 消费那个队列的消息 队列名称
//参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
//参数3: 消费时的回调接口
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2取出消息:===>" + new String(body));
}
});
}
}
结果


如果发送

则只有消费者2能收到消息


本文详细介绍了RabbitMQ的简单模式、workqueues模式、发布订阅模式和路由模式,以及消息的自动确认机制。在简单模式中,一个生产者向一个消息队列发送消息,一个消费者接收。workqueues模式适用于处理耗时任务,允许多个消费者共同消费队列消息。发布订阅模式中,消息会被广播给所有订阅的消费者。路由模式通过交换机和路由键将消息路由到特定队列。消息的自动确认机制保证了消息在正确处理后才会被删除。此外,通过关闭自动确认,可以实现快的消费者多消费,慢的消费者少消费。
1146

被折叠的 条评论
为什么被折叠?



