一.消息丢失可能的原因及其解决方法
1.worker未完成工作便崩溃、关闭、断连了
问题来源: 工作任务消息发送给工人(worker),但是工人(worker)尚未完成全部工作任务,便死掉了。该工人接收到的,尚未完成的和尚未进行的工作任务消息,将全部丢失。
解决方法: 使用RabbitMQ的消息确认机制。
- 将auto-ack参数设置为false
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
false, // auto-ack:保证worker断连、关闭等情况下,消息不丢失,将auto-ack设置为false
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
- 在完成任务后,回复ack
d.Ack(false)//保证worker断连、关闭等情况下,消息不丢失,回复ack。消息确认机制。
2.服务器崩溃、断连、关闭
问题来源: RabbitMQ服务器崩溃、断连、关闭,将会导致消息丢失。
解决方法: 需要将队列和消息都标记为持久性。
q, err := ch.QueueDeclare(
"task_queue", // name
true, // durable:保证rabbitMQ服务器退出或者崩溃时,消息不丢失,将该参数设置为true
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,//保证rabbitMQ服务器退出或者崩溃时,消息不丢失,将该模式设置为amqp.Persistent
ContentType: "text/plain",
Body: []byte(body),
})
3.消息尚未保存到磁盘中,RabbitMQ服务器崩溃、断连、关闭
可以使用 Publisher Confirms解决。
二.消息分发给worker的方式
Fair dispatch: 默认情况下,消息将平均分发给每一个worker。不管任务有没有处理完,有没有回复ack,RabbitMQ服务器均会发送消息给该worker。但是,每个worker处理任务的快慢不同,且任务的工作量也不相同,这就会导致,部分worker非常忙,部分worker非常闲。
为了更高效得利用worker资源,可以设置prefetch count为1,当worker回复ack以后,RabbitMQ才能够分发消息给这个worker。
err = ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
三.代码示例
package main
import (
"bytes"
"github.com/streadway/amqp"
"log"
"time"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
q, err := ch.QueueDeclare(
"task_queue", // name
true, // durable:保证rabbitMQ服务器退出或者崩溃时,消息不丢失,将该参数设置为true
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
err = ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
failOnError(err, "Failed to set QoS")
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
false, // auto-ack:保证worker断连、关闭等情况下,消息不丢失,将auto-ack设置为false
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
forever := make(chan bool)
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
dotCount := bytes.Count(d.Body, []byte("."))
t := time.Duration(dotCount)
time.Sleep(t * time.Second)
log.Printf("Done")
d.Ack(false)//保证worker断连、关闭等情况下,消息不丢失,回复ack。消息确认机制。
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
package main
import (
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
q, err := ch.QueueDeclare(
"task_queue", // name
true, // durable:保证rabbitMQ服务器退出或者崩溃时,消息不丢失,将该参数设置为true
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
body := bodyFrom(os.Args)
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,//保证rabbitMQ服务器退出或者崩溃时,消息不丢失,将该模式设置为amqp.Persistent
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", body)
}
func bodyFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "hello"
} else {
s = strings.Join(args[1:], " ")
}
return s
}