RabbitMQ系列-Exchange介绍
RabbitMQ系列
1. Exchange
RabbitMQ系列-概念及安装提到AMQP 0-9-1协议默认支持四种exchange,分别是Direct Exchange,Fanout Exchange,Topic Exchange,Headers Exchange
除了交换类型之外,交换还声明了许多属性
- Name,交换机名称,唯一的
- Durability,持久性,RabbitMQ Server重启后依旧存在
- Auto-delete,自动删除,没有队列绑定到交换机时,交换机自动删除
- Arguments,可选参数, 用于插件和一些特定功能
1.1 Direct Exchange
Direct Exchange根据路由信息将消息送到指定队列
工作流程如下
- 消息队列绑定到Direct Exchange,并指定路由字符串K,该Direct Exchange名称为E
- 当具有路由键R的新消息到达交换机E时,如果K = R,则交换机将消息副本拷贝到队列
- 继续遍历剩余绑定到交换机E的队列,如果K = R,则交换机将消息副本拷贝到队列
Direct Exchange模型如下图所示
队列queue1和direct exchange的绑定路由有info和warn,类似的queue2和direct exchange的绑定路由有debug,queue3和direct exchange的绑定路由有error
当消息生产者producer发布路由值为info或者warn的消息时,根据绑定关系,该消息将被送到queue1,并被consumer1接收处理
同理,当消息生产者producer发布路由值为debug的消息时,根据绑定关系,该消息将被送到queue2,并被consumer2接收处理
当消息生产者producer发布路由值为error消息时,根据绑定关系,该消息将被送到queue3,并被consumer3接收处理
1.2 Fanout Exchange
Fanout Exchange忽略路由且将消息副本推送到所有绑定到该交换机的队列,假设有N个队列绑定到Fanout Exchange,生产者发送到消息经过该交换机处理,将消息副本发送到这个N个队列。
因此Fanout Exchange适用于广播的场景,Fanout Exchange模型如下图所示
队列queue1、queue2、queue3均绑定到了fanout类型的交换机,消息生产者producer发布的消息将被fanout exchange分发到queue1、queue2、queue3,最后被各自的消费者消费。
1.3 Topic Exchange
topic exchange对消息"分发范围"介于direct exchange和fanout exchange之间,direct exchage要求消息的路由键和队列的绑定路由键完全一致才分发,fanout exchange将消息分发到所有具有绑定关系的队列上
一般情况下,topic exchange的路由键由用英文逗号隔开的多个单词构成。其中,有两个单词比较特殊,*
可以代表任意的一个单词,#
可以代表0个或多个单词
假设,有路由键<地区.新闻种类.子种类>的新闻分发系统,系统模型如下图所示
其中,队列queue1和topic交换机的绑定关系有两个,<us.sport.*>表示关注美国地区所有体育主题相关的消息,<*.food.apple>表示关注所有地区关于苹果这种水果主题的消息
队列queue1与topic交换机绑定路由键<cn.car.byd>表示关注中国地区下汽车类主题下关于比亚迪的消息。
队列queue3与topic交换机的绑定关系为<#.huawei>表示关注所有地区关于华为的所有主题的消息。
1.4 Headers Exchange
headers交换机忽略路由键,利用x-match
参数和多个可选的headers键值对参数来路由消息。x-match
有两种类型值all
和any
当x-math=all
时,所有的headers键值对参数需要全部匹配,当x-math=any
时,只需要headers键值对参数中的一个匹配即可
假设,有学生信息订阅系统使用的时headers类型的交换机,模型如下图所示
其中,队列queue1和headers交换机的绑定关系的x-math=any
,键值对参数为age=18
和height=170
,因此当生产者发布的消息包含age=18
或height=170
时,消息将被路由到queue1
队列queue2和headers交换机的绑定关系的x-math=all
,键值对参数为age=22
和height=180
,因此当生产者发布的消息包含age=22
和height=180
时,消息将被路由到queue2
队列queue3和headers交换机的绑定关系的x-math=all
,键值对参数为gender=male
和score=60
,因此当生产者发布的消息包含gender=male
和score=60
时,消息将被路由到queue3
以amqp091-go为例,使用Direct Exchange说明Direct Exchange的基本使用方法。
2. 消费者代码
以amqp091-go为例,使用Direct Exchange说明消息者端的基本流程。
2.1 连接到RabbitMQ Server
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
Dial接收AMQP URI格式的字符串,建立和RabbitMQ Server的TCP连接,并返回连接Connection
。TCP握手的超时时间默认为30s
2.2 建立轻量级连接Channel
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
通过和RabbitMQ Server一次网络往返交互,建立一个唯一的轻量级连接Connection
当Channel
不在需要时,需要手动调用Channel.Close
关闭Channel
,以释放Channel
占用的资源,避免内存泄漏
当Channel
所属的Connection
关闭时,Channel
也会被关闭。
2.3 声明交换机Exchange
err = ch.ExchangeDeclare(
"log_direct", // name
amqp.ExchangeDirect, // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
生产者发布的消息会先到达Exchange,在根据Exchange类型和绑定关系将消息路由到特定队列。
ExchangeDeclare共有6个参数,这里重点看下其中几个参数
func (ch *Channel) ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args Table) error
第二个参数type/kind
类型,AMQP 0-9-1 broker提供了四种类型,分别是direct
,fanout
,topic
和headers
,这里使用的是direct
第三个参数durable
是否持久化,第四个参数autoDelete
是否自动删除
- 当持久化且不自动删除时,当RabbitMQ重启或者没有队列绑定时,Exchange依旧存在
- 当非持久化且自动删除时,当RabbitMQ重启或者Exchange没有队列绑定时,Exchange会自动删除
- 当非持久化且不自动删除时,当RabbitMQ重启后,Exchange会消失,当Exchange没有队列绑定时,Exchange会存在。即RabbitMQ不重启,Exchange就会一直存在
- 当持久化且自动删除时,当RabbitMQ重启后,Exchange依旧存在,但当Exchange没有队列绑定时,Exchange会被删除
第六个参数noWait
是否等待服务器的确认应答,当该参数no-wait为true是,应当给通过Channel.NotifyClose
异步处理异常。
2.4 声明队列Queue
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
队列Queue充当了Exchange和消费者之间缓冲区的角色
func (ch *Channel) QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args Table) (Queue, error)
如果队列不存在则创建,如果存在时需要确保参数和已经存在的Queue一致,否则会返回错误
当name
为空时,RabbitMQ Server会生成唯一的名称,并返回给q
第二个参数durable
是否持久化,第三个参数autoDelete
是否自动删除
- 当持久化且不自动删除时,当RabbitMQ重启或者没有与消费者的绑定关系时,Queue依旧存在,只有持久化的Exchange才能声明这种Queue
- 当非持久化且自动删除时,当RabbitMQ重启或者没有消费者时,Queue会自动删除,只有非持久化的Exchange才能声明这种Queue
- 当非持久化且不自动删除时,当RabbitMQ重启后,Queue会消失,当没有消费者时,Queue依旧存在。即RabbitMQ不重启,Queue就会一直存在,只有非持久化的Exchange才能声明这种Queue
- 当持久化且自动删除时,当RabbitMQ重启后,Queue依旧存在,但当没有消费者时,Queue会被删除
第四个参数exclusive
是否独占,当该参数为true时,该队列只能被声明这个Queue的Connection访问,并且在Connection关闭时,队列会被删除
第五个参数noWait
是否等待服务器的确认应答,当该参数no-wait为true是,应当给通过Channel.NotifyClose
异步处理异常。
当QueueDeclare
返回错误时,说明Queue创建失败,同时Channel也会被关闭
2.5 绑定关系Binding
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"log_direct", // exchange
false, // noWait
nil // args
)
failOnError(err, "Failed to bind a queue")
使用路由建立交换机和队列的绑定关系,可以使用多个路由建立交换机和队列的绑定关系,交换机根据路由判断是否将消息推送到队列
func (ch *Channel) QueueBind(name, key, exchange string, noWait bool, args Table) error
第一个参数是队列名称name
,第三个参数是交换机名称exchange
,第二个参数时队列和交换机绑定关系的表示
第三个参数noWait
是否等待服务器的确认应答,当该参数no-wait为true是,应当给通过Channel.NotifyClose
异步处理异常。
当建立绑定关系QueueBind
失败时,会返回错误并且Channel会被关闭。
2.6 Consume参数说明
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer tag
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
开始接受来自队列的消息
func (ch *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error)
Channel.Consume
返回<-chan Delivery
,消费者不断从需要该Channel
上接受消息,需要注意的是,消费者需要及时处理消息,否则将阻塞Channel
所属Connection
上的任何操作
第三个参数 autoAck
是否自动向确认RabbitMQ确认成功投递 当该参数为true时,写入TCP套接字即向abbitMQ确认成功投递。当该参数为false,则需要消费者手动发出确认信息,即调用Delivery.Ack
第四个参数exclusive
是否独占,当该参数为true时,消费者独占该队列,当该参数为false是,RabbitMQ Server将在多个消费者之间公平地分配交付
第五个参数noWait
是否等待服务器的确认应答,当该参数no-wait为true是,应当给通过Channel.NotifyClose
异步处理异常。
2.7 消费者汇总代码
查看代码
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
// routingKeys 绑定的路由: debug info warning error
func RecvMsg(routingKeys []string) {
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(
"log_direct", // name
amqp.ExchangeDirect, // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
if len(routingKeys) < 1 {
log.Printf("Usage: %s [info] [warning] [error]", routingKeys)
os.Exit(0)
}
for _, s := range routingKeys {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "fruit_direct", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"log_direct", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
var forever chan struct{}
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
3. 生产者代码
同样以amqp091-go为例,说明消息生产者端的基本流程
3.1 建立连接
和消费端一样,需要通过amqp.Dial
建立TCP连接,通过Connection.Channel
建立一个轻量级连接
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()
3.2 声明交换机Exchange
和消费端一样需要声明交换机Exchange,需要注意的是,生产者和消费者都声明了相同名称的Exchange,需要保持两者的参数是一致的,否则会报错
err = ch.ExchangeDeclare(
"log_direct", // name
amqp091.ExchangeDirect, // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
3.3 发布消息
err = ch.PublishWithContext(ctx,
"log_direct", // exchange
severityFrom(msg), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(msg),
})
failOnError(err, "Failed to publish a message")
采用异步的方式将消息发送到RabbitMQ server到交换机
第三个参数 mandatory
是否强制送达 当该参数为true时,且消费端队列和交换机没有对应的绑定路由时,消息就无法发出,可通过Channel.NotifyReturn
处理这种被退回的消息
第三个参数 immediate
是否理解接收 当该参数为true时,且匹配的消费端队列没有准备好接受此消息时,消息就无法发出,可通过Channel.NotifyReturn
处理这种被退回的消息
3.4 生产者汇总代码
查看代码
import (
"context"
"log"
"strings"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func SendMsg(msg string) {
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(
"log_direct", // name
amqp.ExchangeDirect, // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = ch.PublishWithContext(ctx,
"log_direct", // exchange
severityFrom(msg), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(msg),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", msg)
}
func severityFrom(msg string) string {
var s string
if strings.Contains(msg, "debug") {
s = "debug"
} else if strings.Contains(msg, "error") {
s = "error"
} else if strings.Contains(msg, "warn") {
s = "warn"
} else {
s = "info"
}
return s
}
RabbitMQ系列-Exchange介绍的更多相关文章
- RabbitMQ系列(三)RabbitMQ交换器Exchange介绍与实践
RabbitMQ交换器Exchange介绍与实践 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchang ...
- RabbitMQ交换器Exchange介绍与实践
RabbitMQ交换器Exchange介绍与实践 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchang ...
- RabbitMQ系列随笔——介绍及安装
一.RabbitMQ介绍 RabbitMQ是由erlang开发的AMQP(Advanced Message Queuing Protocol)的开源实现.他是高级消息队列协议,是应用层协议的一个开放标 ...
- RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读
RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...
- RabbitMQ系列(二)深入了解RabbitMQ工作原理及简单使用
深入了解RabbitMQ工作原理及简单使用 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchange介绍 ...
- RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介(转载)
RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介 今天这篇博文是我翻译的RabbitMQ的最后一篇文章了,介绍一下RabbitMQ的C#开发的接口.好了,言归正传吧. N ...
- RabbitMQ系列教程之六:远程过程调用(RPC)(转载)
RabbitMQ系列教程之六:远程过程调用(RPC) 远程过程调用(Remote Proceddure call[RPC]) (本实例都是使用的Net的客户端,使用C#编写) 在第二个教程中,我们学习 ...
- RabbitMQ系列教程之五:主题(Topic)(转载)
RabbitMQ系列教程之五:主题(Topic) (本实例都是使用的Net的客户端,使用C#编写),说明,中文方括号[]表示名词. 在上一个教程中,我们改进了我们的日志记录系统. 没有使用只能够进行虚 ...
- RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe)(转载)
RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe) (本教程是使用Net客户端,也就是针对微软技术平台的) 在前一个教程中,我们创建了一个工作队列.工作队列背后的假设是每个 ...
- RabbitMQ系列教程之四:路由(Routing)(转载)
RabbitMQ系列教程之四:路由(Routing) (使用Net客户端) 在上一个教程中,我们构建了一个简单的日志系统,我们能够向许多消息接受者广播发送日志消息. 在本教程中,我们将为其添加一项功能 ...
随机推荐
- 设置Mysql sort_buffer_size参数
按照官网的解释:Each session that must perform a sort allocates a buffer of this size. sort_buffer_size is n ...
- 手机号码归属地的自动查询.py(亲测有效)
import requests url = "http://m.ip138.com/sj.asp?mobile=" kv = {'user-agent':'Mozilla/5.0' ...
- python3各数据类型的常用方法
python3数据类型包括: 数字.字符串str.列表list.元组tuple.字典dict.集合set.布尔bool 1.字符串(str)-可变-用"".''定义 (1)uppe ...
- Flutter 下载篇 - 叁 | 网络库切换实践与思考
前言 本文是关于使用flutter_download_manager下载功能的实践和探索.我们将基于flutter_download_manager的功能扩展,改造成自己想要的样子.在阅读本文之前,建 ...
- kubernetes(k8s) 安装 Prometheus + Grafana
kubernetes(k8s) 安装 Prometheus + Grafana 组件说明 MetricServer:是kubernetes集群资源使用情况的聚合器,收集数据给kubernetes集群内 ...
- 人工智能 deepface 换脸技术 学习
介绍 Deepface是一个轻量级的python人脸识别和人脸属性分析(年龄.性别.情感和种族)框架.它是一种混合人脸识别框架缠绕状态的最先进的模型:VGG-Face,Google FaceNet,O ...
- w10共享打印机出现011b错误
错误描述:在更新里面没有发现所说的500补丁,可就是报上面的错误,然后百度找答案 解决方案1 WIN10无法连接共享打印机0x0000011b的解决方法,不用卸载更新,在共享打印机的电脑上,打开注册表 ...
- python 启动外部程序四种方法
在Python中,可以方便地使用os模块来运行其他脚本或者程序,这样就可以在脚本中直接使用其他脚本或程序提供的功能,而不必再次编写实现该功能的代码.为了更好地控制运行的进程,可以使用win32proc ...
- [网络]NAT与内网穿透技术初探【待续】
1 局域网网段IP 要真正了解NAT就必须先了解现在IPv4地址的使用情况,私有 IP 地址是指内部网络或主机的IP 地址,公有IP 地址是指在因特网上全球唯一的IP 地址.RFC 1918 为私有网 ...
- Nordic nRF52系列/nRF5340硬件设计(一)选型及原理图设计
Nordic 的BLE系列芯片从第一代的nRF51系列,到第二代的nRF52系列,发展到目前最新的第三代的nRF5340.目前市场中使用最多的nRF52系列一共有七款芯片,它们是:nRF52805.n ...