首先先在rabbitmq
提供的管理端界面上创建一个vhost
便于测试。
通过以上几个步骤就已经创建了一个名为’rabbitmqstudy’的vhost
,但该vhost
只允许guest
账户访问,我们再通过RabbitMQ提供的管理端界面创建一个用户并为它绑定’rabbitmqstudy’这个vhost
。
vhost
与user
添加完成。
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.7.2version>
dependency>
P代表生产者,C代表消费者,中间的红色区域代表队列,队列只受主机内存和磁盘的限制,它本质上是一个大的消息缓冲区。
直连模型队列只能对应一个消费者,是Rabbitmq中最简单的一种模型。
获取RabbitMQ的Connection对象:
@Configuration
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory() {
ConnectionFactory conn = new ConnectionFactory();
conn.setHost("192.168.95.130");
conn.setPort(5672);
conn.setVirtualHost("/rabbitmqstudy");
conn.setUsername("twz");
conn.setPassword("123456");
return conn;
}
@Bean
public Connection connection(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
return connectionFactory.newConnection();
}
}
@Autowired
private Connection connection;
@Test
public void testProducer() throws IOException, TimeoutException {
Channel channel = connection.createChannel(); // 从连接中获取一个信道channel
// 在channel中声明一个队列
// param1: 队列名
// param2: 【队列】是否需要持久化,队列,队列,队列!
// 如果是false,则当重启rabbitmq服务时,该队列会丢失
// 如果是true,则队列不会丢失
// 至于消息是否持久化是在发布消息的api中通过参数设置,这里只是声明队列的性质
// param3: 是否是独占队列
// 独占队列意味着该队列只被该channel绑定,不能再被其余的channel绑定
// 如果绑定会直接抛出错误,在大多数情况下都是false即非独占
// param4: 队列中的消息被取完后是否删除队列
// 只有在消费者与队列断开连接之后,该队列才会删除
// param5: 参数
channel.queueDeclare("hello", true, false, false, null);
// 发布消息,必须要先声明队列才能发布消息
// param1: 交换器,helloword模型中没有交换器
// param2: 队列名 或 routingKey,这里指定的是队列名
// param3: 该消息的属性,比如MessageProperties.PERSISTENT_TEXT_PLAIN就是让该消息持久化,消息持久化的时机是rabbitmq关闭
// param4: 消息体
channel.basicPublish("", "hello", null, "这是发布的一个消息".getBytes());
channel.close();
connection.close();
}
在运行完成后可以看到多出来了一个名字是holle的队列,队列处于idle空闲状态,队列中的消息总数是1。
@Test
public void testBasicConsumer() throws IOException {
Channel channel = connection.createChannel();
// 消费消息,消费消息不需要声明队列
// param1: 队列名
// param2: 是否开启消息的自动确认机制
// 获取消息之后,自动确认该消息已被获取,在后台能看到数据
// param3: 消息回调接口
channel.basicConsume("hello", 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));
}
});
}
可以到消费之后队列中的消息就被全部消费掉了。
由于队列只能对应一个消费者,如果消息处理过慢,可能会导致消息在队列中堆积。
消息确认机制的目的是防止消息丢失,它的原理和TCP可靠传输原理完全一样。
以下摘至官方文档:
完成一项任务可能只需要几秒钟。您可能想知道,如果其中一个消费者启动了一个很长的任务,并且只完成了部分任务而死亡(消费者会尽可能多拿取任务再依次去处理),会发生什么情况。在我们目前的代码中,一旦RabbitMQ向消费者发送消息,它就会立即标记该消息为删除。在本例中,如果您杀死一个worker,我们将丢失它正在处理的消息。我们还将丢失所有已发送到这个特定工作器但尚未处理的消息。
但我们不想失去任何任务。如果一个工人死了,我们希望把任务交给另一个工人。
为了确保消息不会丢失,RabbitMQ支持消息确认。消费者发送回确认信息,告知RabbitMQ已经接收并处理了特定的消息,RabbitMQ可以删除该消息。
如果一个消费者在没有发送ack的情况下死亡(通道关闭,连接关闭,或者TCP连接丢失),RabbitMQ会理解消息没有被完全处理,并将其重新排队。如果同时有其他消费者在线,它会很快地将它重新发送给另一个消费者。这样你就可以确保没有信息丢失,即使这些工人偶尔会死亡。
当消费者死亡时,RabbitMQ会重新发送消息。即使处理一条消息要花费非常非常长的时间,这也是可以的。
默认情况下,手动消息确认是打开的。在前面的例子中,我们通过autoAck=true标志显式地关闭了它们。是时候将此标志设置为false并在完成任务后从worker发送正确的确认了。
在consume那里关闭自动确认机制,然后在处理消息的回调函数那里手动确认消息:
// 手动确认消息,将自动确认设置false
channel.basicConsume("hello", false, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
// 手动确认消息
// param1: 一个标签
// param2: 是否确认多条消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
如果关闭了自动确认机制,同时处理消息后又没有手动确认消息,则消息队列中该消息会保留,因为队列会认为该消息没有被消费者安全处理。
消息确认就是一种保障数据可靠传输的安全机制。
work queue模型可以让多个消费者绑定到同一个队列,共同消费队列中的消息。队列中的消息一旦被某个消费者消费就会消失,因此确保任务不会重复执行。
work queue模型可以解决上一种模型中面临的问题。即当任务处理比较耗时时,可能生产消息的速度会远大于处理消息的速度,从而导致消息堆积,无法及时处理。
work queue模型中,消费者消费的消息,是平均分配的。默认情况下,RabbitMQ会将每条消息依次发送给下一个消费者。平均而言,每个消费者将获得相同数量的消息。这种分发消息的方式称为循环。让三个或更多的工人试试这个方法。
// 和上述代码没有任何区别,只是连续发布了多条消息而已
// 也没有任何区别,只是消费者的数量有两个,两个消费者一起消费同一个队列中的消息
每个消费者会得到相同数量的消息,但得到不是消费,得到是指消费者将消息从队列中取到了,但是还没有使用它。而有些消费者处理消息的可能很慢,而有些消费者处理的快,但是消息是平均分配的,这样就有消费者的资源被浪费掉了。
work queue模型消费者拿到的消息,是平均的,比如A、B两个消费者,A处理一个消息要3秒,B处理处理一个消息只要一瞬间。但由于A和B拿的消息是一样多,拿到了再去消费,所以会有浪费资源的情况。
而我们可以设置每个消费者每次只能拿到一条消息,这样就能实现能者多劳。
// 消费者默认会尽可能的将队列中的消息多拿,我们可以设置每次只拿一个消息
channel.basicQos(1); // 通道中一次只接受一条未被破解的消息
在前几个模型中,我们直接面向队列去发送和接收消息。现在是时候介绍Rabbit中的完整消息传递模型了。
让我们快速浏览一下我们在之前的教程中所涵盖的内容:
RabbitMQ消息模型的核心思想是:生产者从不直接向队列发送任何消息。
实际上,通常情况下,生产者甚至根本不知道消息是否会被传递到哪个队列。它只能将消息发送到交换器。而交换是一件很简单的事情。一方面它接收来自生产者的消息,另一方面它将消息推送到队列。
交换器必须确切地知道如何处理它接收到的消息。它应该被附加到一个特定的队列吗?它应该被附加到许多队列中吗?这些规则由exchange类型定义。
fanout
交换非常简单。正如您可能从名称中猜到的那样,它只是将它接收到的所有消息广播到它知道的所有队列。
# 建立连接
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
# 获取信道
channel = connection.channel()
# 为信道声明交换器,名字是log,策略是fanout
channel.exchange_declare(exchange='logs', exchange_type='fanout')
如您所见,在建立连接之后,我们声明了交换器。这个步骤是必要的,因为发布到一个不存在的交换器是被禁止的。如果没有队列绑定到交换器,消息将丢失,但这对我们来说没有问题;如果还没有消费者在听,我们可以安全地丢弃消息。
# 交换器与队列绑定
channel.queue_bind(exchange='logs', queue=queue_name)
交换器与队列的绑定在consumer完成。
public void producer() throws Exception {
Channel channel = connection.createChannel();
/*
* 声明交换器
* param1: 交换器名
* param2: 交换器策略 fanout 广播
*/
channel.exchangeDeclare("registry", "fanout");
/*
* 有交换器之后,发送消息就会给交换器了
* 生产方与队列就没有关系了
* 队列与交换器的绑定将在消费方完成
*/
channel.basicPublish("registry", "", null, "this is message".getBytes());
channel.close();
connection.close();
}
public void consumer() throws Exception {
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare("registry", "fanout");
/*
由于一个交换器可能对应多个队列,因此直接用API生成一个临时队列
临时队列在消息消费完后就会自动删除掉
创建一个临时队列,返回临时队列的标识
*/
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue, "registry", "");
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(new String(body));
}
});
channel.close();
connection.close();
}
所有与交换器绑定的队列都会收到消息,无法区别对待。
在fanout模型中,一条消息会被所有订阅的队列消费。但是在某些场景下,我们希望不同的消息被不同的队列消费(通过routingkey
),这时就要用到direct类型的交换器。
P:生产者,想交换器发送消息时,会指定消息的routingkey。
X:交换器,接收了生产者的消息后会将消息递交给与routingkey完全匹配的队列。
C1:消费者,其所在队列指定了需要routingkey为error的消息。
C2:消费者,其所在队列指定了需要routingkey为info、error、warning的消息。
因此在direct模型下
public void producer() throws Exception {
Channel channel = connection.createChannel();
// 声明交换器,策略是direct
channel.exchangeDeclare("routing_exc", "direct");
// 发布消息,并制定routingkey为info
channel.basicPublish("routing_exc", "info", null, "this is info msg".getBytes());
channel.close();
connection.close();
}
public void consumer() throws Exception {
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare("routing_exc", "direct");
// 临时队列
String queue = channel.queueDeclare().getQueue();
/*
队列与交换器绑定,一个队列可以绑定多个routingkey
*/
channel.queueBind(queue, "routing_exc", "info");
channel.queueBind(queue, "routing_exc", "warning");
channel.queueBind(queue, "routing_exc", "error");
// 消费消息
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(new String(body));
}
});
channel.close();
connection.close();
}
只能对routingkey进行完全匹配,无法使用通配符,因此direct也称为静态路由。
topic模型与direct模型类似,它们都可以根据routingkey将消息路由到不同的队列。不同的是topic模型的交换器可以使用通配符。
topic模型的routingkey一般是由一个或多个单词组成,多个单词之间以.
分隔,例如:item.insert
public void producer() throws Exception{
Channel channel = connection.createChannel();
// 声明交换器,策略是topic
channel.exchangeDeclare("topic_exc" ,"topic");
// 发送消息,routingkey是user.save
channel.basicPublish("topic_exc", "user.save", null, "this is topic msg and routing key is user.save".getBytes());
channel.close();
connection.close();
}
public void consumer() throws Exception{
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare("topic_exc", "topic");
// 临时队列
String queue = channel.queueDeclare().getQueue();
// 交换器与队列绑定,routingkey是user.*
channel.queueBind(queue, "topic_exc", "user.*");
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(new String(body));
}
});
channel.close();
connection.close();
}
完美。