golang rabbitMQ 生产者复用channel以及生产者组分发策略

文章详细描述了在Go语言中使用rabbitMQ时如何实现连接和channel的复用,以提高效率和可靠性。通过创建Connection和Channel结构体,初始化时动态配置生产者和消费者的连接数及channel数量。消费者和生产者分别通过工作模式运行,生产者发送消息时随机选择connection和channel,确保负载均衡。代码示例展示了具体实现过程。
摘要由CSDN通过智能技术生成

引用的是rabbitMQ官方示例的库github.com/rabbitmq/amqp091-go

在网络编程中我们知道tcp连接的创建、交互、销毁等相关操作的"代价"都是很高的,所以就要去实现如何复用这些连接,并要做到高效并可靠。

预期效果:

项目初始化构建时可以自定义选择生产者开启多个connection,每个connection可以启动多少个channel【都是全局复用的】,因为rabbitMQ所有的命令都是基本都是通过channel去操作完成的,所以这个channel很重要,也是我们想要复用的重点。

初始化创建完connection和channel后,当生产者需要发送一条消息的时候,我们可以通过一些策略去选择它发送到哪个connection和channel,我这里采用的就是随机选择,也可以采用哈希取模、轮询权重算法等,这个可以根据自身业务来做。

我简单画了一个效果图:

定义RabbitMQ的Config、Connection、Channel结构体

type Config struct {
    Host     string
    Port     int
    User     string
    Password string
}

type Channel struct {
    m  *sync.Mutex
    ch *amqp.Channel
}

type Connection struct {
    ctx  context.Context
    n    int
    conn *amqp.Connection
    ch   []*Channel
}

实例化RabbitMQ结构体

func (mq *Connection) New(config Config) (rabbitmq *Connection) {

    configString := fmt.Sprintf("amqp://%s:%s@%s:%d/", config.User, config.Password, config.Host, config.Port)

    conn, err := amqp.Dial(configString)
    if err != nil {
        log.Panicf("amqp connect error: %v \n", err)
    }

    rabbitmq = &Connection{
        ctx:  context.Background(),
        conn: conn,
    }

    return
}

一、创建消费者

// ConsumeWithWork rabbitmq消费消息[work模式 channelNums可以设置当前连接开启多少个channel]
func (mq *Connection) ConsumeWithWork(queueName string, channelNums int) {
    for i := 0; i < channelNums; i++ {
        go func(i int) {

            ch, err := mq.conn.Channel()
            if err != nil {
                log.Panicf("amqp open a channel error: %v \n", err)
            }

            q, err := ch.QueueDeclare(
                queueName, // name
                true,      // durable
                false,     // delete when unused
                false,     // exclusive
                false,     // no-wait
                nil,       // arguments
            )
            if err != nil {
                log.Panicf("amqp declare a queue error: %v \n", err)
            }

            err = ch.Qos(
                1,     // prefetch count
                0,     // prefetch size
                false, // global
            )
            if err != nil {
                log.Panicf("amqp set QoS error: %v \n", err)
            }

            msg, err := ch.Consume(
                q.Name, // queue
                "",     // consumer
                false,  // auto-ack
                false,  // exclusive
                false,  // no-local
                false,  // no-wait
                nil,    // args
            )
            if err != nil {
                log.Panicf("amqp register a consumer error: %v \n", err)
            }

            log.Printf(" [work-%d] Waiting for messages. To exit press CTRL+C", i)

            for d := range msg {
                time.Sleep(time.Second)
                fmt.Printf("[work-%d] Received a message: %s \n", i, d.Body)
                err = d.Ack(false)
                if err != nil {
                    log.Printf("work_one Ack Err: %v", err)
                }
            }
        }(i)
    }

    var forever chan struct{}
    <-forever
}

二、创建生产者组

// NewPlusherGroups 创建生产者组
func NewPlusherGroups(config Config, connNums, channelNums int) (plusherGroups map[int]*Connection) {

    plusherGroups = make(map[int]*Connection, connNums)

    for i := 0; i < connNums; i++ {

        var rabbitmq *Connection
        rabbitmq = rabbitmq.New(config)
        rabbitmq.n = i

        for cN := 0; cN < channelNums; cN++ {
            ch, err := rabbitmq.conn.Channel()
            if err != nil {
                log.Panicf("amqp open a channel error: %v \n", err)
            }
            rabbitmq.ch = append(rabbitmq.ch, &Channel{ch: ch, m: &sync.Mutex{}})
        }

        plusherGroups[i] = rabbitmq
    }
    return
}

三、将消息随机分发给不同的connection、channel

