rabbitmq消息队列——"topic型交换器"
在之前的章节中我们改进了我们的日志系统,我们使用direct型交换器代替了只能盲目广播消息的fanout型交换器,这使得我们可以有选择性地接收日志。
尽管使用direct型交换器改进了我们的日志系统,但它仍然有缺陷——它不能基于多个规则或标准进行路由。
在我们的系统中,我呢也许希望订阅的不仅仅是严重级别的日志,而且基于日志发送方。你可能了解过systool这个unix工具,该工具不仅能路由严重级别的日志(info、warn、crit等),并且具有各种能力(授权、定时等)。
这将会给我们很多灵活性——我们可能希望不但接收那些来自定时器的错误日志而且接收来自核心模块的。
为在我们的日志系统实现这个,我们还需要再学习一个更加复杂的交换器类型——Topic型交换器。
发送到Topic型交换器的消息不能包含任意路由键——它必须是一串字符并且以圆点符号隔开。这些字符可以是任意的,但它们通常都会指定成链接的消息的某些功能。一些有效的路由键如:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"等,路由键可以包括
任意多个字符,上限是255个字节长度。
绑定键也必须使用类似的形式。topic型交换器的逻辑和direct型很相像——消息发送时会指定一个特别的路由键,并且会被路由到所有与绑定键相匹配的队列。不过对绑定键有两种特殊类型:
- *符号用来代替任意单词
- #符号可以代替0个或多个单词
用一个例子简单地解释下:

