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 详细介绍 ...
随机推荐
- Redis多机常用架构-cluster
Redis-cluster:去中心化,中间件,集群中任意节点平等,任一节点可获得全局的数据 Redis-cluster 拓扑图: 架构演变及 cap 理论: 单机 Redis 属于 cp 模型. Re ...
- WinForm/Silverlight多线程编程中如何更新UI控件的值
单线程的winfom程序中,设置一个控件的值是很easy的事情,直接 this.TextBox1.value = "Hello World!";就搞定了,但是如果在一个新线程中这么 ...
- javascript基础知识-类和模块
在JavaScript中可以定义对象的类,让每个对象都共享这些属性. 在JavaScript中,类的实现是基于其原型继承机制的.如果两个实例都从同一个原型对象上继承了属性,我们就说它们是同一个类的实例 ...
- oracle 抛出自定义错误(网上找的例子)
CREATE OR REPALCE TRIGGER minimun_age_checkBEFORE INSERT ON employeeFOR EACH ROWBEGIN IF ADD_MONTHS( ...
- 结对开发训练(续)(郭林林&胡潇丹)
本次题目:求二维数组最大连续的子数组之和. 通过前两次对问题的分析,这次在拿到题目时,我们首先与前两次题目做对比,尤其与第二次的题目相比较,这是在第二次题目上的扩展,第二次的题目是此次题目的一个特例. ...
- Android 断点续传 思路
大部分http服务器本身是可以支持range字段和断点续传的.另外 http返回206字段表示支持断点续传. 但是遇到支持的服务器的时候,就需要手动去处理断点续传的功能. 客户端在请求文件的时候添加 ...
- mongodb安装及基础命令
安装mongodb(mongodb-linux-x86_64-3.2.4.tgz)1 export PATH=$PATH:/usr/local/mongodb/bin2 /usr/local/mong ...
- 第七章 内存管理单元MMU介绍
7.1 内存管理单元MMU介绍 7.1.1 S3C2410/S3C2440 MMU特性 负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查 特性: 与ARM V4兼容的映射长度.域.访问权 ...
- OPC的理解Open Packaging Conventions
Open Packaging Conventions (OPC) 博客地址:www.cnblogs.com/icmzn OPC是一个文件容器技术.被微软创建,用来存储XML或者非XML文件结合起来的规 ...
- java-集合类
框架图 集合类 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式.数组和集合类同是容器,有何不同?数组存储同一类型的基本数据类 ...