本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第五篇——topic。

这些教程涵盖了使用RabbitMQ创建消息传递应用程序的基础知识。 你需要安装RabbitMQ服务器才能完成这些教程,请参阅安装指南或使用Docker镜像。 这些教程的代码是开源的,官方网站也是如此。

先决条件

本教程假设RabbitMQ已安装并运行在本机上的标准端口(5672)。如果你使用不同的主机、端口或凭据,则需要调整连接设置。

topic交换器(主题交换器)

发送到topic交换器的消息不能具有随意的routing_key——它必须是单词列表,以点分隔。这些词可以是任何东西,但通常它们指定与消息相关的某些功能。一些有效的routing_key示例:“stock.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit”。routing_key中可以包含任意多个单词,最多255个字节。

绑定键也必须采用相同的形式。topic交换器背后的逻辑类似于direct交换器——用特定路由键发送的消息将传递到所有匹配绑定键绑定的队列。但是,绑定键有两个重要的特殊情况:

  • *(星号)可以代替一个单词。
  • (井号)可以替代零个或多个单词。

通过下面这个示例可以很容易看明白这一点:

img

在这个例子中,我们将发送一些都是描述动物的信息。将使用包含三个词(两个点)的路由密钥发送消息。路由键中的第一个单词将描述速度,第二个是颜色,第三个是种类:“<speed>.<colour>.<species>”。

我们创建了三个绑定关系:Q1与绑定键“*.orange.*”绑定,Q2与“*.*.rabbit”和“lazy.#”绑定。

这些绑定可以总结为:

  • Q1对所有橙色动物都感兴趣。
  • Q2想接收有关兔子(rabbit)的一切消息,以及有关懒惰(lazy)动物的一切消息。

路由键设置为“quick.orange.rabbit”的消息将传递到两个队列。消息“lazy.orange.elephant”也将发送给他们两个。另一方面,“quick.orange.fox”将仅进入第一个队列,而“lazy.brown.fox”将仅进入第二个队列。即使“lazy.pink.rabbit”与两个绑定匹配(匹配Q2的两个绑定),也只会传递到第二个队列一次。 “quick.brown.fox”与任何绑定都不匹配,因此将被丢弃。

如果我们打破约定并发送一个或四个单词的消息,例如“orange”或“quick.orange.male.rabbit”,会发生什么?好吧,这些消息将不匹配任何绑定,并且将会丢失。

另外,“lazy.orange.male.rabbit”即使有四个单词,也将匹配最后一个绑定,并将其传送到第二个队列。

topic交换器

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

当队列用“#”(井号)绑定键绑定时,它将接收所有消息,而与路由键无关,就像在fanout交换器中一样。

当在绑定中不使用特殊字符“*”(星号)和“#”(井号)时,topic交换器的行为就像direct交换器一样。

完整示例

我们将在日志记录系统中使用topic交换器。我们将从一个可行的假设开始,即日志的路由键将包含两个词:“<facility>.<severity>”。

该代码与上一教程中的代码几乎相同。

emit_log_topic.go的代码:

package main

import (
        "log"
        "os"
        "strings"

        "github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
        if err != nil {
                log.Fatalf("%s: %s", msg, err)
        }
}

func main() {
        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()

        err = ch.ExchangeDeclare(
                "logs_topic", // name
                "topic",      // type
                true,         // durable
                false,        // auto-deleted
                false,        // internal
                false,        // no-wait
                nil,          // arguments
        )
        failOnError(err, "Failed to declare an exchange")

        body := bodyFrom(os.Args)
        err = ch.Publish(
                "logs_topic",          // exchange
                severityFrom(os.Args), // routing key
                false, // mandatory
                false, // immediate
                amqp.Publishing{
                        ContentType: "text/plain",
                        Body:        []byte(body),
                })
        failOnError(err, "Failed to publish a message")

        log.Printf(" [x] Sent %s", body)
}

func bodyFrom(args []string) string {
        var s string
        if (len(args) < 3) || os.Args[2] == "" {
                s = "hello"
        } else {
                s = strings.Join(args[2:], " ")
        }
        return s
}

func severityFrom(args []string) string {
        var s string
        if (len(args) < 2) || os.Args[1] == "" {
                s = "anonymous.info"
        } else {
                s = os.Args[1]
        }
        return s
}

receive_logs_topic.go的代码:

package main

import (
        "log"
        "os"

        "github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
        if err != nil {
                log.Fatalf("%s: %s", msg, err)
        }
}

func main() {
        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()

        err = ch.ExchangeDeclare(
                "logs_topic", // name
                "topic",      // type
                true,         // durable
                false,        // auto-deleted
                false,        // internal
                false,        // no-wait
                nil,          // arguments
        )
        failOnError(err, "Failed to declare an exchange")

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

        if len(os.Args) < 2 {
                log.Printf("Usage: %s [binding_key]...", os.Args[0])
                os.Exit(0)
        }
     // 绑定topic
        for _, s := range os.Args[1:] {
                log.Printf("Binding queue %s to exchange %s with routing key %s",
                        q.Name, "logs_topic", s)
                err = ch.QueueBind(
                        q.Name,       // queue name
                        s,            // routing key
                        "logs_topic", // exchange
                        false,
                        nil)
                failOnError(err, "Failed to bind a queue")
        }

        msgs, err := ch.Consume(
                q.Name, // queue
                "",     // consumer
                true,   // auto ack
                false,  // exclusive
                false,  // no local
                false,  // no wait
                nil,    // args
        )
        failOnError(err, "Failed to register a consumer")

        forever := make(chan bool)

        go func() {
                for d := range msgs {
                        log.Printf(" [x] %s", d.Body)
                }
        }()

        log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
        <-forever
}

