浅谈Go与RabbitMQ(五)

110 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情

今天接着来学习下RabbitMQ中另一个类型的交换器topic,它可以根据Bindingkey更加灵活的绑定,不会像direct交换器只能固定一种字符串,topic的bindingkey可以代表包含更多信息,例如:stock.usd.nyse,nyse.vmw,quick.blue.rabbit,通过.分割每个语义可以包括任意多个单词,最多255个字节

topic交换器

顾名思义topic称作主题交换器,不同消费者可以订阅根据自己的需求,更灵活的接收符合自己要求的消息

topic交换器背后的逻辑类似于direct交换器: 用特定路由键发送的消息将传递到所有匹配Bindingkey的队列

这里需要注意bindingkey的使用规则:

  • *星号可以代替一个单词
  • #井号可以代替零个或者多个单词

image.png

上图所示中,传递信息是用来描述动物,Bindingkey的密钥包含三个单词,两个分割点.,其中三个单词分别代表:速度.颜色.种类,形如<speed>.<colour>.<species>

上图的绑定可以理解为:

  1. Q1队列只对颜色为blue的所有动物感兴趣
  2. Q2队列只接收兔子相关的消息,或者速度慢slow的动物信息
  • 路由键设置为“quick.blue.rabbit”的消息将传递到两个队列
  • 消息“lazy.blue.elephant”也将发送给他们两个
  • quick.blue.fox”将仅进入第一个队列,而“lazy.brown.fox”将仅进入第二个队列
  • 即使“lazy.pink.rabbit”与Q2的两个绑定匹配,也只会传递到Q2队列一次
  • quick.brown.fox”与任何绑定都不匹配,因此将被丢弃。

如果一次性发送个一个或者四个单词的消息,例如 blue 或者slow.blue.sky.rabbit将不会匹配任何绑定,直接丢弃

topic交换器

topic交换器功能强大,可以像其他交换器一样运行。

当队列用#(井号)绑定键绑定时,它将接收所有消息,与fanout交换器中一样。

当在绑定中不使用特殊字符*(星号)和#(井号)时,topic交换器就与direct交换器一样

代码示例

接着上次的文章,继续在日志系统中进行修改,这次使用topic交换器,日志的路由键包含两个单词 <time>.<facility>.<severity>

send.go

// 处理错误返回信息
func DoError(err error, msg string) {
   if err != nil {
      log.Fatalf("%s:%s", msg, err)
   }
}
func getParam(args []string) (res string) {
   if (len(args) < 3) || os.Args[2] == "" {
      res = "YYQQ"
   } else {
      res = strings.Join(args[2:], "")
   }
   return
}
func getFrom(args []string) (res string) {
   if (len(args) < 2) || os.Args[1] == "" {
      res = "YYQQ info"
   } else {
      res = os.Args[1]
   }
   return
}
func main() {

   conn, err := amqp.Dial("amqp://admin:admin@localhost:5672/")
   DoError(err, "Failed connect to RabbitMQ!")
   defer conn.Close()
   ch, err := conn.Channel()
   DoError(err, "Failed to open a channel")
   defer ch.Close()

   err = ch.ExchangeDeclare(
      "logs_topic",
      "topic", //交换器类型
      true,
      false,
      false,
      false,
      nil)
   DoError(err, "Failed to declare a exchange")

   content := getParam(os.Args) //从命令行中返回数据
   err = ch.Publish(
      "logs_topic",
      getFrom(os.Args),
      false,
      false,
      amqp.Publishing{
         ContentType: "text/plain",
         Body:        []byte(content),
      })
   DoError(err, "Failed to publish a message")
}

receive.go代码:

func main() {
   conn, err := amqp.Dial("amqp://admin:admin@localhost:5672/")
   DoError(err, "Failed to connect to RabbitMQ")
   defer conn.Close()

   ch, err := conn.Channel()
   DoError(err, "Failed to open a channel")
   defer ch.Close()

   err = ch.ExchangeDeclare(
      "logs_topic",
      "topic",
      true,
      false,
      false,
      false,
      nil)
   DoError(err, "Failed to declare a exchange")

   queue, err := ch.QueueDeclare(
      "",    // name
      false, // durable
      false, // delete when unused
      true,  // exclusive
      false, // no-wait
      nil,   // arguments
   )
   DoError(err, "Failed to declare a queue")
   fmt.Println("queue.name: ", queue.Name)

   if len(os.Args) < 2 {
      log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
      os.Exit(0)
   }
   
   //建议多个绑定关系
   for _, bindkey := range os.Args[1:] {
      log.Printf("Binding queue %s to exchange %s with routing key %s",
         queue.Name, "logs_topic", bindkey)
      err = ch.QueueBind(
         queue.Name,   // queue name
         bindkey,      // routing key
         "logs_topic", // exchange
         false,
         nil)
      DoError(err, "Failed to bind a queue")
   }

   content, err := ch.Consume(
      queue.Name, // queue
      "",         // consumer
      true,       // auto-ack
      false,      // exclusive
      false,      // no-local
      false,      // no-wait
      nil,        // args
   )
   DoError(err, "Failed to register a consumer")
   var wait chan struct{}
   go func() {
      for msg := range content {
         log.Printf("Received a message %s", msg.Body)      
      }
   }()
   <-wait
}

接收所有的日志信息:

go run receive.go "#"

只想接收computer日志

go run receive.go "*.computer.*"

image.png

可以创建多个绑定

go run receive.go "*.computer.*" "#"

image.png

发送带有路由键2022.computer.critical日志

go run send.go "2022.computer.critical" "this is a message"

image.png

总结

今天浅谈了Go与RabbitMQ(五),还有很多相关的知识后面会继续深入,对于刚入门go语言的我来说,还有许多地方需要学习,有错误的地方欢迎大家指出,共同进步!!