work queue
主要思想: 避免立即执行资源密集型任务,而不得不等待它完成。安排任务在之后执行,我们将任务封装为消息并放送到队列。而后台的工作进程从消息队列中读取消息并执行任务。当有多个工作线程时,这些线程将一起处理这些任务。
这个概念在 Web 应用程序中特别有用,在这些应用程序中,无法在短暂的 HTTP 请求窗口中处理复杂的任务。
work queue适合在集群环境中做异步处理,能最大程度发挥每一台服务器的性能
生产者代码, 简单的往队列发送20条消息
package main
import (
"fmt"
"github.com/streadway/amqp"
"log"
"rabbit/utils"
)
func main() {
ch, _ := utils.GetRabbitMQChannel()
q, _ := ch.QueueDeclare(
"hello", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
for i := 0; i < 20; i++ {
msg := fmt.Sprintf("%d号消息", i+1)
_ = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: []byte(msg),
})
log.Printf(" Sent %s", msg)
}
}
消费者代码, 开启三个goroutine作为worker从队列里读数据
package main
import (
"github.com/streadway/amqp"
"log"
"rabbit/utils"
"sync"
)
func main() {
ch, _ := utils.GetRabbitMQChannel()
q, _ := ch.QueueDeclare(
"hello", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go work(i+1, q, ch, &wg)
}
wg.Wait()
}
func work(id int, q amqp.Queue, ch *amqp.Channel, wg *sync.WaitGroup) {
defer wg.Done()
msgs, _ := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
forever := make(chan bool)
go func() {
for d := range msgs {
log.Printf("[%d号worker]Received a message: %s", id, d.Body)
}
}()
<-forever
}
运行结果
F:\go_workspace\mq\rabbit\work-queues\workers>go run worker.go
2022/04/18 22:10:12 [3号worker]Received a message: 1号消息
2022/04/18 22:10:12 [1号worker]Received a message: 2号消息
2022/04/18 22:10:12 [1号worker]Received a message: 5号消息
2022/04/18 22:10:12 [3号worker]Received a message: 4号消息
2022/04/18 22:10:12 [3号worker]Received a message: 7号消息
2022/04/18 22:10:12 [3号worker]Received a message: 10号消息
2022/04/18 22:10:12 [2号worker]Received a message: 3号消息
2022/04/18 22:10:12 [2号worker]Received a message: 6号消息
2022/04/18 22:10:12 [2号worker]Received a message: 9号消息
2022/04/18 22:10:12 [1号worker]Received a message: 8号消息
2022/04/18 22:10:12 [1号worker]Received a message: 11号消息
2022/04/18 22:10:12 [2号worker]Received a message: 12号消息
2022/04/18 22:10:12 [2号worker]Received a message: 15号消息
2022/04/18 22:10:12 [3号worker]Received a message: 13号消息
2022/04/18 22:10:12 [1号worker]Received a message: 14号消息
2022/04/18 22:10:12 [1号worker]Received a message: 17号消息
2022/04/18 22:10:12 [3号worker]Received a message: 16号消息
2022/04/18 22:10:12 [3号worker]Received a message: 19号消息
2022/04/18 22:10:12 [2号worker]Received a message: 18号消息
2022/04/18 22:10:12 [1号worker]Received a message: 20号消息
仔细观察发现,
消息号n%3 等于1的都是3号worker处理的;
消息号n%3 等于2的都是1号worker处理的;
消息号n%3 等于0的都是2号worker处理的。
就是轮询机制
消息确认[Message acknowledgment]
因为RabbitMQ一旦向消费者传递一条消息,便立即将消息标记为删除。消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个任务只完成了部分突然挂掉,这样会导致我们丢失正在处理的消息,以及后续发送给该消费者的消息。
消息应答的方法:
- 自动确认
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
false, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
- 手动确认
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
d.Ack(false) //不批量确认
}
}()
推荐使用手动确认,并其不批量确认
消息自动重新入队
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或TCP连接丢失), 导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将其重新加入到队列中,等待其他消费者来处理。
消息持久化[message durability]
首先,我们需要确保队列能够在 RabbitMQ 节点重启后继续存在。
生产者和消费者的队列声明都需要设置durable: true
q, err := ch.QueueDeclare(
"hello", // name
true, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
其次, 我们需要将我们的消息标记为持久 - 通过使用 amqp.Persistent 选项 amqp.Publishing 采取。
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false,
amqp.Publishing {
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: []byte(body),
})
注意:rabbitmq中的队列是不允许同名但属性不同的
公平分发[fair dispatch]
轮询分发并不算是公平的。例如在有两个worker的情况下,当所有奇数消息很重而偶数消息都很轻时,一个worker会一直很忙,另一个worker几乎不会做任何工作。
消费者添加代码
err = ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
failOnError(err, "Failed to set QoS")
prefechCount = 1表示:在处理并确认前一个消息之前,不要向worker发送新消息。相反,它将把它分派给下一个不忙的worker。
发布确认
单个确认发布
批量确认
异步批量确认