golang RabbitMQ教程(5) 主题交换器

在上一篇文章中,我们对之前的日志系统进行了改进,使用direct类型的exchange替代了只能广播消息的fanout类型,让日志系统能够有选择性的接收处理消息。

虽然使用direct类型的exchange提升了日志系统的扩展性,但还是有它的局限性存在,那就是无法配置多重标准的路由。

如果想让系统不能仅根据日志级别来定义,还能根据发送日志的源信息来订阅。如unix工具syslog,就是根据日志级别(info/warn/crit..)和设备(auth/cron/kern)来进行路由的。这会提供更多的灵活性,如可以做到监听所有来自'cron'和'kern'设备的error信息。

为了实现这种灵活性,需要来学习下另外一个功能更综合的topic类型的交换器(exchange).

Topic exchange#

Topic类型的exchange消息的routing key是有一定限制的,必须是一组使用“.”分开的单词。单词可以是任意的,但是一般来说以能准确的表达功能的为佳。如以下的例子都是合法的:"stock.usd.nyse", "nyse.vmw","quick.orange.rabbit".Routing key可以是任意多个单词组成,但其总长度不能超过255个字节。

Topic exchange的binding key跟之前的没有太大区别,其逻辑跟direct一样,其接收到的消息会分发到所有与其routing key相匹配的绑定队列。以下两个通配符也可作为binding key使用:

Copy Highlighter-hljs
* 表示一个单词 # 表示0或多个单词

看例子:

上图中,消息的routing key用三个单词来表示,依次表示速度、颜色、种类,其形式如:"..”。

图中有三个绑定:Q1的绑定键是".orange.", Q2的绑定键是"..rabbit"和"lazy.#".

这三个绑定规则可以简单概括为:

Copy Highlighter-hljs
Q1对orange颜色的动物感兴趣; Q2则对所有物种是rabbit的速度是lazy的所有动物感兴趣;

举例来说明带有不同routing key的消息会被路由到哪个队列:

Copy Highlighter-hljs
"quick.orange.rabbit": 分发到Q1Q2"lazy.orange.elephant": 分发到Q1Q2; "quick.orange.fox": 分发到Q1"lazy.brown.fox": 分发到Q2; "lazy.pink.rabbit": 分发到Q2,且只会分发一次,虽然它匹配了Q2的两个绑定; "quick.brown.fox": Q1Q2都不分发,因为没有与之匹配的绑定规则,将会被忽略; "orange": 无匹配规则,丢失 "quick.orange.male.rabbit": 无匹配规则,丢失 "lazy.orange.male.rabbit": 分发到Q2,虽然有4个单词,但符合"lazy.#"规则的通配符

Topic exchange

Topic exchange很灵活,也很容易用此实现其他类型的功能.

如果将"#"指定为绑定键,那么就会接收所有的消息,相当于fanout类型的广播;

如果通配符"*","#"均不作为绑定键使用,那么其功能实现就等同于direct类型;

整个文件#

在前一篇的基础上实现topic exchange,只需做少许改的即可,这里我们假设routing key由两个单词组成,类似于".".

emit_log_topic.go:

Copy Highlighter-hljs
package main import ( "fmt" "log" "os" "strings" "github.com/streadway/amqp" ) func failOnError(err error, msg string){ if err != nil { log.Fatalf("%s: %s", msg, err) panic(fmt.Sprintf("%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 an channel") defer ch.Close() err = ch.ExchangeDeclare( "logs_topic", //name "topic", //type true, false, false, false, nil, ) failOnError(err, "Failed to declare an exchange") body := bodyFrom(os.Args) err = ch.Publish( "logs_topic", // exchange severityFrom(os.Args), //routing key false, false, 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 || args[1] == "" { s = "info" }else { s = os.Args[1] } return s }

receive_logs_topic.go:

Copy Highlighter-hljs
import( "fmt" "log" "os" "github.com/streadway/amqp" ) func failOnError(err error, msg string){ if err != nil { log.Fatalf("%s: %s", msg, err) panic(fmt.Sprintf("%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", "topic", true, false, false, false, nil, ) failOnError(err, "Failed to declare an exchange") q, err := ch.QueueDeclare( "", //name false, false, true, false, nil, ) failOnError(err, "Failed to declare a queue") if len(os.Args) < 2 { log.Printf("Usage: %s [info] [warning] [error]", os.Args[0]) os.Exit(0) } 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, s, "logs_topic", false, nil) failOnError(err, "Failed to bind a queue") } msgs, err := ch.Consume( q.Name, "", true, false, false, false, nil, ) 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 }

运行:#

接收所有消息:

Copy Highlighter-hljs
go run receive_logs_topic.go "#"

接收来自"kern"设备的消息:

Copy Highlighter-hljs
go run receive_logs_topic.go "kern.*"

接收所有以"critical"结尾的消息:

Copy Highlighter-hljs
go run receive_logs_topic.go "*.critical"

创建多重绑定:

Copy Highlighter-hljs
go run receive_logs_topic.go "kern.*" "*.critical"

发送消息:

Copy Highlighter-hljs
go run emit_log_topic.go "kern.critical" "A critical kernal error"
posted @   caibaotimes  阅读(148)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
CONTENTS