引言
在上一节【RabbitMQ(一)】docker-compose安装rabbitmq及简单使用Hello World中,我们介绍了Rabbitmq的安装和简单操作。达到了生产者发送一个Hello World之后,消费者便能接收到一个Hello World的效果。
- 如果有多个消费者同时在接收消息,消息会如何分发呢?
- 消费者在处理消息的时候宕机了,那这个消息怎么办呢?
- rabbitmq宕机了,其中的队列和消息怎么办?
- 两个消费者情况下,消息分发不合理,一个特忙,一个特闲怎么办?
带着这些问题,开始RabbitMQ第二节的学习内容,队列的使用及策略
循环调度
使用任务队列的优点之一是能够轻松并行化工作。如果我们的工作正在积压,我们可以增加更多的工人,这样就可以轻松扩展。
这里我们利用上一节中consumer.go开启两个consumer在准备接收消息,然后不断运行producer.go,会发现结果是怎样的呢?
代码部分就不做展示运行了,不难发现两个consumer是一次循环接收到了消息,也就是第一个消息被consumer1接收,第二个消息便会被consumer2接收,依次循环下去。也可以开启3个consumer甚至更多看一样,结果都是一样的。
这样我们可以得出结果:
默认情况下,RabbitMQ将按顺序将每个消息发送给下一个消费者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为轮询
。
消息确认
consumer
完成任务可能需要耗费几秒钟,如果一个consumer
在任务执行过程中宕机了该怎么办呢?我们当前的代码中,RabbitMQ一旦向消费者传递了一条消息,便立即将其标记为删除。在这种情况下,如果你终止一个consumer
那么你就可能会丢失这个任务,我们还将丢失所有已经交付给这个consumer
的尚未处理的消息。
我们不想丢失任何消息,如果一个consumer
意外宕机了,那么我们希望将任务交付给其他consumer
来处理。
为了确保消息永不丢失,RabbitMQ支持 消息确认。消费者发送回一个确认(acknowledgement),以告知RabbitMQ已经接收,处理了特定的消息,并且RabbitMQ可以自由删除它。
如果使用者在不发送确认的情况下死亡(其通道已关闭,连接已关闭或TCP连接丢失),RabbitMQ将得知消息未完全处理,并将对其重新排队。如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,您可以确保即使工人偶尔死亡也不会丢失任何消息。
没有任何消息超时;RabbitMQ将在消费者死亡时重新传递消息。即使处理一条消息需要很长时间也没关系。
手动确认模式
在这里,我们将使用手动消息确认,方法是为“auto-ack”参数传递一个false,然后在完成任务后,使用d.Ack(false)从consumer
发送一个正确的确认(这将确认一次传递)。
将第一节中的consumer.go在接收消息时由自动消息确认模式切换成手动传递消息确认,完整代码如下:
package main
import (
"log"
"time"
"github.com/streadway/amqp"
)
func main() {
// 建立连接
conn, err := amqp.Dial("amqp://root:[email protected]:5672/")
if err != nil {
log.Fatalf("%s", "Failed to connect to RabbitMQ", err)
return
}
defer conn.Close()
// 获取channel
ch, err := conn.Channel()
if err != nil {
log.Fatalf("%s", "Failed to open a channel", err)
return
}
defer ch.Close()
// 声明队列
//请注意,我们也在这里声明队列。因为我们可能在发布者之前启动使用者,所以我们希望在尝试使用队列中的消息之前确保队列存在。
q, err := ch.QueueDeclare(
"hello", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
if err != nil {
log.Fatalf("%s", "Failed to declare a queue", err)
return
}
// 获取接收消息的Delivery通道
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
false,