RabbitMQ 高级功能
一、mandatory 和 immediate
生产者发送消息的方法 Channel.Publish() 各个参数解析如下:
1 2 3 4 5 6 7 | err := ch.Publish( "helloEx" , //exchange:源交换器名称,如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。 "helloEx2helloQ" , //key:路由键 false, //mandatory: false, //immediate: msg, //msg:发送消息,数据类型为 amqp.Publishing ) |
其中 mandatory 和 immediate 定义了消息的最终去向。
1. mandatory
消息发送到交换器以后,当交换器无法根据自身的类型和路由键找到一个符合条件的队列,若 mandatory 为 true,RabbitMQ 会调用 Basic.Return 命令将消息返回给生产者;若 mandatory 为 false,消息会被直接丢弃。
AMQP 协议流转过程:

2. immediate
当 immediate 为 true 时,如果交换器在将消息路由到队列时发现队列上并不存在任何消费者,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时,该消息会通过 Basic.Return 返回至生产者。
概括来说,mandatory 参数告诉服务器至少将该消息路由到一个队列中,否则将消息返回给生产者。immediate 参数告诉服务器,如果该消息关联的队列上有消费者,则立刻投递;如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者,不用将消息存入队列而等待消费者了。
RabbitMQ 3.0 版本开始去掉了对 immediate 参数的支持,对此 RabbitMQ 官方解释是:immediate 参数会影响镜像队列的性能,增加了代码复杂性,建议采用 TTL+DLX 的方法替代。
3. Channel.NotifyReturn
通过调用 Channel.NotifyReturn() 添加监听器,生产者可以获取到没有被正确路由到合适队列的消息。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | package main import ( "fmt" "time" "github.com/streadway/amqp" ) func main() { // connect to rabbitmq conn, err := amqp.Dial( "amqp://root:shiajun666@192.168.10.4:5672" ) if err != nil { fmt.Println( "Connect to RabbitMQ failed: " , err) return } defer conn.Close() // open a channel ch, err := conn.Channel() if err != nil { fmt.Println( "Open channel failed: " , err) return } defer ch.Close() // declare an exchange err = ch.ExchangeDeclare( "HelloEx2" , "direct" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare exchange failed: " , err) return } // declare an queue _, err = ch.QueueDeclare( "helloQ2" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare queue failed: " , err) return } // bind the exchange with the queue err = ch.QueueBind( "helloQ2" , "HelloEx22helloQ" , "HelloEx2" , false, nil) if err != nil { fmt.Println( "Bind the exchange with the queue failed: " , err) return } // add return listener returnChan := make( chan amqp.Return, 0) ch.NotifyReturn(returnChan) // send a message msg := amqp.Publishing{ ContentType: "text/plain" , //消息内容类型 DeliveryMode: amqp.Persistent, //消息传输类型:1 amqp.Transient 不管队列是否持久化,消息都不会被持久 // 2 amqp.Persistent 只有队列是持久化的,消息才会持久化,否则消息同样不会持久化 Priority: 0, //消息优先级:0 - 9 ReplyTo: "" , //address to to reply to (ex: RPC) Expiration: "" , //消息有效期 MessageId: "" , //message identifier Timestamp: time.Now(), //消息发送时间戳 Type: "" , //消息类型 UserId: "" , //creating user id - ex: "guest AppId: "" , //creating application id Body: []byte( "Hello World" ), //消息内容 } err = ch.Publish( "HelloEx2" , //exchange:源交换器名称,如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。 "HelloEx22helloQ2" , //key:路由键 true, //mandatory: false, //immediate: msg, //msg:发送消息,数据类型为 amqp.Publishing ) if err != nil { fmt.Println( "Send message failed: " , err) return } // get return messages for { select { case returnMsg := <-returnChan: fmt.Printf( "Get return message from exchange[%s], routing key[%s], content: %s]\n" , returnMsg.Exchange, returnMsg.RoutingKey, string(returnMsg.Body)) } } } |
上述程序中,交换器 HelloEx2 与队列 helloQ2 绑定,Binding key 为 HelloEx22helloQ,而发送消息时指定的 Routing key 为 HelloEx22helloQ2,mandatory 为 true。此时消息无法路由到合适的队列,所以 RabbitMQ 会将消息返回给生产者,运行程序最终打印结果为:
1 | Get return message from exchange[HelloEx2], routing key[HelloEx22helloQ2], content: Hello World] |
二、备份交换器
备份交换器,英文名称为 Alternate Exchange,简称 AE。
如果既不想因为添加返回消息监听而复杂化生产者的编程逻辑,又不想消息丢失,那么可以使用备份交换器,这样可以将未被路由的消息存储在 RabbitMQ 中,在需要的时候去处理这些消息。
Go 语言客户端设置为某个交换器设置备份交换器的方法很简单,只需在调用 Channel.ExchangeDeclare() 方法声明交换器的时候,在最后一个参数加入 alternate-exchange 参数指定备份交换器的名称即可。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | package main import ( "fmt" "time" "github.com/streadway/amqp" ) func main() { // connect to rabbitmq conn, err := amqp.Dial( "amqp://root:shiajun666@192.168.10.4:5672" ) if err != nil { fmt.Println( "Connect to RabbitMQ failed: " , err) return } defer conn.Close() // open a channel ch, err := conn.Channel() if err != nil { fmt.Println( "Open channel failed: " , err) return } defer ch.Close() // alternate exchange name aeName := "myAe" // declare an exchange with a alternate exchange args := amqp.Table{ "alternate-exchange" : aeName} err = ch.ExchangeDeclare( "normalExchange" , "direct" , true, false, false, false, args) if err != nil { fmt.Println( "Declare exchange failed: " , err) return } // declare an queue _, err = ch.QueueDeclare( "normalQueue" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare queue failed: " , err) return } // bind the exchange with the queue err = ch.QueueBind( "normalQueue" , "normalRoutingKey" , "normalExchange" , false, nil) if err != nil { fmt.Println( "Bind the exchange with the queue failed: " , err) return } // declare an alternate exchange err = ch.ExchangeDeclare(aeName, "fanout" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare exchange failed: " , err) return } // declare an queue _, err = ch.QueueDeclare( "unrouteQueue" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare queue failed: " , err) return } // bind the alternate exchange with the queue err = ch.QueueBind( "unrouteQueue" , "normalRoutingKey" , aeName, false, nil) if err != nil { fmt.Println( "Bind the exchange with the queue failed2: " , err) return } // send a message msg := amqp.Publishing{ ContentType: "text/plain" , //消息内容类型 DeliveryMode: amqp.Persistent, //消息传输类型:1 amqp.Transient 不管队列是否持久化,消息都不会被持久 // 2 amqp.Persistent 只有队列是持久化的,消息才会持久化,否则消息同样不会持久化 Priority: 0, //消息优先级:0 - 9 ReplyTo: "" , //address to to reply to (ex: RPC) Expiration: "" , //消息有效期 MessageId: "" , //message identifier Timestamp: time.Now(), //消息发送时间戳 Type: "" , //消息类型 UserId: "" , //creating user id - ex: "guest AppId: "" , //creating application id Body: []byte( "Hello World" ), //消息内容 } err = ch.Publish( "normalExchange" , //exchange:源交换器名称,如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。 "notExistRoutingKey" , //key:路由键 false, //mandatory: false, //immediate: msg, //msg:发送消息,数据类型为 amqp.Publishing ) if err != nil { fmt.Println( "Send message failed: " , err) return } } |
运行程序,从 RabbitMQ 管理后台可以看到,备份交换器 myAE 绑定的队列 unrouteQueue 上多了一条消息。
流转过程:

注:
如果设置的备份交换器不存在,或者备份交换器没有绑定任何队列,或者没有任何与 RoutineKey 匹配的队列,消息都会丢失。
为了防止消息丢失,备份交换器的类型最好设置为 fanout。
如果备份交换器和 mandatory 参数一起使用,那么 mandatory 参数无效。
三、过期时间(TTL)
TTL,Time to Live 的简称,即过期时间。RabbitMQ 可以对消息和队列设置 TTL。
1. 设置消息的 TTL
目前有两种方法可以设置消息的 TTL。第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。第二种方法是对消息本身进行单独设置,每条消息的 TTL 可以不同。如果两种方法一起使用,则消息的 TTL 以两者之间较小的那个数值为准。
如果不设置 TTL,则表示此消息不会过期;如果将 TTL 设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃。
消息在队列中的生存时间一旦超过设置的 TTL 值时,就会变成“死信”(Dead Message),消费者将无法再收到该消息。
对于第一种设置队列 TTL 属性的方法,一旦消息过期,就会从队列中抹去,而在第二种方法中,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的。
为什么这两种方法处理的方式不一样?因为第一种方法里,队列中已过期的消息肯定在队列头部,RabbitMQ 只要定期从队头开始扫描是否有过期的消息即可(先入列的消息肯定是先过期的)。而第二种方法里,每条消息的过期时间不同,如果要删除所有过期消息势必要扫描整个队列,所以不如等到此消息即将被消费时再判定是否过期,如果过期再进行删除即可。
代码示例:
(1)通过队列属性设置消息 TTL
调用 Channel.QueueDeclare() 方法声明队列时,在最后的扩展参数中设置 “x-message-ttl”。
1 2 3 4 5 6 | qargs := amqp.Table{ "x-message-ttl" : 10000} //单位:毫秒 _, err := ch.QueueDeclare( "normalQueue2" , true, false, false, false, qargs) if err != nil { fmt.Println( "Declare queue failed: " , err) return } |
(2)对消息本身设置 TTL
通过 amqp.Publishing 结构体的 “Expiration” 字段进行设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | msg := amqp.Publishing{ ContentType: "text/plain" , //消息内容类型 DeliveryMode: amqp.Persistent, //消息传输类型:1 amqp.Transient 不管队列是否持久化,消息都不会被持久 // 2 amqp.Persistent 只有队列是持久化的,消息才会持久化,否则消息同样不会持久化 Priority: 0, //消息优先级:0 - 9 ReplyTo: "" , //address to to reply to (ex: RPC) Expiration: "5000" , //消息有效期 MessageId: "" , //message identifier Timestamp: time.Now(), //消息发送时间戳 Type: "" , //消息类型 UserId: "" , //creating user id - ex: "guest AppId: "" , //creating application id Body: []byte( "Hello World" ), //消息内容 } |
2. 设置队列的 TTL
调用 Channel.QueueDeclare() 方法声明队列时,在最后的扩展参数中设置 “x-expires”。
1 2 3 4 5 6 | qargs := amqp.Table{ "x-expires" : 10000} //单位:毫秒 _, err = ch.QueueDeclare( "normalQueue3" , true, false, false, false, qargs) if err != nil { fmt.Println( "Declare queue failed: " , err) return } |
四、死信队列
DLX,全称为 Dead-Letter-Exchange,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
消息变成死信一般是由于以下几种情况:
(1)消息被接收方拒绝,并且没有重新入列的参数设置;
(2)消息过期;
(3)队列达到最大长度。
DLX 也是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列中存在死信时,RabbitMQ 就会自动地将这个消息重新发布到设置的 DLX 上去,进而被路由到另一个队列,即死信队列。
DLX 的设置也很简单,在 Channel.QueueDeclare() 扩展参数中设置“x-dead-letter-exchange”即可。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | package main import ( "fmt" "time" "github.com/streadway/amqp" ) func main() { // connect to rabbitmq conn, err := amqp.Dial( "amqp://root:shiajun666@192.168.10.4:5672" ) if err != nil { fmt.Println( "Connect to RabbitMQ failed: " , err) return } defer conn.Close() // open a channel ch, err := conn.Channel() if err != nil { fmt.Println( "Open channel failed: " , err) return } defer ch.Close() // dead letter exchange name dlxName := "exchange.dlx" // declare an exchange err = ch.ExchangeDeclare( "exchange.normal" , "fanout" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare exchange failed: " , err) return } // declare a queue with TTL and DLX qargs := amqp.Table{ "x-message-ttl" : 10000, "x-dead-letter-exchange" : dlxName, } _, err = ch.QueueDeclare( "queue.normal" , true, false, false, false, qargs) if err != nil { fmt.Println( "Declare queue failed: " , err) return } // bind the exchange with the queue err = ch.QueueBind( "queue.normal" , "normalRoutingKey" , "exchange.normal" , false, nil) if err != nil { fmt.Println( "Bind the exchange with the queue failed: " , err) return } // declare a dead letter exchange err = ch.ExchangeDeclare(dlxName, "direct" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare exchange failed: " , err) return } // declare a queue _, err = ch.QueueDeclare( "queue.dlx" , true, false, false, false, nil) if err != nil { fmt.Println( "Declare queue failed: " , err) return } // bind the dead letter exchange with the queue err = ch.QueueBind( "queue.dlx" , "normalRoutingKey" , dlxName, false, nil) if err != nil { fmt.Println( "Bind the exchange with the queue failed: " , err) return } // send a message msg := amqp.Publishing{ ContentType: "text/plain" , //消息内容类型 DeliveryMode: amqp.Persistent, //消息传输类型:1 amqp.Transient 不管队列是否持久化,消息都不会被持久 // 2 amqp.Persistent 只有队列是持久化的,消息才会持久化,否则消息同样不会持久化 Priority: 0, //消息优先级:0 - 9 ReplyTo: "" , //address to to reply to (ex: RPC) Expiration: "" , //消息有效期 MessageId: "" , //message identifier Timestamp: time.Now(), //消息发送时间戳 Type: "" , //消息类型 UserId: "" , //creating user id - ex: "guest AppId: "" , //creating application id Body: []byte( "Hello World" ), //消息内容 } err = ch.Publish( "exchange.normal" , //exchange:源交换器名称,如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。 "normalRoutingKey" , //key:路由键 false, //mandatory: false, //immediate: msg, //msg:发送消息,数据类型为 amqp.Publishing ) if err != nil { fmt.Println( "Send message failed: " , err) return } } |
以上代码中,交换器 exchange.normal 绑定队列 queue.normal,交换器 exchange.dlx 绑定队列 queue.dlx,同时设置队列 queue.normal 的消息过期时间为 10000 毫秒,且其 DLX 为 exchange.dlx。消息发送给交换器 exchange.normal,再路由到队列 queue.normal,10000 毫秒后,消息过期,RabbitMQ 自动将其发送到交换器 exchange.dlx,再路由到队列 queue.dlx。
流转过程:

RabbitMQ web 后台:

五、TTL+DLX 的拓展应用
1. 实现 immediate 参数效果
通过队列属性设置消息的 TTL 为 0,同时为该队列设置一个 DLX,生产者监听死信队列中的消息,进行相应的处理。这样,如果消息无法直接投递到消费者,则会进入死信队列,从而被生产者监听到,这与 RabbitMQ 3.0 版本之前的 immediate 参数为 true 时消息无法直接投递到消费者则返回给生产者有异曲同工之妙。
2. 实现延迟队列功能
例如,在“4. 死信队列”的代码示例中,若生产者直接将消息发送到交换器 exchange.normal,但消费者却监听的是死信队列 queue.dlx,则生产者发送的每条消息都会在队列 queue.nomal 中逗留 10000 毫秒之后才能被消费者接收到,从而实现了延迟队列的功能。
可通过使用多个队列设置多个 TTL 和 DLX,实现不同延迟等级的队列:

六、优先级队列
优先级高的消息具备优先被消费的特权。可在 Channel.QueueDeclare() 扩展参数中通过“x-max-priority”设置队列中消息的优先级。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2019-09-30 go读写excel文件
2019-09-30 go读写文本文件
2019-09-30 排序算法