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 详细介绍 ...
随机推荐
- spring的beans.xml中classpath
classpath就是代表 /WEB-INF /classes/ 这个路径(如果不理解该路径,就把一个web工程发布为war包,然后用winrar查看其包内路径就理解啦) 常用的场景: 在SSH架构中 ...
- 百度贴吧python吧抓取用户名和图片
原理就是将贴吧条数中的用户提取出来并在此爬取用户中的图片 #!/usr/bin/env python #coding:utf-8 import requests import urllib2 impo ...
- myeclipse如何修改Web项目名称,eclipse如何修改项目名字
myeclipse如何修改Web项目名称 1.复制一个现有的项目,重命名项目名称 2.这里的项目名称重新命名了,但是Web项目本质下的名称还是没有改变的.所以需要更改,更改方法-->选择项目右击 ...
- flask_用户信息和头像
一.用户信息页 1.创建视图函数(microblog.py) @app.route('/user/<nickname>') @login_required def user(nicknam ...
- scrollHeight、scrollTop等的比较
自接触js以来一直使用的是jquery插件,对js的了解甚少,经常容易混淆element.scrollHeight.element.scrollTop等方法.今天对这些方法做出比较. scrollTo ...
- Alpha、Beta、RC、GA版本的区别 ZT
http://www.blogjava.net/RomulusW/archive/2008/05/04/197985.html Alpha:是内部测试版,一般不向外部发布,会有很多Bug.一般只有测试 ...
- vc6编译apache遇到的问题
在apache的官网上下载了httpd-2.0.65-win32-src,打算了解一下其中的细节,以给目前做的一个项目提供抄袭性的参考.那是一个vc6的工程.用vc6一编译,apr那个工程报错,说什么 ...
- iOS 7 UI Transition – Porting View Controller Layouts from iOS 6
http://www.mobinett.com/2013/08/19/ios7-ui-transition-porting-view-controller-layouts-ios6/
- java类集开发中一对多和多对多的关系的实现
摘自<java开发实战经典>李兴华.著 一对多的关系 一个学校可以包含多个学生,一个学生属于一个学校,那么这就是一个典型的一对多关系,此时就可以通过类集进行关系的表示. 在定义Studen ...
- LintCode 392 House Robber
// Ref: https://segmentfault.com/a/1190000003811581// Ref: http://www.cnblogs.com/grandyang/p/438363 ...