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使用:
* 表示一个单词
# 表示0或多个单词
上图中,消息的routing key用三个单词来表示,依次表示速度、颜色、种类,其形式如:"
图中有三个绑定:Q1的绑定键是".orange.", Q2的绑定键是"..rabbit"和"lazy.#".
这三个绑定规则可以简单概括为:
Q1对orange颜色的动物感兴趣;
Q2则对所有物种是rabbit的、速度是lazy的所有动物感兴趣;
举例来说明带有不同routing key的消息会被路由到哪个队列:
"quick.orange.rabbit": 分发到Q1和Q2;
"lazy.orange.elephant": 分发到Q1和Q2;
"quick.orange.fox": 分发到Q1;
"lazy.brown.fox": 分发到Q2;
"lazy.pink.rabbit": 分发到Q2,且只会分发一次,虽然它匹配了Q2的两个绑定;
"quick.brown.fox": Q1、Q2都不分发,因为没有与之匹配的绑定规则,将会被忽略;
"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:
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:
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
}
运行:#
接收所有消息:
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"
发送消息:
go run emit_log_topic.go "kern.critical" "A critical kernal error"
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?