生产者
gin结合rabbitmq(生产者完整代码)
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/gin-gonic/gin"
amqp "github.com/rabbitmq/amqp091-go"
)
// 处理错误,如果有错误则输出错误信息并终止程序
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
var ch *amqp.Channel // 声明一个全局变量用于存储 RabbitMQ 频道
// 初始化 RabbitMQ 连接和队列
func initmq() {
// 连接到 RabbitMQ 服务器
conn, err := amqp.DialConfig("amqp://testmq:testmq@127.0.0.1:5672/", amqp.Config{
Heartbeat: 60 * time.Second, // 设置心跳间隔为 60 秒,防止连接超时
})
failOnError(err, "Failed to connect to RabbitMQ")
// 创建一个通道
ch, err = conn.Channel()
failOnError(err, "Failed to open a channel")
// 声明一个队列
_, err = ch.QueueDeclare(
"hello", // 队列名称
true, // 是否持久化
false, // 是否自动删除(当没有消费者时删除)
false, // 是否排他(仅当前连接可用)
false, // 等待服务器确认队列创建
nil, // 额外参数
)
failOnError(err, "Failed to declare a queue")
// 监听 RabbitMQ 连接是否被关闭
notifyClose := conn.NotifyClose(make(chan *amqp.Error))
go func() {
err := <-notifyClose
if err != nil {
fmt.Printf("连接关闭,心跳超时或其他原因: %v\n", err)
} else {
fmt.Println("连接正常关闭")
}
}()
}
func main() {
initmq() // 初始化 RabbitMQ 连接
//------------------------------------------------------------------------
r := gin.New() // 创建 Gin 路由引擎
r.GET("/mq", func(c *gin.Context) { // 定义一个 GET 请求的接口
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 设置 5 秒的上下文超时
defer cancel()
body := "bodyFrom" // 消息内容
err := ch.PublishWithContext(ctx,
"", // 交换机(此处为空,表示使用默认交换机)
"hello", // 路由键(即队列名称)
false, // 是否强制投递(mandatory)
false, // 是否立即投递(immediate)
amqp.Publishing{
DeliveryMode: amqp.Persistent, // 设置消息持久化
ContentType: "text/plain", // 消息内容类型
Body: []byte(body), // 消息内容
})
failOnError(err, "Failed to publish a message") // 发送消息失败时打印错误信息
})
r.Run() // 启动 Gin 服务器
}
连接rabbittmq并配置心跳
conn, err := amqp.DialConfig("amqp://testmq:testmq@122.51.231.250:5672/", amqp.Config{
Heartbeat: 60 * time.Second, // 设置心跳间隔为 60 秒,防止连接超时
})
心跳一般配置为60秒最佳
声明队列
_, err = ch.QueueDeclare(
"hello", // 队列名称
true, // 是否持久化
false, // 是否自动删除(当没有消费者时删除)
false, // 是否排他(仅当前连接可用)
false, // 等待服务器确认队列创建
nil, // 额外参数
)
这里有只需要管前两个参数,后面保持默认即可。或者只需要管第一个参数,声明一下队列名称。第二个参数是否持久化,保持默认开启即可。它会把消息持久化到内存中,重启rabbitmq消息还在。
使用队列(向队列发送消息)
err := ch.PublishWithContext(ctx,
"", // 交换机(此处为空,表示使用默认交换机)
"hello", // 路由键(即队列名称)
false, // 是否强制投递(mandatory)
false, // 是否立即投递(immediate)
amqp.Publishing{
DeliveryMode: amqp.Persistent, // 设置消息持久化
ContentType: "text/plain", // 消息内容类型
Body: []byte(body), // 消息内容
})
消费者
消费者完整代码
package main
import (
"fmt"
"log"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
// 处理错误并打印日志,如果发生错误则终止程序
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
// 连接到 RabbitMQ 服务器,并设置心跳间隔为 60 秒,防止连接超时
conn, err := amqp.DialConfig("amqp://testmq:testmq@127.0.0.1:5672/", amqp.Config{
Heartbeat: 60 * time.Second,
})
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close() // 在函数结束时关闭连接
// 创建一个通道(Channel),通过该通道与 RabbitMQ 进行通信
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close() // 在函数结束时关闭通道
// 声明一个队列
q, err := ch.QueueDeclare(
"hello", // 队列名称
true, // 是否持久化
false, // 是否自动删除(当没有消费者连接时是否删除队列)
false, // 是否排他(仅限当前连接使用)
false, // 等待服务器确认队列创建
nil, // 其他参数
)
failOnError(err, "Failed to declare a queue")
// 设置消费端的 QoS(质量保证服务)
err = ch.Qos(
30, // 预取消息数,限制每次最多分配 30 条消息,防止消费者积压过多未处理的消息
0, // 预取大小(0 表示不限制单条消息大小)
false, // 是否应用到整个连接(false 表示只对当前通道生效)
)
failOnError(err, "Failed to set QoS")
// 注册消费者,监听队列中的消息
msgs, err := ch.Consume(
q.Name, // 队列名称
"", // 消费者名称(留空表示让 RabbitMQ 自动分配)
false, // 是否自动确认(auto-ack: false 需要手动确认消息)
false, // 是否排他(exclusive: false 允许多个消费者监听同一队列)
false, // 是否本地消费(no-local: false 允许连接自己消费自己发布的消息)
false, // 是否阻塞(no-wait: false 表示等待服务器返回确认)
nil, // 额外参数
)
failOnError(err, "Failed to register a consumer")
// 创建一个阻塞通道,防止主协程退出
var forever chan struct{}
// 启动一个 goroutine 监听 RabbitMQ 消息
go func() {
for d := range msgs {
// 业务逻辑处理部分
fmt.Println(d.Body) // 打印收到的消息
// 手动确认消息已被正确处理,false 表示仅确认该条消息
d.Ack(false)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever // 阻塞主线程,使程序保持运行
}
预取值
err = ch.Qos(
30, // 预取消息数,限制每次最多分配 30 条消息,防止消费者积压过多未处理的消息
0, // 预取大小(0 表示不限制单条消息大小)
false, // 是否应用到整个连接(false 表示只对当前通道生效)
)
这里示例为30,实际运用时,要考消费者数量与消息堆积。经验值1-300,通常100以内都够用。如果不知道如何填可以3,填1的话均等分发最保守,性能太差。
手动确认消息
msgs, err := ch.Consume(
q.Name, // 队列名称
"", // 消费者名称(留空表示让 RabbitMQ 自动分配)
false, // 是否自动确认(auto-ack: false 需要手动确认消息)
false, // 是否排他(exclusive: false 允许多个消费者监听同一队列)
false, // 是否本地消费(no-local: false 允许连接自己消费自己发布的消息)
false, // 是否阻塞(no-wait: false 表示等待服务器返回确认)
nil, // 额外参数
)
第二个参数为false
d.Ack(false)
消息被成功消费后执行d.Ack(false)确认