想要接收所有的日志:

go run receive_logs_topic.go "#"

要从“kern”接收所有日志:

go run receive_logs_topic.go "kern.*"

或者,如果你只想接收“critical”日志:

go run receive_logs_topic.go "*.critical"

你可以创建多个绑定:

go run receive_logs_topic.go "kern.*" "*.critical"

并发出带有路由键“kern.critical”的日志:

go run emit_log_topic.go "kern.critical" "A critical kernel error"

你可以自己尝试玩一下这个程序。请注意,代码没有对路由键或绑定键进行任何假设,你可能希望使用两个以上的路由键参数。

(关于emit_log_topic.goreceive_logs_topic.go的完整源代码)

接下来,我们将在教程6中了解如何将往返消息用作远程过程调用。

RabbitMQ Go客户端教程5——topic的更多相关文章

  1. RabbitMQ Go客户端教程1——HelloWorld

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,共分为六篇,本文是第一篇--HelloWorld. 这些教程涵盖了使用RabbitMQ创建消 ...

  2. RabbitMQ Go客户端教程3——发布/订阅

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第三篇--发布/订阅. 这些教程涵盖了使用RabbitMQ创建消息传递 ...

  3. RabbitMQ Go客户端教程6——RPC

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第六篇--RPC. 这些教程涵盖了使用RabbitMQ创建消息传递应用 ...

  4. RabbitMQ Go客户端教程4——路由

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第四篇--路由. 这些教程涵盖了使用RabbitMQ创建消息传递应用程 ...

  5. RabbitMQ Go客户端教程2——任务队列/工作队列

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第二篇--任务队列. 这些教程涵盖了使用RabbitMQ创建消息传递应 ...

  6. SpringBoot系列之RabbitMQ使用实用教程

    SpringBoot系列之RabbitMQ使用实用教程 @ 目录 1. 消息队列概述 1.1 MQ的概述 1.2 MQ目的地形式 2. 消息队列实现方式 2.1 常见MQ框架 2.2 MQ实现方式 3 ...

  7. RabbitMq C# .net 教程

    本文转载来自 [http://www.cnblogs.com/yangecnu/p/Introduce-RabbitMQ.html]写的很详细. 文件安装包官方DEMO下载地址是:http://pan ...

  8. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  9. C#基于RabbitMQ实现客户端之间消息通讯实战演练

    一.背景介绍和描述 MQ消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之一.何时需要消息队列?当你需要使用 ...

随机推荐

  1. Javasript中this指向问题和改变this指向的方法

    在学习javascript中我们往往会被this的指向问题弄的头昏转向,今天我们就来学习一下this的指向问题,和改变this指向的方法. 一.this的指向问题 在学习this的指向问题之前我们需要 ...

  2. redis 在windows 下的安装和使用

    1.下载试用 首先下载https://github.com/dmajkic/redis/downloads,我下载的是2.4.5的版本,是个压缩包解压后里面有两个文件夹一个是32bit一个是64位的, ...

  3. 14、Cookie和Session组件

    cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不 ...

  4. Servlet基础使用总结

    Servlet通俗理解:主要功能在于交互式地浏览和生成数据,生成动态Web内容.Servlet运行于支持Java的应用服务器中.从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Ser ...

  5. Android序列化问题与思考

    今天再来谈谈Android中的对象序列化,你了解多少呢? 序列化指的是什么?有什么用 序列化指的是讲对象变成有序的字节流,变成字节流之后才能进行传输存储等一系列操作. 反序列化就是序列化的相反操作,也 ...

  6. git clone克隆github仓库慢,问题解决

    导读 转载自:https://www.hangge.com/blog/cache/detail_2670.html 原因     由于国内网络问题,当我们使用 git clone 命令从 github ...

  7. php随机填充字符串内容

    public function getStr($str=false){ $poems="从,善,如,登,从,恶,如,崩,已,知,花,意,未,见,其,花,,,已,见,其,花,,,未,闻,花,名 ...

  8. 客户的一个紧急bug,我用了两种方式进行 C# 反编译修改源码

    一:背景 1. 讲故事 周五下午运营反馈了一个紧急bug,说客户那边一个信息列表打不开,急需解决,附带的日志文件也发过来了,看了下日志大概是这样的: 日期:2020-11-13 12:25:45,92 ...

  9. 极客mysql16

    1.MySQL会为每个线程分配一个内存(sort_buffer)用于排序该内存大小为sort_buffer_size 1>如果排序的数据量小于sort_buffer_size,排序将会在内存中完 ...

  10. TIME_WAIT状态存在的原因

    TIME_WAIT状态存在有两个理由: 1.可靠地实现TCP全双工连接的中断 2.允许老的重复分节在网络中消失 第一个理由:如果客户端的ACK丢失了,服务器将会重新发送它的最终的那个FIN,因此客户端 ...