RabbitMQ消费者性能-预取与并发

时间:2019-08-06 12:11:21

标签: go rabbitmq

我有一个Go应用程序,它处理来自单个RabbitMQ队列的事件。我使用github.com/streadway/amqp RabbitMQ客户端库。

Go应用程序会在大约2-3秒内处理所有消息。如果我从内存中获取消息,则可能并行处理约1000条甚至更多的消息。 但是,不幸的是,RabbitMQ性能更差。 因此,我想更快地使用队列中的消息。

因此,问题是:如何使用github.com/streadway/amqp以最有效的方式使用消息?

据我了解,有两种方法:

  1. 设置高预取

    https://godoc.org/github.com/streadway/amqp#Channel.Qos

    使用单个消费者goroutine

    示例代码:

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()

ch.Qos(
        10000,           // prefetch count
        0,               // prefetch size
        false,           // global
    )

msgs, err := ch.Consume(
  q.Name, // queue
  "",     // consumer
  false,  // NO auto-ack
  false,  // exclusive
  false,  // no-local
  false,  // no-wait
  nil,    // args
)

for d := range msgs {
  log.Printf("Received a message: %s", d.Body)
  err:= processMessage(d)
  if err != nil {
      log.Printf("%s : while consuming task", err)
      d.Nack(false, true)
  } else {
      d.Ack(false)
  }
  continue // consume other messages
}

但是processMessage是否会在这里并行调用?

  1. 催生许多渠道并使用多个消费者
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
var i = 0
for i = 0; i<=100; i++ {
  go func(){
      ch, err := conn.Channel()
      failOnError(err, "Failed to open a channel")
      defer ch.Close()

      ch.Qos(
            10,           // prefetch count
            0,               // prefetch size
            false,           // global
    )

      msgs, err := ch.Consume(
        q.Name, // queue
        "",     // consumer
        false,  // NO auto-ack
        false,  // exclusive
        false,  // no-local
        false,  // no-wait
        nil,    // args
      )

      for d := range msgs {
        log.Printf("Received a message: %s", d.Body)
        err:= processMessage(d)
        if err != nil {
            log.Printf("%s : while consuming task", err)
            d.Nack(false, true)
        } else {
            d.Ack(false)
        }
        continue // consume other messages
      }
  }()
}

但这是RAM友好的方法吗?对于RabbitMQ来说,不是为每个工作人员都产生一个新的渠道会很戏剧性吗?

所以,问题是,哪个变种更好?更好的性能,更好的内存使用率等。

那么,这里RabbitMQ的最佳用法是什么?

更新:目前,我遇到一种情况,当我的工作人员消耗了VPS上的所有RAM并被OOM杀死。我使用第二种方法。因此,就我而言,更好的是在几分钟的工作后能够保持我的工作人员不被OOM杀死的能力。

更新2:nack是当工作程序无法处理消息时,ack是非常重要的。必须处理所有消息(其客户分析),但是有时工作人员无法处理它,因此必须nack消息才能将其传递给其他工作人员(当前,某些用于处理消息的第三方API有时仅返回503状态代码,在这种情况下,消息应传递给其他工作人员或重试)。 因此,不幸的是,不能选择使用auto-ack

1 个答案:

答案 0 :(得分:0)

我想每个processMessage()都在一个新的goroutine中运行。

  

哪个版本更好?

我更喜欢第一个,因为打开/关闭通道有点贵(2 + 2 TCP数据包)。我认为您的OOM问题与gorutine无关,gorutine非常轻巧,仅需5KB左右。因此,问题可能是由您的processMessage()引起的。

我认为github.com/streadway/amqp通道消耗操作是thread/gorutine-safe,因此,只要执行一些消耗操作,就可以在goruntine之间共享通道。