在上一教程中,我们改进了日志记录系统。 我们没有使用只能进行虚拟广播的fanout交换器,而是使用直接交换器,并有可能选择性地接收日志。

尽管使用直接交换改进了我们的系统,但它仍然存在局限性-它不能基于多个条件进行路由。

在我们的日志记录系统中,我们可能不仅要根据严重性订阅日志,还要根据发出日志的源订阅日志。 您可能从syslog unix工具中了解了这个概念,该工具根据严重性(info / warn / crit ...)和工具(auth / cron / kern ...)路由日志。

这将为我们提供很大的灵活性-我们可能只想听来自“ cron”的严重错误,也可以听“ kern”的所有日志。

为了在日志系统中实现这一点,我们需要学习更复杂的topic交换器。

Topic交换器

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

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

  • *(星号)可以代替一个单词。

  • #(哈希)可以替代零个或多个单词。

在一个示例中最容易解释这一点:

RabbitMQ官方教程五 Topic(GOLANG语言实现)-LMLPHP

在此示例中,我们将发送所有描述动物的消息。 将使用包含三个词(两个点)的路由键发送消息。 路由键中的第一个单词将描述速度,第二个描述颜色,第三个描述物种:“ ..”。

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

这些绑定可以总结为:

  • Q1对所有橙色动物都感兴趣。
  • Q2想知道有关兔子的一切,以及有关懒惰动物的一切。

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

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

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

Topic交换器功能强大,可以像其他交流一样进行。

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

所有代码

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

# 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
}

获取所有日志

#shell 1
go run receive_logs_topic.go "#"

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

#shell 1
go run receive_logs_topic.go "kern.*"

或者,如果您只想听听“critical”日志:

#shell 1
go run receive_logs_topic.go "*.critical"

您可以创建多个绑定:

#shell 1
go run receive_logs_topic.go "kern.*" "*.critical"

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

#shell 1
go run emit_log_topic.go "kern.critical" "A critical kernel error"
05-11 22:38