// SendMessageWithWork 生产者发送消息[work模式+(many conn and many channel)]
func SendMessageWithWork(plusherGroups map[int]*Connection, queueName, body string) bool {

    if plusherGroups == nil {
        log.Panicln("SendMessageWithWork plusherGroups params is nil!")
    }

    rand.Seed(time.Now().UnixNano())

    //获取连接个数
    connNums := len(plusherGroups)

    //随机分配一个连接对象
    randConnIndex := rand.Intn(connNums)

    //选择随机分配的连接对象
    conn := plusherGroups[randConnIndex]

    //获取当前对象的channel个数
    channelNums := len(conn.ch)

    //随机分配一个channel对象
    randChannelIndex := rand.Intn(channelNums)

    //选择随机分配的channel
    ch := conn.ch[randChannelIndex]

    //既然采用了发布者复用conn、channel的形式那么一定要加锁处理
    //这里为每个对象的操作进行加锁(非线程安全,不加锁会报错的)
    //至于在存在并发竞争的情况下会存在一定性能损耗,但是我们配置好适量的conn和channel这个基本可以忽略
    ch.m.Lock()
    defer ch.m.Unlock()

    q, err := ch.ch.QueueDeclare(
        queueName, // name
        true,      // durable
        false,     // delete when unused
        false,     // exclusive
        false,     // no-wait
        nil,       // arguments
    )
    if err != nil {
        log.Panicf("amqp declare a queue error: %v \n", err)
    }

    body = fmt.Sprintf("conn[%d] channel[%d] send message : %s", randConnIndex, randChannelIndex, body)
    err = ch.ch.PublishWithContext(conn.ctx,
        "",     // exchange
        q.Name, // routing key
        false,  // mandatory
        false,
        amqp.Publishing{
            DeliveryMode: amqp.Persistent,
            ContentType:  "text/plain",
            Body:         []byte(body),
        })
    if err != nil {
        log.Panicf("amqp publish a message error: %v \n", err)
    }

    return true
}

四、main函数调用消费者

package main

import (
    rabbitmq "go-test/rabbitmq/package"
)

func main()  {

    queueName := "task_queue"

    config := rabbitmq.Config{
        Host: "192.168.6.103",
        Port: 5672,
        User: "root",
        Password: "root",
    }

    var mq *rabbitmq.Connection
    mq = mq.New(config)

    //开启N个消费者
    mq.ConsumeWithWork(queueName, 3)
}

五、main函数调用生产者组发送消息

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    rabbitmq "go-test/rabbitmq/package"
    "net/http"
    "time"
)

func main()  {

    var messageNo int

    queueName := "task_queue"

    config := rabbitmq.Config{
        Host: "192.168.6.103",
        Port: 5672,
        User: "root",
        Password: "root",
    }

    //conn连接数
    connNums := 2
    //channel连接数
    channelNums := 3

    //启动N个不同conn的连接,并且每个连接对应的channel为N个的rabbitmq实例
    plusherGroup := rabbitmq.NewPlusherGroups(config, connNums, channelNums)


    e := gin.Default()
    e.GET("/", func(c *gin.Context) {

        body := fmt.Sprintf("这是第%d条消息...", messageNo)

        if rabbitmq.SendMessageWithWork(plusherGroup, queueName, body) == true {
            messageNo++
            c.JSON(200, gin.H{
                "code": 200,
                "msg": "success",
            })
        } else {
            c.JSON(200, gin.H{
                "code": 500,
                "msg": "error",
            })
        }
    })

    server := &http.Server{
        Addr:         ":18776",
        Handler:      e,
        ReadTimeout:  time.Minute,
        WriteTimeout: time.Minute,
    }
    if err := server.ListenAndServe(); err != nil {
        panic(any("HttpServer启动失败"))
    }
}

执行流程:

  1. 启动消费者进程

可以看到我们用3个协程开启了3个work,也就是对应了3个channel

  1. 启动生产者组进程

这里用的gin框架,正常启动

我们可以看到rabbitMQ的控制台中,一共3个连接,1个是消费者进程,另外2个是生产者组进程,这2个正好和我们上面配置的connNums参数匹配

我们可以看到rabbitMQ的控制台中,一共9个channel,3个是消费者进程,另外6个是生产者组进程,这6个正好和我们上面配置的channelNums参数匹配

  1. 调用发送消息

ab.exe -n 1000 -c 1000 http://127.0.0.1:18776/

我们来看消费者日志打印情况,标红的可以证明我们在发送消息时让生产者根据我们的随机分配策略选择connection和channel

golang中,使用RabbitMQ实现多个生产者的方式有多种方法。 一种常见的方法是使用分布式锁。在这种方法中,每个生产者送消息之前尝试获取一个全局锁。只有一个生产者能够成功获取锁,然后送消息到RabbitMQ。其他没有获取到锁的生产者会等待一定的时间后再尝试获取锁。这样可以确保每次只有一个生产者能够送消息,避免多个生产者同时送导致消息的乱序或重复。 另一种方法是使用消息队列的事务机制。每个生产者送消息之前,开始一个事务并将消息送到RabbitMQ。然后,生产者提交事务。RabbitMQ会确保只有一个生产者能够成功提交事务,其他生产者在提交事务时会失败。这样可以确保每次只有一个生产者能够成功送消息,避免重复消息的问题。 还有一种方法是使用RabbitMQ布-订阅模式。每个生产者将消息送到一个特定的交换机中,然后交换机将消息广播给所有的消费者。这种方式下,多个生产者可以同时向交换机送消息,而不需要进行同步或者互斥操作。这种方式适用于需要将消息广播给多个消费者的场景。 无论使用哪种方式,多个生产者可以并送消息到RabbitMQ,提高整体系统的吞吐量和并性能。但是需要注意的是,当多个生产者送到同一个队列时,可能会引消息的重复或乱序的问题。因此,在设计多个生产者的架构时,需要根据具体场景来选择合适的方式,并进行适当的消息幂等性设计,以保证消息的一致性和可靠性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值