开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情
今天接着来学习下RabbitMQ中另一个类型的交换器topic,它可以根据Bindingkey更加灵活的绑定,不会像direct交换器只能固定一种字符串,topic的bindingkey可以代表包含更多信息,例如:stock.usd.nyse,nyse.vmw,quick.blue.rabbit,通过.分割每个语义可以包括任意多个单词,最多255个字节
topic交换器
顾名思义topic称作主题交换器,不同消费者可以订阅根据自己的需求,更灵活的接收符合自己要求的消息
topic交换器背后的逻辑类似于direct交换器: 用特定路由键发送的消息将传递到所有匹配Bindingkey的队列
这里需要注意bindingkey的使用规则:
*星号可以代替一个单词#井号可以代替零个或者多个单词
上图所示中,传递信息是用来描述动物,Bindingkey的密钥包含三个单词,两个分割点.,其中三个单词分别代表:速度.颜色.种类,形如<speed>.<colour>.<species>
上图的绑定可以理解为:
- Q1队列只对颜色为blue的所有动物感兴趣
- 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.*"
可以创建多个绑定
go run receive.go "*.computer.*" "#"
发送带有路由键2022.computer.critical日志
go run send.go "2022.computer.critical" "this is a message"
总结
今天浅谈了Go与RabbitMQ(五),还有很多相关的知识后面会继续深入,对于刚入门go语言的我来说,还有许多地方需要学习,有错误的地方欢迎大家指出,共同进步!!