库的选择
go连接rabbitmq最常用的库有以下几个,
建议使用amqp091-go库
库名 | GitHub地址 | Star数 | 使用方式 |
---|---|---|---|
rabbitmq-stream-go-client | github.com/rabbitmq/ra… | 36 | 用于RabbitMQ流的客户端库,支持高性能、可靠性和可扩展性 |
amqp091-go | github.com/rabbitmq/am… | 1.2k | RabbitMQ核心团队维护的AMQP 0.9.1客户端,支持多种特性和配置 |
go-rabbitmq | github.com/wagslane/go… | 1.1k | 对streadway/amqp的封装,提供自动重连、多线程消费、合理的默认值等 |
使用
首先,请确保您已经安装了rabbitmq/amqp091-go库。由于这个库不是一个常见的Go库,您可能需要手动下载并将其放置在您的项目中。
go get github.com/rabbitmq/amqp091-go
以下是使用rabbitmq/amqp091-go库登录RabbitMQ并发布一条指定routingKey的JSON结构消息的Go代码示例:
package Arim
import (
"context"
amqp "github.com/rabbitmq/amqp091-go"
"io/ioutil"
"log"
"time"
)
func Publish() {
filePath := "src/Arim/msg.json"
jsonData, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatalf("Failed to read JSON file: %v", err)
}
// 1. 使用指定的用户名和密码连接到RabbitMQ服务器 15672是界面的端口 5672才是
conn, err := amqp.Dial("amqp://poi:****/")
if err != nil {
log.Fatalf("Failed to connect to RabbitMQ: %s", err)
}
defer conn.Close()
// 2. 创建一个新的通道
ch, err := conn.Channel()
if err != nil {
log.Fatalf("Failed to open a channel: %s", err)
}
defer ch.Close()
// 3. 声明一个交换机(如果尚未存在)
// 4. 发布消息到指定的交换机和routingKey
msg := amqp.Publishing{
ContentType: "application/json",
Body: jsonData,
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
routingKey := "Arim.Ladle.Transfer.Model.Finished"
exchange := "Arim_Poi_Service"
//发布消息时下面的报错,可能是因为设置了5s,调试导致超时引起。运行,不调试时,发布成功
//Failed to publish a message: write tcp 10.255.236.39:65149->10.220.23.22:5672: wsasend: An existing connection was forcibly closed by the remote host.
err = ch.PublishWithContext(ctx, exchange, routingKey, false, false, msg)
if err != nil {
log.Fatalf("Failed to publish a message: %v", err)
}
log.Println("Message published successfully!")
}
请注意,虽然库的名称和路径已经更改,但大多数API调用和函数签名应该与streadway/amqp库非常相似。这是因为rabbitmq/amqp091-go库是基于streadway/amqp库的一个分支或变种。
开启生产者确认
在 RabbitMQ 中,生产者确认(Publisher Acknowledgements)默认是关闭的。要启用生产者确认,你需要在发布消息之前显式地将该功能开启。
以下是一个使用 Go 语言和 github.com/rabbitmq/amqp091-go 库发布消息时启用生产者确认的代码示例:
package main
import (
"github.com/rabbitmq/amqp091-go"
"log"
)
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()
// 开启生产者确认模式
err = ch.Confirm(false)
failOnError(err, "Failed to put channel into confirm mode")
// 创建一个用于接收确认消息的通道
confirms := ch.NotifyPublish(make(chan amqp.Confirmation, 1))
// 声明一个队列
q, err := ch.QueueDeclare(
"test_confirm",
false,
false,
false,
false,
nil,
)
failOnError(err, "Failed to declare a queue")
body := "Hello, RabbitMQ Confirm Message!"
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
// 等待确认消息
confirm := <-confirms
if confirm.Ack {
log.Println("Message confirmed with tag", confirm.DeliveryTag)
} else {
log.Println("Message failed to confirm with tag", confirm.DeliveryTag)
}
}
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
在这个示例中:
- 开启生产者确认模式:
-
- 通过调用 ch.Confirm(false) 方法,我们将这个通道(channel)置入确认模式。这告诉 RabbitMQ,我们想要在每次发布消息后接收一个确认消息。
- 创建一个用于接收确认消息的通道:
-
- ch.NotifyPublish(make(chan amqp.Confirmation, 1)) 创建了一个 amqp.Confirmation 类型的通道,并告诉 RabbitMQ 我们想要通过这个通道接收确认消息。
- 发布消息:
-
- 我们像往常一样使用 ch.Publish 方法发布消息。
- 等待确认消息:
-
- 我们从 confirms 通道中读取确认消息。如果 confirm.Ack 是 true,那么这个消息已经被 RabbitMQ 成功接收;如果是 false,那么消息可能因为某种原因没有被成功处理。
这个示例展示了如何在使用 Go 语言和 github.com/rabbitmq/amqp091-go 库发布消息时启用和处理 RabbitMQ 的生产者确认。
消费者确认-手动确认
在 RabbitMQ 中,消费者确认(Consumer Acknowledgements)的默认设置是开启的。这意味着,当消费者从队列中接收到消息后,默认情况下,它需要显式地向 RabbitMQ 发送一个确认信号(acknowledgement)来表示这条消息已经被成功处理。只有当 RabbitMQ 收到这个确认信号后,它才会从队列中移除这条消息。
在消费者确认模式中,有两种常见的确认方式:
- 自动确认(Auto-ack):
-
- 当消费者的 autoAck 参数被设置为 true 时,消费者接收到消息后,RabbitMQ 会自动认为这个消息已经被成功处理,并将其从队列中移除。
- 这种模式下,消费者不需要显式地发送确认信号。
- 手动确认:
-
- 当消费者的 autoAck 参数被设置为 false 时,消费者需要在处理完消息后,显式地发送一个确认信号(ack)给 RabbitMQ。
- 这种模式下,如果消费者因为某种原因(例如崩溃)未能处理消息,并且没有发送确认信号,那么这个消息将不会从队列中移除。RabbitMQ 会在适当的时候将这个消息重新分发给其他消费者。
默认情况下, autoAck 参数是 false ,即消费者确认是开启的,消费者需要显式地发送确认信号。
以下是一个 Go 语言使用 github.com/rabbitmq/amqp091-go 库的代码示例,展示了如何在消费者中处理手动确认:
package main
import (
"github.com/rabbitmq/amqp091-go"
"log"
)
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(
"test_queue",
false,
false,
false,
false,
nil,
)
failOnError(err, "Failed to declare a queue")
msgs, err := ch.Consume(
q.Name,
"",
false, // auto-ack set to false, we will ack manually
false,
false,
false,
nil,
)
failOnError(err, "Failed to register a consumer")
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
d.Ack(false) // Manually send an ack after processing the message
}
}
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
在这个示例中,我们设置 autoAck 参数为 false,这意味着我们需要在处理完每条消息后显式地调用 d.Ack(false) 来发送确认信号。
事务操作
RabbitMQ 支持事务机制,允许你将一组操作(如:发送消息、确认消息等)组合成一个原子操作。这意味着,在一个事务中的所有操作要么全部成功,要么全部失败。以下是 RabbitMQ 事务机制的详细介绍:
RabbitMQ 事务的基本步骤:
- 开启事务:
-
- 在开始一个事务之前,你需要将当前的通道(Channel)设置为事务模式。这可以通过调用 channel.Tx() 方法来实现。
- 执行操作:
-
- 在事务中,你可以执行多个操作,如:发送消息、确认消息等。
- 提交事务:
-
- 如果所有的操作都成功执行,你可以提交事务,这将使所有的操作生效。这可以通过调用 channel.TxCommit() 方法来实现。
- 回滚事务:
-
- 如果在事务中的某个操作失败,你可以回滚事务,这将撤销所有的操作。这可以通过调用 channel.TxRollback() 方法来实现。
Go 代码示例:
package main
import (
"log"
"github.com/rabbitmq/amqp091-go"
)
func main() {
// 连接到 RabbitMQ 服务器...
// 创建一个通道...
// 声明一个队列
_, err = ch.QueueDeclare("test-queue", false, false, false, false, nil)
if err != nil {
log.Fatalf("Failed to declare a queue: %v", err)
}
// 开启事务
err = ch.Tx()
if err != nil {
log.Fatalf("Failed to start a transaction: %v", err)
}
// 发送消息
err = ch.Publish("", "test-queue", false, false, amqp.Publishing{
ContentType: "text/plain",
Body: []byte("Hello, RabbitMQ!"),
})
if err != nil {
log.Fatalf("Failed to publish a message: %v", err)
}
// 根据需要决定是提交还是回滚事务
// 提交事务
err = ch.TxCommit()
if err != nil {
log.Fatalf("Failed to commit the transaction: %v", err)
}
// 如果需要回滚事务,可以调用以下方法:
err = ch.TxRollback()
if err != nil {
log.Fatalf("Failed to rollback the transaction: %v", err)
}
log.Println("Successfully published a message in a transaction")
}
注意事项:
- RabbitMQ 的事务机制可能会对性能产生较大的影响,因为它需要 RabbitMQ 服务器在磁盘上持久化事务的状态。因此,在高吞吐量的场景下,使用事务可能不是最佳选择。
- 作为事务的替代方案,RabbitMQ 还支持发布者确认(Publisher Confirms)和消费者确认(Consumer Acknowledgments)机制,这些机制提供了一种更轻量级的方式来确保消息的可靠传递。
这个 Go 代码示例展示了如何在 RabbitMQ 中使用事务机制来确保消息的可靠传递。在这个示例中,我们首先开启一个事务,然后在事务中发送一条消息,最后提交事务。如果在事务中的任何操作失败,我们可以选择回滚事务,从而撤销所有的操作。
消息重试机制
在 RabbitMQ 中,消息重试机制通常是通过设置消息的 TTL(Time To Live)和 Dead Letter Exchanges 来实现的。当消息在队列中的存活时间超过 TTL 设置的时间后,它会被移动到一个指定的 Dead Letter Exchange,然后可以重新被发送到原队列或者其他队列,从而实现消息的重试。
以下是配置消息重试机制的步骤和相应的 Go 代码示例,使用的库是 github.com/rabbitmq/amqp091-go:
- 创建一个 Dead Letter Exchange 和一个 Dead Letter Queue:
-
- 当消息在主队列中的存活时间超过 TTL 后,它会被发送到这个 Dead Letter Exchange,然后路由到 Dead Letter Queue。
- 为主队列设置 TTL 和 Dead Letter Exchange:
-
- 设置主队列的消息 TTL 和 Dead Letter Exchange。
- 消费 Dead Letter Queue 中的消息,并根据需要重新发布到主队列:
-
- 从 Dead Letter Queue 中消费消息,并决定是否将消息重新发布到主队列以进行重试。
以下是完整的 Go 代码示例:
package main
import (
"log"
"time"
"github.com/rabbitmq/amqp091-go"
)
func main() {
// 连接到 RabbitMQ 服务器...
// 创建一个通道...
// 创建一个 Dead Letter Exchange
err = ch.ExchangeDeclare("dlx", "direct", true, false, false, false, nil)
if err != nil {
log.Fatalf("Failed to declare a dead letter exchange: %v", err)
}
// 创建一个 Dead Letter Queue
dlq, err := ch.QueueDeclare("dlq", true, false, false, false, nil)
if err != nil {
log.Fatalf("Failed to declare a dead letter queue: %v", err)
}
// 将 Dead Letter Queue 绑定到 Dead Letter Exchange
err = ch.QueueBind(dlq.Name, "dlk", "dlx", false, nil)
if err != nil {
log.Fatalf("Failed to bind the dead letter queue: %v", err)
}
// 创建一个主队列,并设置消息的 TTL 和 Dead Letter Exchange
args := amqp.Table{
"x-message-ttl": 5000, // 设置消息的 TTL 为 5000 毫秒
"x-dead-letter-exchange": "dlx", // 设置 Dead Letter Exchange
"x-dead-letter-routing-key": "dlk", // 设置 Dead Letter Routing Key
}
queue, err := ch.QueueDeclare("test-queue", true, false, false, false, args)
if err != nil {
log.Fatalf("Failed to declare a queue: %v", err)
}
// 发布一条消息到主队列
err = ch.Publish("", queue.Name, false, false, amqp.Publishing{
ContentType: "text/plain",
Body: []byte("Hello, RabbitMQ!"),
})
if err != nil {
log.Fatalf("Failed to publish a message: %v", err)
}
// 消费 Dead Letter Queue 中的消息
msgs, err := ch.Consume(dlq.Name, "", false, false, false, false, nil)
if err != nil {
log.Fatalf("Failed to consume messages: %v", err)
}
go func() {
for msg := range msgs {
log.Printf("Received a message from DLQ: %s", msg.Body)
// 根据需要决定是否将消息重新发布到主队列以进行重试
err := ch.Publish("", queue.Name, false, false, amqp.Publishing{
ContentType: "text/plain",
Body: msg.Body,
})
if err != nil {
log.Printf("Failed to republish a message: %v", err)
} else {
log.Println("Successfully republished a message for retry")
msg.Ack(false) // 确认消息,从 DLQ 中移除
}
}
}()
// 让程序继续运行,以便消费消息
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
select {}
}
这个 Go 代码示例展示了如何在 RabbitMQ 中使用 github.com/rabbitmq/amqp091-go 库和事务机制来配置和实现消息的重试机制。在这个示例中,我们首先创建了一个 Dead Letter Exchange 和一个 Dead Letter Queue,然后为主队列设置了消息的 TTL 和 Dead Letter Exchange。当消息在主队列中的存活时间超过 TTL 后,它会被发送到 Dead Letter Queue。我们从 Dead Letter Queue 中消费这些消息,并决定是否将它们重新发布到主队列以进行重试。
消息预取
消息预取机制(Prefetching):
消息预取机制是 RabbitMQ 提供的一种流量控制策略,它允许你限制一个消费者一次性预取(即拉取但不确认)的消息数量。这个机制对于实现消费者的负载均衡和提高消息处理的效率非常有用。
当你设置了预取计数(Prefetch Count)后,RabbitMQ 会确保一个消费者在同一时间最多只会有这么多未确认的消息。也就是说,消费者需要确认一个消息后,RabbitMQ 才会发送新的消息给它,直到达到预取计数的限制。
默认是0,这意味着没有预取限制,可以接收更可能多的消息。
这种设置可能会导致某些消费者被大量的消息淹没,特别是当某些消费者处理消息的速度远远快于其他消费者时。因此,在需要实现消费者之间的负载均衡时,通常建议设置一个合适的预取数量。
优点:
- 负载均衡: 通过限制每个消费者可以处理的消息数量,可以更均匀地将消息分配给所有的消费者。
- 提高吞吐量: 预取允许消费者在本地缓存一批消息,从而减少了网络往返的延迟,提高了处理消息的速度。
参数:
- prefetchCount : 这个参数设置了一个消费者在同一时间最多可以接收但不确认的消息数量。
- global : 这个参数是一个布尔值,如果设置为 true,则 prefetchCount 的设置将应用于整个 channel,而不仅仅是一个 consumer。
假设一个队列有两个消费客户端,消息预取数量都设置为3,队列中有顺序排列的6条消息(1, 2, 3, 4, 5, 6)。
在这种情况下,RabbitMQ 的行为通常是这样的:
- 客户端1 会收到消息 1, 2, 3(因为预取数量设置为3)
- 客户端2 会收到消息 4, 5, 6(因为预取数量设置为3)
这是因为 RabbitMQ 会尽量平均地将消息分发给各个消费客户端。当客户端1的预取限制被达到(即收到3条消息)后,RabbitMQ 会开始将消息发送给客户端2,直到它的预取限制也被达到。
Go 代码示例:
以下是一个使用 Go 语言和 github.com/rabbitmq/amqp091-go 库实现的 RabbitMQ 消息预取机制的示例:
package main
import (
"log"
"github.com/rabbitmq/amqp091-go"
)
func main() {
// 连接到 RabbitMQ 服务器...
// 创建一个通道...
// 声明一个队列
_, err = ch.QueueDeclare("test-queue", true, false, false, false, nil)
if err != nil {
log.Fatalf("Failed to declare a queue: %v", err)
}
// 设置消息预取计数
err = ch.Qos(3, 0, false) // 每个消费者一次最多处理3条未确认的消息
if err != nil {
log.Fatalf("Failed to set QoS: %v", err)
}
// 消费消息
msgs, err := ch.Consume("test-queue", "", false, false, false, false, nil)
if err != nil {
log.Fatalf("Failed to register a consumer: %v", err)
}
go func() {
for msg := range msgs {
log.Printf("Received a message: %s", msg.Body)
msg.Ack(false) // 手动发送确认消息
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
select {}
}
代码解释:
- 连接到 RabbitMQ 服务器: 使用 amqp.Dial 函数连接到 RabbitMQ 服务器。
- 创建一个通道: 使用 conn.Channel 方法创建一个新的通道。
- 声明一个队列: 使用 ch.QueueDeclare 方法声明一个队列。
- 设置消息预取计数: 使用 ch.Qos 方法设置消息预取计数。在这个示例中,我们设置每个消费者一次最多处理3条未确认的消息。
- 消费消息: 使用 ch.Consume 方法从队列中消费消息。
- 手动确认消息: 在消费者处理完消息后,需要手动发送确认消息,这可以通过调用 msg.Ack(false) 来实现。
这个 Go 代码示例展示了如何在 RabbitMQ 中使用 github.com/rabbitmq/amqp091-go 库和消息预取机制来实现消费者的负载均衡和提高消息处理的效率。在这个示例中,我们首先声明了一个队列,然后设置了消息预取计数,并消费这些消息。
使用mqtt协议推送rabbitmq中的消息
在使用 RabbitMQ 与 MQTT 协议来实现消息的推送时,通常的做法是让前端客户端通过 MQTT 协议订阅某个 Topic,而后端服务则通过 RabbitMQ 的 MQTT 插件将消息发布到这个 Topic。这样,前端客户端就可以实时地接收到这些消息。
以下是一个简单的示例,展示了如何使用 Go 语言和 github.com/rabbitmq/amqp091-go 库与 RabbitMQ 交互,以及如何使用 MQTT 协议将消息推送到前端。
步骤1:启用 RabbitMQ 的 MQTT 插件
首先,你需要在 RabbitMQ 服务器上启用 MQTT 插件。这可以通过以下命令完成:
rabbitmq-plugins enable rabbitmq_mqtt
启用该插件后,RabbitMQ 服务器将开始接受 MQTT 客户端的连接。
步骤2:后端服务发布消息到 RabbitMQ
以下是一个 Go 语言的示例代码,展示了如何使用 github.com/rabbitmq/amqp091-go 库将消息发布到 RabbitMQ:
package main
import (
"log"
"github.com/rabbitmq/amqp091-go"
)
func main() {
// 连接到 RabbitMQ 服务器
// 创建一个 Channel
// 声明一个 Exchange
err = ch.ExchangeDeclare(
"mqtt-exchange", // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
if err != nil {
log.Fatalf("Failed to declare an exchange: %v", err)
}
// 发布一条消息,错误,需要使用支持mqtt协议的库!
body := "Hello MQTT"
err = ch.Publish(
"mqtt-exchange", // exchange
"mqtt/topic", // routing key (MQTT topic)
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
if err != nil {
log.Fatalf("Failed to publish a message: %v", err)
}
log.Printf(" [x] Sent %s", body)
}
这段 Go 代码做了以下几件事:
- 连接到 RabbitMQ 服务器: 使用 amqp.Dial 函数连接到 RabbitMQ 服务器。
- 创建一个 Channel: 使用 conn.Channel 方法创建一个新的 Channel。
- 声明一个 Exchange: 使用 ch.ExchangeDeclare 方法声明一个新的 Exchange。在这个例子中,我们使用了 topic 类型的 Exchange,这允许我们使用 MQTT 风格的 Topic。
- 发布一条消息: 需要使用支持mqtt协议的库!
步骤3:前端客户端订阅 MQTT Topic
前端客户端(例如一个浏览器中运行的 JavaScript 应用,或一个移动应用)需要使用一个 MQTT 客户端库来订阅相应的 Topic。当后端服务通过 RabbitMQ 发布消息到这个 Topic 时,前端客户端将实时地接收到这些消息。
这里不再展示前端的 MQTT 客户端代码,因为它与具体的客户端库和应用平台有关。但一般来说,你需要做的是:
- 在前端应用中集成一个 MQTT 客户端库。
- 使用这个客户端库连接到 RabbitMQ 服务器(注意,此时 RabbitMQ 服务器充当了一个 MQTT 代理)。
- 订阅一个或多个 Topic。
- 定义一个回调函数,用于处理接收到的消息。
在前端,你可以使用一个 MQTT 客户端库来订阅 RabbitMQ 服务器上的某个 Topic,并实时接收到这些消息。以下是一个使用 JavaScript 和 MQTT.js 库的简单示例。
首先,你需要在你的项目中安装 MQTT.js 库。如果你的项目是一个基于 Node.js 的项目(例如一个使用 React 或 Vue.js 构建的项目),你可以使用 npm 或 yarn 来安装这个库:
npm install mqtt
# 或
yarn add mqtt
如果你的项目是一个纯前端的项目(例如一个简单的 HTML/CSS/JavaScript 项目),你可以通过
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
然后,你可以使用以下 JavaScript 代码来连接到 RabbitMQ 服务器(它需要启用 MQTT 插件),订阅一个 Topic,并定义一个回调函数来处理接收到的消息:
// 连接到 RabbitMQ 服务器(充当 MQTT 代理)
// 注意:你需要替换 'your_server' 为你的 RabbitMQ 服务器的地址
const client = mqtt.connect('ws://your_server:15675/ws', {
username: 'guest',
password: 'guest'
});
// 当与服务器的连接建立时触发
client.on('connect', () => {
console.log('Connected to RabbitMQ MQTT broker');
// 订阅一个 Topic
// 注意:你需要替换 'mqtt/topic' 为你想要订阅的 Topic
client.subscribe('mqtt/topic', (err) => {
if (err) {
console.error('Failed to subscribe to topic:', err);
} else {
console.log('Subscribed to topic');
}
});
});
// 当接收到一个消息时触发
client.on('message', (topic, message) => {
// message 是一个 Buffer,需要转换为字符串
console.log('Received message:', message.toString());
});
这段 JavaScript 代码做了以下几件事:
- 连接到 RabbitMQ 服务器: 使用 mqtt.connect 函数连接到 RabbitMQ 服务器。这里我们使用了 RabbitMQ 的 WebSocket 端口(默认是 15675)来连接。你需要提供 RabbitMQ 服务器的地址、用户名和密码。
- 订阅一个 Topic: 在与服务器的连接建立后,我们使用 client.subscribe 方法订阅一个 Topic。你可以根据你的需求替换 'mqtt/topic' 为你想要订阅的 Topic。
- 处理接收到的消息: 我们定义了一个回调函数,它会在我们接收到一个消息时被触发。这个回调函数接收两个参数:topic(消息所属的 Topic)和 message(消息的内容,它是一个 Buffer 对象)。
请注意,这个示例代码使用了 RabbitMQ 的 WebSocket 端口来连接。这是因为大多数浏览器的安全策略不允许前端代码直接连接到非 WebSocket 的端口。因此,你需要确保你的 RabbitMQ 服务器已经启用了 WebSocket 支持(这通常是通过启用 RabbitMQ 的 Web MQTT 插件来实现的)。
这个示例代码是一个非常基本的示例,仅用于演示如何使用 MQTT.js 库与 RabbitMQ 服务器交互。在实际的项目中,你可能需要添加更多的错误处理和状态管理的代码。
总结
在这个架构中,RabbitMQ 服务器充当了一个桥接器的角色,它既是 AMQP 的代理,也是 MQTT 的代理。后端服务使用 AMQP 协议与 RabbitMQ 交互,而前端客户端使用 MQTT 协议与 RabbitMQ 交互。这允许我们利用 RabbitMQ 强大的消息路由和分发能力,同时也能利用 MQTT 协议轻量级和实时性的优势。
发布消息
当 RabbitMQ 作为 MQTT 代理使用时,它可以接收遵循 MQTT 协议的客户端连接和消息。在这种情况下,如果你想从 Go 应用程序发布消息到 RabbitMQ MQTT 代理,你需要使用一个 MQTT 客户端库,而不是 AMQP 客户端库。
github.com/rabbitmq/amqp091-go 是一个 AMQP 0-9-1 客户端库,用于与遵循 AMQP 0-9-1 协议的代理(如 RabbitMQ)进行通信。它不支持 MQTT 协议。
如果你想从 Go 应用程序通过 MQTT 协议与 RabbitMQ 通信,你需要使用一个 Go 的 MQTT 客户端库。以下是一些流行的 Go MQTT 客户端库:
- github.com/eclipse/paho.mqtt.golang: 这是 Eclipse Paho 项目的 Go 语言版本。Paho 提供了多种语言的 MQTT 客户端库。
- github.com/gomqtt/client: 这是一个简单易用的 Go MQTT 客户端库。
以下是一个使用 github.com/eclipse/paho.mqtt.golang 库从 Go 应用程序发布消息到 RabbitMQ MQTT 代理的简单示例:
package main
import (
"fmt"
"log"
"os"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
func main() {
// MQTT 代理地址
broker := "tcp://localhost:1883"
// 客户端ID
clientID := "go-mqtt-sample"
// 创建一个新的 MQTT 客户端
opts := mqtt.NewClientOptions().AddBroker(broker).SetClientID(clientID)
client := mqtt.NewClient(opts)
// 连接到 MQTT 代理(在这个例子中是 RabbitMQ)
if token := client.Connect(); token.Wait() && token.Error() != nil {
log.Fatalf("Failed to connect to broker: %v", token.Error())
os.Exit(1)
}
// 主题
topic := "test/topic"
// 消息内容
message := "Hello MQTT"
// 发布消息
token := client.Publish(topic, 0, false, message)
token.Wait()
fmt.Printf("Published message: %s to topic: %s\n", message, topic)
// 等待一段时间,以确保消息被发送
time.Sleep(time.Second * 2)
// 断开与 MQTT 代理的连接
client.Disconnect(250)
}
在这个示例中,我们使用了 github.com/eclipse/paho.mqtt.golang 库来创建一个 MQTT 客户端,连接到 RabbitMQ MQTT 代理(假设 RabbitMQ 的 MQTT 插件已经启用,并且代理正在监听标准的 MQTT 端口 1883),然后发布一条消息到指定的主题。
这只是一个简单的示例,展示如何连接到 EMQ X 服务器并发布消息。在实际应用中,你可能还需要处理更多的功能,如消息订阅、QoS 设置、消息持久化、断线重连等。
请注意,要使这个示例工作,你需要确保 RabbitMQ 的 MQTT 插件已经启用。你可以使用以下命令来启用 RabbitMQ 的 MQTT 插件:
rabbitmq-plugins enable rabbitmq_mqtt
总结:如果你想将 RabbitMQ 作为 MQTT 代理使用,你需要在 Go 应用程序中使用一个 MQTT 客户端库,而不是 AMQP 客户端库。
amqp091-go库方法
连接串的坑
连接串中某些特殊字符要进行URL编码,如下所示,#是特殊字符,应该进行URL编码为%23
//amqp://rabbitmqadmin:rabbitmq#2023@192.168.151.71:5672/ 注意,#是特殊字符,应该进行URL编码为%23
mqUrl := "amqp://rabbitmqadmin:rabbitmq%232023@192.168.151.71:5672/"
conn, err := amqp.Dial(mqUrl)
URL 编码(又称为百分号编码)是一种用于在 URI(统一资源标识符)中嵌入特殊字符的机制。在这种编码中,特殊字符被转换成 % 符号后跟着两个十六进制表示的字符。这是因为一些字符在 URL 中具有特殊含义(如 /, ?, :, @, &, =, +, # 等),或者不是 URL 安全的(即在网络环境中可能引起歧义或者丢失)。
例如,在您的情况中,密码 rabbitmq#2023 中的 # 字符在 URL 中有特殊含义(通常用来指示 URL 的片段部分),所以需要对它进行编码。URL 编码规则如下:
-
字符的 ASCII 码是 35(十进制),对应的十六进制是 23。因此,它在 URL 中被编码为 %23。
任何不安全或保留的字符都可以通过这种方式进行编码。例如:
- 空格(ASCII 码 32)编码为 %20。
- 加号(+)(ASCII 码 43)编码为 %2B。
- 等号(=)(ASCII 码 61)编码为 %3D。
在许多编程环境中,都提供了函数或方法来自动进行这种编码,例如在 Go 语言中,可以使用 url.QueryEscape 函数来自动对这些特殊字符进行编码。
PublishWithContext
PublishWithContext 是 amqp091-go 库中的一个方法,用于在特定的上下文中发布消息。这是一个非常有用的方法,尤其是当你想在特定的上下文,如超时或取消的情况下发送消息时。
以下是 PublishWithContext 方法的定义:
// PublishWithContext will publish a message with a context.
// This is useful when you want to publish a message with a timeout or cancellation.
func (ch *Channel) PublishWithContext(
ctx context.Context,
exchange, key string,
mandatory, immediate bool,
msg Publishing,
) error {
select {
case ch.publish <- msg:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
这个方法接受以下参数:
- ctx: 上下文对象,通常用于超时或取消操作。
- exchange: 交换机的名称。
- key: 路由键。
- mandatory: 如果为 true,则在没有任何队列绑定到交换机的情况下,消息将被返回到发送者。
- immediate: 如果为 true,则如果没有消费者准备好接收消息,消息将被返回到发送者。
- msg: 要发布的消息。
方法的工作原理是尝试将消息发送到 publish 通道。如果上下文被取消或超时,它将返回一个错误。
以下是如何使用 PublishWithContext 的示例:
package main
import (
"context"
"log"
"time"
"github.com/rabbitmq/amqp091-go"
)
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatalf("Failed to connect to RabbitMQ: %v", err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
log.Fatalf("Failed to open a channel: %v", err)
}
defer ch.Close()
msg := amqp.Publishing{
ContentType: "text/plain",
Body: []byte("Hello World!"),
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = ch.PublishWithContext(ctx, "test-exchange", "test-key", false, false, msg)
if err != nil {
log.Fatalf("Failed to publish a message: %v", err)
}
log.Println("Message published successfully!")
}
在上述示例中,我们创建了一个与 RabbitMQ 服务器的连接和一个通道。然后,我们定义了一个消息并使用 PublishWithContext 方法将其发布到 test-exchange 交换机和 test-key 路由键。我们还为此操作设置了一个5秒的超时。如果在5秒内消息没有被发布,我们将收到一个超时错误。
ch.QueueBind
github.com/rabbitmq/amqp091-go 库中的 ch.QueueBind 方法用于将一个队列绑定到一个交换机上,这样交换机就可以将消息路由到这个队列。这是RabbitMQ中一个非常重要的概念,它允许你根据一定的规则将消息从交换机路由到一个或多个队列。
以下是 ch.QueueBind 方法的详细介绍:
方法签名
func (ch *Channel) QueueBind(name string, key string, exchange string, noWait bool, args Table) error
参数说明
- name string: 队列的名称。这是你想要绑定的队列的名字。
- key string: 绑定键(Binding Key)。这个键将用于将队列与交换机关联起来。交换机根据这个键和发送的消息的路由键(Routing Key)来决定如何路由消息。
- exchange string: 交换机的名称。这是你想要将队列绑定到的交换机的名字。
- noWait bool: 是否等待服务器的回应。如果设置为 true,函数将不等待服务器确认,直接返回。如果设置为 false,则会等待服务器的确认。
- args Table: 一个可选的参数表,用于传递额外的参数给服务器。这个参数表是一个键值对的映射。
返回值
- error: 如果绑定操作成功,返回值为 nil。否则,返回一个描述错误的 error 对象。
使用示例
package main
import (
"github.com/rabbitmq/amqp091-go"
"log"
)
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatalf("Failed to connect to RabbitMQ: %v", err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
log.Fatalf("Failed to open a channel: %v", err)
}
defer ch.Close()
q, err := ch.QueueDeclare(
"testQueue", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
if err != nil {
log.Fatalf("Failed to declare a queue: %v", err)
}
err = ch.QueueBind(
q.Name, // queue name
"routingKey", // routing key
"exchange", // exchange
false,
nil,
)
if err != nil {
log.Fatalf("Failed to bind a queue: %v", err)
}
}
在这个示例中,我们首先连接到RabbitMQ服务器,并创建一个名为 testQueue 的队列。然后,我们使用 ch.QueueBind 方法将这个队列绑定到名为 exchange 的交换机上,并指定绑定键(Binding Key)为 routingKey。
这样,当一个消息被发送到名为 exchange 的交换机,并且该消息的路由键(Routing Key)与 routingKey 匹配时,这个消息就会被路由到 testQueue 队列中。
ch.Consume
ch.Consume 方法是 github.com/rabbitmq/amqp091-go 库中用于从一个指定队列中消费消息的方法。当你调用这个方法后,它会返回一个消息通道(channel of amqp.Delivery),你可以从这个通道中读取到队列中的消息。
以下是 ch.Consume 方法的参数详细介绍:
- queue string
-
- 队列的名称。这是你想从中消费消息的队列。
- consumer string
-
- 消费者标识符。这是一个可以用来区分不同消费者的字符串。如果你不指定,RabbitMQ 会自动生成一个。
- autoAck bool
-
- 是否自动确认。当设置为 true 时,每接收到一条消息后,消费者会自动发送一个确认信号给 RabbitMQ。当设置为 false 时,你需要在处理完消息后手动调用 msg.Ack(false) 来发送确认信号。
- exclusive bool
-
- 是否独占。当设置为 true 时,这个消费者将独占这个队列,其他消费者(即使是在其他的连接和通道上)都不能从这个队列中消费消息。
- noLocal bool
-
- 如果设置为 true,则表示不能将同一连接中发送到该队列的消息传递给这个连接的消费者。
- noWait bool
-
- 如果设置为 true,在执行 Consume 操作时不会等待 RabbitMQ 的响应。
- args amqp.Table
-
- 一个可选的参数表,用于传递一些额外的参数给 RabbitMQ。
ch.Consume 方法的返回值:
- msgs <-chan amqp.Delivery
-
- 这是一个消息通道。你可以从这个通道中读取到队列中的消息。每个消息都是一个 amqp.Delivery 实例,它包含了消息的内容和一些额外的属性。
- err error
-
- 如果在调用 Consume 方法时发生了错误,这个值会包含具体的错误信息。
以下是一个使用 ch.Consume 方法的 Go 代码示例:
package main
import (
"github.com/rabbitmq/amqp091-go"
"log"
)
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(
"test_queue",
false,
false,
false,
false,
nil,
)
failOnError(err, "Failed to declare a queue")
msgs, err := ch.Consume(
q.Name,
"",
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
select {}
}
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
在这个示例中,我们创建了一个名为 test_queue 的队列,并从这个队列中消费消息。我们设置 auto-ack 为 true,这意味着消费者会自动确认每一条接收到的消息。我们从 msgs 通道中读取消息,并在一个 Goroutine 中处理这些消息。
ch.Confirm
ch.Confirm 方法是 github.com/rabbitmq/amqp091-go 库中用于开启 RabbitMQ 的生产者确认(Publisher Acknowledgements)模式的方法。当生产者确认模式被开启后,RabbitMQ 服务器会对每一条发布到交换机的消息发送确认消息,告诉生产者这条消息是否已经被成功处理。
以下是 ch.Confirm 方法的参数详细介绍:
参数:
- noWait bool
-
- 如果设置为 true,则生产者不会等待 RabbitMQ 的确认消息。这意味着生产者不会知道消息是否已经被成功处理。
- 如果设置为 false,则生产者会等待 RabbitMQ 的确认消息。这意味着生产者会知道每一条消息是否已经被成功处理。
返回值:
- err error
-
- 如果在调用 ch.Confirm 方法时发生了错误,这个值会包含具体的错误信息。
为什么参数传 false 反而是开启而不是关闭?
ch.Confirm 方法的 noWait 参数并不是用来控制生产者确认模式是否开启的。调用 ch.Confirm 方法本身就意味着你想开启生产者确认模式。
noWait 参数实际上控制的是生产者是否等待 RabbitMQ 的确认消息:
- 当 noWait 为 true 时,生产者不会等待 RabbitMQ 的确认消息。这意味着生产者不会知道消息是否已经被成功处理,但这样可以获得更高的发布消息的吞吐量。
- 当 noWait 为 false 时,生产者会等待 RabbitMQ 的确认消息。这意味着生产者会知道每一条消息是否已经被成功处理,从而可以确保消息的可靠传递。
因此,noWait 参数的名称和它的作用是一致的:它控制的是生产者是否“不等待”(noWait)RabbitMQ 的确认消息。
示例:
err := ch.Confirm(false)
if err != nil {
log.Fatalf("Failed to put channel into confirm mode: %s", err)
}
confirms := ch.NotifyPublish(make(chan amqp.Confirmation, 1))
// ... publish messages ...
confirm := <-confirms
if confirm.Ack {
log.Println("Message confirmed with tag", confirm.DeliveryTag)
} else {
log.Println("Message failed to confirm with tag", confirm.DeliveryTag)
}
在这个示例中,我们首先通过调用 ch.Confirm(false) 开启了生产者确认模式,并指定生产者会等待 RabbitMQ 的确认消息。然后,我们创建了一个 amqp.Confirmation 类型的通道,并告诉 RabbitMQ 我们想要通过这个通道接收确认消息。最后,我们从这个通道中读取确认消息,并根据 confirm.Ack 的值判断消息是否已经被成功处理。
Delivery结构体
以下是 amqp.Delivery 结构体的关键方法和属性的详细介绍,以表格形式展示:
方法:
名称 | 函数签名 | 描述 |
---|---|---|
Ack | func (d Delivery) Ack(multiple bool) error | 发送一个确认信号(acknowledgement)给 RabbitMQ,表示这条消息已经被成功处理。multiple 参数指定是否确认所有小于当前消息的 DeliveryTag 的消息。 |
Nack | func (d Delivery) Nack(multiple bool, requeue bool) error | 发送一个负面确认信号(negative acknowledgement)给 RabbitMQ。multiple 参数指定是否拒绝所有小于当前消息的 DeliveryTag 的消息。requeue 参数指定是否将消息重新放入队列。 |
Reject | func (d Delivery) Reject(requeue bool) error | 拒绝一条消息,并可选择是否将其重新放入队列。requeue 参数指定是否将消息重新放入队列。 |
属性:
名称 | 类型 | 描述 |
---|---|---|
ConsumerTag | string | 消费者的标识符。 |
DeliveryTag | uint64 | 消息的唯一标识符,用于在确认消息时标识这条消息。 |
Redelivered | bool | 如果这条消息是因为之前的消费者没有确认而被重新发送的,那么这个属性的值为 true。 |
Exchange | string | 消息发布到的交换机的名称。 |
RoutingKey | string | 用于发布消息时的路由键。 |
Body | []byte | 消息的主体内容。 |
MessageId | string | 消息的唯一标识符。 |
Timestamp | time.Time | 消息的时间戳。 |
ContentType | string | 消息内容的 MIME 类型。 |
ContentEncoding | string | 消息内容的编码方式。 |
Headers | amqp.Table | 消息的头部信息,是一个键值对的集合。 |
请注意,amqp.Delivery 结构体中还包含其他一些属性,但这些是最常用和关键的属性和方法。
以下是一个完整的 Go 代码示例,展示了如何使用 amqp.Delivery 结构体中的各个方法和属性。每个方法或属性的中文描述已以注释形式标注在相应的代码行后面:
package main
import (
"fmt"
"github.com/rabbitmq/amqp091-go"
"log"
"time"
)
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(
"test_queue",
false,
false,
false,
false,
nil,
)
failOnError(err, "Failed to declare a queue")
msgs, err := ch.Consume(
q.Name,
"",
false, // auto-ack set to false, we will ack manually
false,
false,
false,
nil,
)
failOnError(err, "Failed to register a consumer")
for d := range msgs {
// 消费者的标识符
fmt.Println("ConsumerTag:", d.ConsumerTag) // ConsumerTag:
// 消息的唯一标识符,用于在确认消息时标识这条消息
fmt.Println("DeliveryTag:", d.DeliveryTag) // DeliveryTag: 1
// 如果这条消息是因为之前的消费者没有确认而被重新发送的,那么这个属性的值为 true
fmt.Println("Redelivered:", d.Redelivered) // Redelivered: false
// 消息发布到的交换机的名称
fmt.Println("Exchange:", d.Exchange) // Exchange:
// 用于发布消息时的路由键
fmt.Println("RoutingKey:", d.RoutingKey) // RoutingKey: test_queue
// 消息的主体内容
fmt.Println("Body:", string(d.Body)) // Body: Hello, RabbitMQ!
// 消息的唯一标识符
fmt.Println("MessageId:", d.MessageId) // MessageId:
// 消息的时间戳
fmt.Println("Timestamp:", d.Timestamp.Format(time.RFC3339)) // Timestamp: 2022-01-01T12:34:56Z
// 消息内容的 MIME 类型
fmt.Println("ContentType:", d.ContentType) // ContentType: text/plain
// 消息内容的编码方式
fmt.Println("ContentEncoding:", d.ContentEncoding) // ContentEncoding:
// 消息的头部信息,是一个键值对的集合
fmt.Println("Headers:", d.Headers) // Headers: map[]
// 发送一个确认信号(acknowledgement)给 RabbitMQ,表示这条消息已经被成功处理
// multiple 参数指定是否确认所有小于当前消息的 DeliveryTag 的消息
err := d.Ack(false) // Acknowledgement sent
failOnError(err, "Failed to acknowledge the message")
// 以下是如何使用 Nack 和 Reject 方法的示例,但在实际代码中,通常只需要使用 Ack 方法
// 发送一个负面确认信号(negative acknowledgement)给 RabbitMQ
err = d.Nack(false, false) // Negative Acknowledgement sent
failOnError(err, "Failed to negatively acknowledge the message")
// 拒绝一条消息,并可选择是否将其重新放入队列
err = d.Reject(false) // Message rejected
failOnError(err, "Failed to reject the message")
}
}
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
在这个示例中,我们首先创建了一个名为 test_queue 的队列,并从这个队列中消费消息。我们设置 autoAck 参数为 false,这意味着我们需要在处理完每条消息后显式地调用 d.Ack(false) 来发送确认信号。
请注意,Nack 和 Reject 方法的示例代码在这个示例中被注释掉了,因为在实际的消息处理逻辑中,通常只需要使用 Ack 方法来确认消息。如果你想要测试 Nack 和 Reject 方法,可以取消相应代码的注释。
这个示例展示了如何在使用 Go 语言和 github.com/rabbitmq/amqp091-go 库消费消息时使用 amqp.Delivery 结构体中的各个方法和属性。