一.消息丢失可能的原因及其解决方法
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
}