go和rabbitMq 消息队列封装使用
最近在开发一个购车相关的app,使用到go和rabbitMQ记录分享。
项目技术栈:
- 搜索部分用到ES
- go框架使用 go-zero
- 消息队列:rabbitMq
- 数据库:mysql、redis
go库 github.com/rabbitmq/amqp091-go
rabbitMQ go客户端初始化代码
package rabbitMq
import (
"errors"
"fmt"
"log"
"math"
"reflect"
"sync"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
var (
AmqpClient *amqpConnection
clientMutex sync.Mutex
)
type amqpConnection struct {
*amqp.Connection
initChannel []func() error
amqpError chan *amqp.Error
}
func New() *amqpConnection {
return AmqpClient
}
func NewChannel() (ch *amqp.Channel, err error) {
if AmqpClient == nil {
return nil, errors.New("rabbitMq 未初始化连接")
}
if AmqpClient.IsClosed() {
return nil, errors.New("rabbitMq 连接已关闭")
}
return AmqpClient.Channel()
}
func Init(url string, initChannel func() error) (err error) {
clientMutex.Lock()
defer clientMutex.Unlock()
if AmqpClient == nil {
AmqpClient = &amqpConnection{}
}
if AmqpClient.Connection == nil {
dialConfig := amqp.Config{
Heartbeat: 30 * time.Second, // 设置心跳间隔为30秒
}
AmqpClient.Connection, err = amqp.DialConfig(url, dialConfig)
if err != nil {
return err
}
// 监听连接关闭并重连
AmqpClient.monitorConnection(url)
}
if AmqpClient != nil && !AmqpClient.IsClosed() && initChannel != nil {
AmqpClient.initChannel = append(AmqpClient.initChannel, initChannel)
return initChannel()
} else if AmqpClient != nil && !AmqpClient.IsClosed() && len(AmqpClient.initChannel) > 0 {
for _, f := range AmqpClient.initChannel {
if err := f(); err != nil {
return err
}
}
}
return
}
// 初始化交换机队列
func InitExchangeQueue(e *ExchangeDeclare, queues ...QueueDeclare) (ch *amqp.Channel, err error) {
ch, err = NewChannel()
if err != nil {
return nil, err
}
defer func() {
if r := recover(); r != nil {
ch.Close()
return
}
if err != nil {
ch.Close()
return
}
}()
if e != nil {
err = ch.ExchangeDeclare(
e.Exchange,
e.Type,
e.Durable, // durable
e.AutoDelete, // auto-delete
e.Internal, // internal
e.NoWait, // noWait
e.Arguments,
)
if err != nil {
return nil, err
}
}
if len(queues) > 0 {
err = exchangeBindQueue(ch, e, queues...)
if err != nil {
return nil, err
}
}
// 监听通道关闭(便于日志与排查)
// closeCh := make(chan *amqp.Error)
// ch.NotifyClose(closeCh)
// go func() {
// if err := <-closeCh; err != nil {
// log.Printf("RabbitMQ 通道关闭: %v", err)
// }
// }()
return
}
func exchangeBindQueue(ch *amqp.Channel, e *ExchangeDeclare, queues ...QueueDeclare) (err error) {
for _, v := range queues {
q, err := ch.QueueDeclare(
v.Queue,
v.Durable, // durable
v.AutoDelete, // delete when unused
v.Exclusive, // exclusive
v.NoWait, // no-wait
v.Arguments, // arguments
)
if err != nil {
return err
}
err = ch.QueueBind(
q.Name,
v.QueueBind.RoutingKey,
e.Exchange,
v.QueueBind.NoWait,
v.QueueBind.Arguments,
)
if err != nil {
return err
}
if v.BasicQos != nil {
err = ch.Qos(
v.BasicQos.PrefetchCount, // prefetch count
v.BasicQos.PrefetchSize, // prefetch size
v.BasicQos.Global, // global
)
if err != nil {
return err
}
}
if v.Func != nil {
msgs, err := ch.Consume(
q.Name,
v.BasicConsume.ConsumerTag,
v.BasicConsume.AutoAck, // auto-ack set to false for manual acknowledgment
v.BasicConsume.Exclusive,
v.BasicConsume.NoLocal,
v.BasicConsume.NoWait,
v.BasicConsume.Arguments,
)
if err != nil {
return err
}
go func() {
for d := range msgs {
v.Func(ch, d)
}
}()
}
}
return
}
// monitorBlocked 会监听 RabbitMQ 的阻塞和解除阻塞事件。
// 当服务器启用流控时(Active 为 true),连接会被阻塞;当流控解除时(Active 为 false),连接恢复。
func (that *amqpConnection) monitorBlocked() {
// 创建一个 channel 用于接收阻塞通知
blockedChan := make(chan amqp.Blocking)
// 注册监听阻塞通知,NotifyBlocked 会返回传入的 channel
that.Connection.NotifyBlocked(blockedChan)
// 启动一个 goroutine 处理阻塞事件
go func() {
for b := range blockedChan {
if b.Active {
// 当 Active 为 true 时表示连接被阻塞
log.Printf("RabbitMQ 连接被阻塞: %v", b.Reason)
} else {
// 当 Active 为 false 时表示阻塞解除
log.Printf("RabbitMQ 连接阻塞解除")
}
}
}()
}
// 监控 RabbitMQ 连接关闭的情况,并尝试重新连接
func (that *amqpConnection) monitorConnection(url string) {
go func(url string) {
that.amqpError = make(chan *amqp.Error)
closeErr := <-that.Connection.NotifyClose(that.amqpError)
if closeErr != nil {
log.Printf("RabbitMQ连接关闭: %v. Attempting to reconnect...", closeErr)
}
that.reconnect(url)
}(url)
}
func (that *amqpConnection) reconnect(url string) {
maxRetries := 92
var err error
var wait time.Duration
var y float64
for i := range maxRetries {
if i <= 60 {
wait = time.Second
} else {
wait = time.Duration(math.Pow(2, float64(y))) * time.Second
y++
}
time.Sleep(wait)
DeferClose()
err = Init(url, nil)
if err != nil {
log.Printf("重连 RabbitMQ 失败,尝试第 %d 次 err: %v", i+1, err)
continue
}
log.Printf("成功重连到 RabbitMQ")
return
}
log.Printf("无法重连到 RabbitMQ,错误: %v", err)
}
// 关闭 connection
func DeferClose() {
if AmqpClient != nil && AmqpClient.Connection != nil {
AmqpClient.Connection.Close()
AmqpClient.Connection = nil
}
}
// SetChannel 将 *amqp.Channel 塞到 chStruct 指向的结构体的指定字段中。
// 要求:
// 1. chStruct 必须是“指向结构体的指针”(*T 且 T 为 struct)
// 2. fieldName 必须存在且为可导出字段(否则不可设置)
// 3. 字段类型必须能接收 *amqp.Channel(类型可赋值)
func SetChannel(chStruct any, fieldName string, ch *amqp.Channel) error {
if chStruct == nil {
return errors.New("chStruct is nil")
}
if ch == nil {
return errors.New("channel is nil")
}
// 必须是指向结构体的指针
t := reflect.TypeOf(chStruct)
v := reflect.ValueOf(chStruct)
if t.Kind() != reflect.Ptr {
return errors.New("chStruct 必须是指向结构体的指针")
}
if v.IsNil() {
return errors.New("chStruct 不能是 nil 指针")
}
elemType := t.Elem()
if elemType.Kind() != reflect.Struct {
return errors.New("chStruct 必须指向结构体")
}
// 查找字段
field, ok := elemType.FieldByName(fieldName)
if !ok {
return fmt.Errorf("字段 %q 不存在于 %s", fieldName, elemType.Name())
}
// 未导出字段无法设置(CanSet=false)
fieldVal := v.Elem().FieldByIndex(field.Index)
if !fieldVal.CanSet() {
return fmt.Errorf("字段 %q 不可设置(可能未导出)", fieldName)
}
// 类型可赋值检查:值类型必须“可赋给”字段类型
chVal := reflect.ValueOf(ch)
if !chVal.Type().AssignableTo(fieldVal.Type()) {
return fmt.Errorf("字段 %q 的类型是 %s,无法接收 %s",
fieldName, fieldVal.Type(), chVal.Type())
}
fieldVal.Set(chVal)
return nil
}
// 声明一个临时、独占、自动删除的队列用于接收响应
func ReplyTo(ch *amqp.Channel) (msgs <-chan amqp.Delivery, q amqp.Queue, err error) {
q, err = ch.QueueDeclare("", false, false, true, false, nil)
if err != nil {
return
}
msgs, err = ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
if err != nil {
return
}
return
}
type ExchangeDeclare struct {
Exchange string
Type string
Passive bool
Durable bool
AutoDelete bool
Internal bool
NoWait bool
Arguments amqp.Table
QueueDeclares []QueueDeclare
FieldName string
}
type QueueDeclare struct {
Queue string
Passive bool
Durable bool
Exclusive bool
AutoDelete bool
NoWait bool
Arguments amqp.Table
QueueBind QueueBind
BasicConsume BasicConsume
BasicQos *BasicQos
Func func(*amqp.Channel, amqp.Delivery)
}
type QueueBind struct {
Queue string
Exchange string
RoutingKey string
NoWait bool
Arguments amqp.Table
}
type BasicConsume struct {
Queue string
ConsumerTag string
NoLocal bool
AutoAck bool
Exclusive bool
NoWait bool
Arguments amqp.Table
}
type BasicQos struct {
PrefetchCount int
PrefetchSize int
Global bool
}
配置部分大家可以按照自己的框架配置
package config
import (
"base/pkg/rabbitMq"
amqp "github.com/rabbitmq/amqp091-go"
)
type exchange struct {
ExchangeMap map[string]*rabbitMq.ExchangeDeclare
}
func NewExchange() *exchange {
return &exchange{}
}
func (that *exchange) GetExchange(exchangeName string) (e *rabbitMq.ExchangeDeclare) {
if v, ok := that.ExchangeMap[exchangeName]; ok {
return v
}
return nil
}
func (that *exchange) Get() map[string]*rabbitMq.ExchangeDeclare {
return that.ExchangeMap
}
func (that *exchange) Set(exchangeName string, e *rabbitMq.ExchangeDeclare) *exchange {
that.ExchangeMap[exchangeName] = e
return that
}
func (that *exchange) SetFunc(exchangeName string, f func(*amqp.Channel, amqp.Delivery)) *exchange {
if _, ok := that.ExchangeMap[exchangeName]; ok {
for i := range that.ExchangeMap[exchangeName].QueueDeclares {
that.ExchangeMap[exchangeName].QueueDeclares[i].Func = f
}
}
return that
}
// 车源MQ
const (
SyncCar = "SyncCar"
SyncCarMaxConcurrentJobs = 10 // 最大并发处理数量
)
func (that *exchange) CarExchange() *exchange {
that.ExchangeMap = map[string]*rabbitMq.ExchangeDeclare{
//同步车源
SyncCar: {
Exchange: "car_sync_exchange",
Type: "topic",
Durable: true,
Arguments: amqp.Table{},
QueueDeclares: []rabbitMq.QueueDeclare{
{
Queue: "car_sync_queue",
Durable: true,
BasicQos: &rabbitMq.BasicQos{PrefetchCount: SyncCarMaxConcurrentJobs, PrefetchSize: 0, Global: false},
},
},
FieldName: SyncCar,
},
}
return that
}
// 网关MQ
const (
OrderClose = "OrderClose"
OrderComplete = "OrderComplete"
PaySettlement = "PaySettlement"
OrderCreate = "OrderCreate"
)
func (that *exchange) GatewayExchange() *exchange {
that.ExchangeMap = map[string]*rabbitMq.ExchangeDeclare{
// 下单使用
OrderCreate: {
Exchange: "order_create_exchange",
Type: "direct",
Durable: false,
Arguments: amqp.Table{},
QueueDeclares: []rabbitMq.QueueDeclare{
{
Queue: "order_create_queue",
Durable: false,
BasicQos: &rabbitMq.BasicQos{
PrefetchCount: 1, // prefetch count
PrefetchSize: 0, // prefetch size
Global: false, // global
},
},
},
FieldName: OrderCreate,
},
}
return that
}
示例同步车源数据到ES上消费者
// 初始化 Exchange Queue
func InitChannel() (err error) {
// 获取Exchange配置并设置消费者方法 syncCarConsumer,我这里是获取上面示例配置,大家可以根据自己使用框架来
exchangeConfig := config.NewExchange().CarExchange().SetFunc(config.SyncCar, syncCarConsumer)
for _, v := range exchangeConfig.Get() {
//开始初始化Exchange Queue
ch, err := rabbitMq.InitExchangeQueue(v, v.QueueDeclares...)
if err != nil {
return err
}
}
return
}
var startOnce sync.Once
func syncCarConsumer(ch *amqp.Channel, d amqp.Delivery) {
workChan <- d
}
// 使用工作池限制并发处理var
var workChan = make(chan amqp.Delivery, 2*config.SyncCarMaxConcurrentJobs)
func SyncCarWork(svcCtx *svc.ServiceContext) {
defer func() {
if r := recover(); r != nil {
logx.Errorf("SyncCarConsumer panic: %v", r)
}
}()
// 启动工作协程
for i := range config.SyncCarMaxConcurrentJobs {
go func(i int) {
for d := range workChan {
// 同步ES代码。。。
}
}(i)
}
}
var url = "amqp://" + "Username" + ":" + "Password" + "@" + "RabbitMq Hosts" + "/"
err := rabbitMq.Init(url, InitChannel)
if err != nil {
//链接失败返回错误
return err
}
//链接成功
例子购车下单消息队列
现在新能源车这么火,来做一个购车下单消息队列
订单消费者consumer.go
func GetExchange() (exchange map[string]*rabbitMq.ExchangeDeclare) {
exchangeConfig := config.NewExchange().GatewayExchange().
SetFunc(config.OrderCreate, func(ch *amqp.Channel, d amqp.Delivery) {
// 购车业务处理 。。。
// 发回给消费者回调
body := []byte("下单成功")
err = ch.PublishWithContext(ctx, "", d.ReplyTo, false, false, amqp.Publishing{
ContentType: "application/json",
CorrelationId: d.CorrelationId,
Body: body,
})
if err != nil {
logx.Error("Publish error:", err)
err = d.Nack(false, false)
if err != nil {
logx.Error(" Nack error:", err)
}
return err
}
// Acknowledge message
err = d.Ack(false)
if err != nil {
logx.Error("Ack error:", err)
return err
}
})
return exchangeConfig.Exchange
}
// Init initializes the RabbitMQ connection and channels
func Init(ctx *svc.ServiceContext) error {
var url = "amqp://" + "Username" + ":" + "Password" + "@" + "Hosts" + "/"
err := rabbitMq.Init(url, InitChannel)
if err != nil {
return err
}
return nil
}
// InitChannel initializes RabbitMQ channels and consumers
func InitChannel() error {
exchangeArr := GetExchange()
for _, v := range exchangeArr {
_, err := rabbitMq.InitExchangeQueue(v, v.QueueDeclares...)
if err != nil {
return err
}
}
return nil
}
订单生产者 producer.go
var (
ch *Channel
once sync.Once
)
type Channel struct {
OrderCreate *amqp.Channel //订单关闭
}
func NewCh() *Channel {
once.Do(func() {
ch = &Channel{}
})
return ch
}
func Init() (err error) {
var url = "amqp://" + "Username"+ ":" + "Password" + "@" + "Hosts" + "/"
err = rabbitMq.Init(url, InitChannel)
if err != nil {
logx.Error(err)
return err
}
return
}
func GetExchange() (exchange []*rabbitMq.ExchangeDeclare) {
exchangeConfig := config.NewExchange().GatewayExchange()
exchange = append(exchange, exchangeConfig.GetExchange(config.OrderCreate))
return
}
func InitChannel() (err error) {
exchangeArr := GetExchange()
for _, v := range exchangeArr {
ch, err := rabbitMq.InitExchangeQueue(v, v.QueueDeclares...)
if err != nil {
return err
}
err = rabbitMq.SetChannel(NewCh(), v.FieldName, ch)
if err != nil {
return err
}
}
return
}
type BuyCarReq struct {
CarNo string `json:"CarNo"` // 车源编号
}
type BuyCarRsp struct {
OrderNo string `json:"OrderNo,omitempty"` //订单编号
PayNo string `json:"PayNo,omitempty"` //支付单号
Amount float64 `json:"Amount,omitempty"` //金额
}
// 下单方法 客户端请求调用
func OrderCreateSend(buyCarReq *types.BuyCarReq) (buyCarRsp *types.BuyCarRsp, err error) {
buyCarRsp = &types.BuyCarRsp{}
reqBody, err := json.Marshal(buyCarReq)
if err != nil {
logx.Error("Failed to marshal buyCarReq:", err)
return buyCarRsp, err
}
ch := NewCh().OrderCreate
// 回调临时队列,也可以用固定队列
msgs, q, err := rabbitMq.ReplyTo(ch)
corrId := uuid.New()
//设置下单超时
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// 往rabbitMQ的 order_create_exchange 交换机发送消息
err = ch.PublishWithContext(ctx,
"order_create_exchange", // exchange
"", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "application/json",
CorrelationId: corrId,
ReplyTo: q.Name, //回调队列
Body: reqBody,
})
if err != nil {
//发送失败
return buyCarRsp, err
}
//等待回调
for {
select {
case <-ctx.Done():
logx.Error("Timeout waiting for response")
return buyCarRsp, fmt.Errorf("购买超时,请稍后重试")
case d, ok := <-msgs:
if !ok {
return buyCarRsp, fmt.Errorf("购买失败,请稍后重试")
}
if d.CorrelationId == corrId {
if err = json.Unmarshal(d.Body, &buyCarRsp); err != nil {
return buyCarRsp, err
}
if buyCarRsp.Error != "" {
err = errors.New(buyCarRsp.Error)
}
return buyCarRsp, err
}
}
}
}

浙公网安备 33010602011771号