这个例子中,我们准备发送一些描述动物的消息,这些消息被发送的时候路由键都是包含三个单词并且以圆点符号分开(总共两个圆点符),路由键的订单第一个单词用来描述速度,第二个是颜色,第三个是物种:
"<speed>.<colour>.<species>"。
我们创建3种绑定:Q1和绑定键"*.orange.*"绑定,Q2和"*.*.rabbit" 、"lazy.#"分别绑定。这些绑定描述如下:
- Q1只对所有橙色的动物感兴趣
- Q2希望收到所有兔子的消息及一切懒惰的动物的
使用路由键"quick.orange.rabbit"的消息将发送至Q1和Q2,"lazy.orange.elephant"的也是一样。另一方面,使用路由键"quick.orange.fox"的消息将仅被路由Q1,"lazy.brown.fox"仅被路由到Q2。"lazy.pink.rabbit"的消息仅仅会被路由到Q2一次,尽管它匹配了
两种绑定关系,"quick.brown.fox""quick.brown.fox"没有绑定任何关系所有该消息将会被丢弃。
可是如果我们背弃”约定”直接发送一个单词或者四个单词会怎么样?比如:"orange" or "quick.orange.male.rabbit",当然了,第一个不会匹配任何绑定,直接被丢弃。
另一方面四个单词的会匹配第二种,所以它会被路由到第二个队列中。
备注:
Topic型交换器比较强大跟其它交换器很相似。
当一个队列以”#”作为绑定键时,它将接收所有消息,而不管路由键如何,类似于fanout型交换器。
当特殊字符”*”、”#”没有用到绑定时,topic型交换器就好比direct型交换器了。
揉在一起
我们将在我们的日志系统中使用topic型交换器。我们假设日志的路由键仅由两部分单词组成:"<facility>.<severity>"。
emit_log_topic.go源代码如下:
package main import (
"fmt"
"github.com/streadway/amqp"
"log"
"os"
"strings"
) func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s:%s", msg, err)
panic(fmt.Sprintf("%s:%s", msg, err))
}
} func bodyFrom(args []string) string { if len(args) < 3 || os.Args[2] == "" {
return "hello"
} else {
return strings.Join(os.Args[2:], " ")
}
} func severityFrom(args []string) string {
if len(os.Args) < 2 || os.Args[1] == "" {
return "anonymous.info"
} else {
return os.Args[1]
}
} 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") body := bodyFrom(os.Args) //请求参数
ch.Publish("logs_topic", severityFrom(os.Args), false, false, amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message") log.Printf(" [x] Sent %s", body)
}
receive_logs_topic.go端代码如下:
package main 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("", false, false, true, false, nil)
failOnError(err, "Failed to declare a queue")
if len(os.Args) < 2 {
log.Printf("Usage: %s [binding_key]...", 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 m := range msgs {
log.Printf(" [x] %s", m.Body)
}
}() log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
接收所有log:
go run receive_logs_topic.go "#"
接收facility为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"
具体效果如下:
接收所有:

接收kern:

接收critical:

同时接收kern、critical:

无效绑定(这里接收不到消息):

发送端:

至此,topic型交换器就over了,topic型交换器相比direct的单对单,fanout型的广播式,topic型灵活性更大,稍作修改绑定键及路由键即可实现以上两种交换器类型,简直了!
rabbitmq消息队列——"topic型交换器"的更多相关文章
- (八)RabbitMQ消息队列-通过Topic主题模式分发消息
原文:(八)RabbitMQ消息队列-通过Topic主题模式分发消息 前两章我们讲了RabbitMQ的direct模式和fanout模式,本章介绍topic主题模式的应用.如果对direct模式下通过 ...
- RabbitMQ消息队列(二)-RabbitMQ消息队列架构与基本概念
没错我还是没有讲怎么安装和写一个HelloWord,不过快了,这一章我们先了解下RabbitMQ的基本概念. RabbitMQ架构 说是架构其实更像是应用场景下的架构(自己画的有点丑,勿嫌弃) 从图中 ...
- rabbitMQ消息队列1
rabbitmq 消息队列 解耦 异步 优点:解决排队问题 缺点: 不能保证任务被及时的执行 应用场景:去哪儿, 同步 优点:保证任务被及时的执行 缺点:排队问题 大并发 Web nginx 1000 ...
- RabbitMQ消息队列随笔
本文权当各位看官对RabbitMQ的基本概念以及使用场景有了一定的了解,如果你还对它所知甚少或者只是停留在仅仅是听说过,建议你先看看这篇文章,在对RabbitMQ有了基本认识后,我们正式开启我们的Ra ...
- (二)RabbitMQ消息队列-RabbitMQ消息队列架构与基本概念
原文:(二)RabbitMQ消息队列-RabbitMQ消息队列架构与基本概念 没错我还是没有讲怎么安装和写一个HelloWord,不过快了,这一章我们先了解下RabbitMQ的基本概念. Rabbit ...
- RabbitMQ基本概念(二)-RabbitMQ消息队列架构与基本概念
没错我还是没有讲怎么安装和写一个HelloWord,不过快了,这一章我们先了解下RabbitMQ的基本概念. RabbitMQ架构 说是架构其实更像是应用场景下的架构(自己画的有点丑,勿嫌弃) 从图中 ...
- SpringCloud之RabbitMQ消息队列原理及配置
本篇章讲解RabbitMQ的用途.原理以及配置,RabbitMQ的安装请查看SpringCloud之RabbitMQ安装 一.MQ用途 1.同步变异步消息 场景:用户下单完成后,发送邮件和短信通知. ...
- openresty 学习笔记番外篇:python访问RabbitMQ消息队列
openresty 学习笔记番外篇:python访问RabbitMQ消息队列 python使用pika扩展库操作RabbitMQ的流程梳理. 客户端连接到消息队列服务器,打开一个channel. 客户 ...
- RabbitMQ消息队列(一): Detailed Introduction 详细介绍
http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...
随机推荐
- maven nexus私服搭建,特别痛苦!!
一.下载nexu,配置环境 参考我的文章:http://www.cnblogs.com/quanyongan/archive/2013/04/24/3037589.html 二.解压并准备Nexus安 ...
- Win7 64位 VS2015环境编译cegui-0.8.5
首先是去官网下载源码与依赖库 http://cegui.org.uk/ 然后得提一下,编译DX11版本带Effects11框架的话会有问题,也就是默认情况编译有问题,这是因为VS2015升级后编译器对 ...
- js数组去重的三种方法
<script type="text/javascript"> /*// 第一种冒泡法删除 var arr=[1,2,2,78,3,456,456]; for(var ...
- linux c++循环缓冲区模板类
一:概述 实际学习和工作中,我们经常会遇到读写大量数据的情况,这个时候我们可能就用到了循环缓冲区. 循环缓冲区在处理大量数据的时候有很大的优点,循环缓冲区在一些竞争问题上提供了一种免锁的机制,免锁的前 ...
- surface上的手势事件
surface上的手势事件 1.surface上的触控事件测试. 手指触控在CHROME和FIREFOX下会触发touch事件,而IE10是不支持touch事件的. 手指触控在三个浏览器下均会触发全部 ...
- (多重背包+记录路径)Charlie's Change (poj 1787)
http://poj.org/problem?id=1787 描述 Charlie is a driver of Advanced Cargo Movement, Ltd. Charlie dri ...
- 【转】linux shell实现随机数多种方法(date,random,uuid)
在日常生活中,随机数实际上经常遇到,想丢骰子,抓阄,还有抽签.呵呵,非常简单就可以实现.那么在做程序设计,真的要通过自己程序设计出随机数那还真的不简单了.现在很多都是操作系统内核会提供相应的api,这 ...
- 基于AutoCAD的ObjectARX之NET扩展(mcnetarx)-AcdbEntNext、AcdbEntLast
1.AcdbEntLast用于获取最后一个创建的实体. 2.AcdbEntNext用于获取指定实体名称之后的下一个创建的实体. ' 定义保存实体名称的变量 Dim ent() As Integer = ...
- php图片处理类库 Image
image 下载地址 https://github.com/Intervention/image.git 下载之后解压 执行composer update 生成 autoload.php文件 该类 ...
- easyui的datagrid打印(转)
在使用easyui插件的时候,使用最多的应该是datagrid插件.有时候根据客户需求,可能需要将datagrid内容进行打印,这时候如果直接调用window.print,可能由于easyui的dat ...