1.消息ACK
如果不进行ACK,当消费端挂掉,比如channel关闭、connection关闭、TCPconnection关闭等都会使得消息丢失,而不进行重发。所以需要ACK,为了测试,关掉自动ACK选项,自己手动ACK,当接受到消息,sleep几秒再ACK
- msgs, err := ch.Consume(
- q.Name, // queue
- "", // consumer
- false, // auto-ack 不进行自动ACK
- 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 { // msgs 是一个channel,从中取东西
- log.Printf("Received a message: %s", d.Body)
- dot_count := bytes.Count(d.Body, []byte(".")) // 统计d.Body中的"."的个数
- t := time.Duration(dot_count)
- time.Sleep(t * time.Second) // 有几个点就sleep几秒
- log.Printf("Done")
- d.Ack(false) // 手动ACK,如果不ACK的话,那么无法保证这个消息被处理,可能它已经丢失了(比如消息队列挂了)
- }
- }()
2.消息持久化
消息ACK保证了消息不会丢失,但是当rabbitMQ Server停止(不是consumer 挂掉)的时候,我们的所有消息都会丢失。
针对这种情况,我们先确保消息队列的持久化,设置消息队列的durable选项为true
- q, err := ch.QueueDeclare(
- "task_queue", // name
- true, // durable 队列持久化
- false, // delete when unused
- false, // exclusive
- false, // no-wait
- nil, // arguments
- )
- failOnError(err, "Failed to declare a queue")
队列持久化了,接下来就是消息持久化,使用amqp.Persistent选项
- body := bodyFrom(os.Args)
- err = ch.Publish(
- "", // exchange
- q.Name, // routing key
- false, // mandatory
- false,
- amqp.Publishing{
- DeliveryMode: amqp.Persistent, // 消息持久化,虽然消息设置持久化了,但是并不能保证一定会
- ContentType: "text/plain",
- Body: []byte(body),
- })
- failOnError(err, "Failed to publish a message")
- log.Printf(" [x] Sent %s", body)
这种方式并不能保证消息一定持久化到硬盘中,可以还未来的及写入硬盘
3.消息的公平分派
设置Qos,设置预取大小prefetch,当prefetch=1时,表示在没收到consumer的ACK消息之前,只会为其consumer分派一个消息
- // 为了保证公平分发,不至于其中某个consumer一直处理,而其他不处理
- err = ch.Qos(
- 1, // prefetch count 在server收到consumer的ACK之前,预取的数量。为1,表示在没收到consumer的ACK之前,只会为其分发一个消息
- 0, // prefetch size 大于0时,表示在收到consumer确认消息之前,将size个字节保留在网络中
- false, // global true:Qos对同一个connection的所有channel有效; false:Qos对同一个channel上的所有consumer有效
- )
4.producer端完整程序
- 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 发送与接送两端队列都要持久化,队列持久化了,但是并不能保证消息也持久化
- 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, // 消息持久化,虽然消息设置持久化了,但是并不能保证一定会
- 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
- }
5.consumer端完整代码
- package main
- import (
- "bytes"
- "log"
- "time"
- "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 队列持久化
- false, // delete when unused
- false, // exclusive
- false, // no-wait
- nil, // arguments
- )
- failOnError(err, "Failed www.yongshiyule178.com to declare a queue")
- // 为了保证公平分发,不至于其中某个consumer一直处理,而其他不处理
- err = ch.Qos(
- 1, // prefetch count 在server收到consumer的ACK之前,预取的数量。为1,表示在没收到consumer的ACK之前,只会为其分发一个消息
- 0, // prefetch size 大于0时,表示在收到consumer确认消息之前,将size个字节保留在网络中
- false, // global true:Qos对同一个connection的所有channel有效; false:Qos对同一个channel上的所有consumer有效
- )
- failOnError(err, "Failed to set www.078881.cn QoS")
- msgs, err := ch.Consume(
- q.Name, // queue
- "", // consumer
- false, // auto-ack 不进行自动ACK
- 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 { www.meiwanyule.cn // msgs 是一个channel,从中取东西
- log.Printf("Received a message:www.huachengj1980.com %s", d.Body)
- dot_count := bytes.Count(d.Body, []byte(".")) // 统计d.Body中的"."的个数
- t := time.Duration(dot_count)
- time.Sleep(t * time.Second) // 有几个点就sleep几秒
- log.Printf("Done")
- d.Ack(false) www.wanmeiyuele.cn // 手动ACK,如果不ACK的话,那么无法保证这个消息被处理,可能它已经丢失了(比如消息队列挂了)
- }
- }()
- log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
- <-forever
- }
6.运行
producer:
consumer:
consumer1 | consumer2 |