rabbitmq消息队列——"发布订阅"
三、”发布订阅”
上一节的练习中我们创建了一个工作队列。队列中的每条消息都会被发送至一个工作进程。这节,我们将做些完全不同的事情——我们将发送单个消息发送至多个消费者。这种模式就是广为人知的“发布订阅”模式。
为了说明这种模式,我们将构建一个简单的日志系统。包括2个应用程序,一个传送日志消息另一个接收并打印这些消息。
我们的日志系统中每一个运作的接收端程序都会收到这些消息。这种方式下,我们就可以运行一个接收端发送日志消息至硬盘,同时可以运行另一个接收端将日志打印到屏幕上。
理论上讲,已发布的日志消息将会被广播到所有的接收者。
交换器(Exchange)
之前的几节练习中我们发送接收消息都是在队列中进行,是时候介绍下RabbitMQ完整的消息传递模式了。
先来迅速的回顾下我们之前章节:
- 一个生产者就是一个用来发送消息的应用程序
- 一个 队列好比存储消息的缓存buffer
- 一个消费者就是一个用户应用程序用来接收消息
RabbitMQ消息传递模型的核心思想是生产者从来不会直接发送消息至队列。事实上,生产者经常都不知道消息会被分发至哪个队列。
相反的是,生产者仅仅发送消息至交换器。交换器是非常简单的东西:一边从生产者那边接收消息一边发送这些消息至队列。交换器必须准确的知道这些被接收的消息该如何处理。它应该被添加到某个特定队列?或者添加到多个队列?甚至直接放弃。具体的传输规则就是通过交换器类型来定义的。
交换器类型有四种:direct、topic、headers、fanout。这节我们主要关注最后一种——fanout。让我们来创建一个fanout类型的交换器,命名为logs:
err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
正如你从名字中猜测的一样,它仅仅广播所有消息到所有已知的接收队列。实际上这正是我们需要的日志系统。
备注:之前的几节练习中我们并不知道交换器,但我们依然能够将消息发送至队列中,之所以可以实现是因为我们使用了默认的交换器,使用空字符串表示。
回顾下之前我们发送消息是这样子的:
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
这里我们可以使用默认也可以自己命名交换器:如果路由键存在的话,消息会被路由到加上路由键参数的地址,注意fanout类型会直接忽略路由键的存在。
以下是修改后的代码:
err = ch.ExchangeDeclare(
"logs", // name 定义一个名为logs的交换器
"fanout", // type 交换器类型为fanout即广播类型
true, // durable 持久化
false, // auto-deleted 无队列绑定时是否自动删除
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange") body := bodyFrom(os.Args)
err = ch.Publish(
"logs", // exchange 指定消息发送的交换器名称
"", // routing key 因为fanout类型会自动忽略路由键,所以这里的路由键参数任意,一般不填
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
临时队列
你可能记得之前我们声明队列的时候都会指定一个队列名称(记得hello和task_queue?)。队列的命名对我们来说至关重要——我们需要将工作进程指向同一个队列。当你需要在消费者和生产者之间共享队列的话声明队列就显得很重要。
但这对我们的日志系统来说无关重要。我们需要监听的是所有的日志消息,而不是他们中的某一类。我们只关注当前流中的消息而不关注旧的那些。解决这个我们需要做两件事。
首先,每当链接RabbitMQ的时候我们需要创建一个新的、空的队列。为做到这点,我们必须创建一个名称随机的队列,甚至更好的实现方式是——让服务端给我们自动生成一个随机的队列。
其次,一旦消费者链接断开,该队列便会自动删除。
在amqp客户端中,当我们给一个队列名称设定为空字符串时,我们就创建了一个非持久化的生成队列:
q, err := ch.QueueDeclare(
"", // name 满足第一点:服务端自动产生随机队列
false, // durable
false, // delete when usused
true, // exclusive 满足第二点:连接断开立即删除
false, // no-wait
nil, // arguments
)
当该方法返回的时候,声明好的队列便包含一个由RabbitMQ生成的随机队列名称。举例来说,队列名称形如:amq.gen-JzTY20BRgKO-HjmUJj0wLg这种的。
当消费者的链接宣布关闭后,队列便像exclusive参数设置的那样,自动删除。
绑定
我们已经创建了一个fanout类型的交换器和一个队列,现在我们需要告诉交换器将消息发送至我们的队列。这种交换器和队列中的关联关系就叫做绑定。
err = ch.QueueBind(
q.Name, // queue name 绑定的队列名称
"", // routing key 绑定的路由键
"logs", // exchange 绑定的交换器名称
false,
nil
)
从现在起,logs交换器便能发送消息至我们的队列。
糅合在一起
生产者的程序,也就是发送消息端,跟之前几节的发送代码差不多。最重要的是我们现在要发送消息到logs交换器而非默认的交换器。发送的时候我们可以设置一个路由键,但是对于fanout类型的交换器来说它将被忽略。下面就是发送日志方的代码:
// rabbitmq_3_emit_log.go project main.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", err, msg)
panic(fmt.Sprintf("%s:%s", err, msg))
}
} func bodyForm(args []string) string {
var s string
if len(args) < || os.Args[] == "" {
s = "Hello World! This is a test!"
} else {
s = strings.Join(args[:], " ")
}
return s
} func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "failed to dial rabbitmq server")
defer conn.Close() ch, err := conn.Channel()
failOnError(err, "failed to declare the channel")
defer ch.Close() //声明一个交换器,交换器名称logs,类型fanout
err = ch.ExchangeDeclare("logs", "fanout", true, false, false, false, nil)
failOnError(err, "failed to declare the exchange") body := bodyForm(os.Args)
//发送消息到交换器
err = ch.Publish("logs", "", false, false, amqp.Publishing{
Body: []byte(body),
ContentType: "text/plain",
})
failOnError(err, "failed to publish the message")
}
备注:这里发送方并不需要声明队列之类的,不像之前的代码需要声明,这里的发送方唯一关联的是交换器,所以只需声明交换器并发送消息至交换器即可。
正如你想的那样,链接建立后我们声明交换器,这一步是必须的因为发送消息到一个不存在的交换器是完全禁止的。
如果该交换器上面没有队列绑定的话那么发送至该交换器的消息将全部丢失,但这对我们来时ok;如果没有消费者我们会安全地丢弃这些消息。
下面是日志接收方的代码:
// rabbitmq_3_receive_logs.go project main.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", err, msg)
panic(fmt.Sprintf("%s:%s", err, msg))
}
} func bodyForm(args []string) string {
var s string
if len(args) < || os.Args[] == "" {
s = "Hello World! This is a test!"
} else {
s = strings.Join(args[:], " ")
}
return s
} func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "failed to dial rabbitmq server")
defer conn.Close() ch, err := conn.Channel()
failOnError(err, "failed to declare the channel")
defer ch.Close() //声明一个交换器,交换器名称logs,类型fanout
err = ch.ExchangeDeclare("logs", "fanout", true, false, false, false, nil)
failOnError(err, "failed to declare the exchange") //声明一个队列
q, err := ch.QueueDeclare("", false, false, true, false, nil)
failOnError(err, "failed to declare the queue") //设置绑定(第二个参数为路由键,这里为空)
err = ch.QueueBind(q.Name, "", "logs", false, nil)
failOnError(err, "failed to bind the 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.go > logs_from_rabbit.log
如果你仅仅想在屏幕上查看日志,开启一个新的控制台执行如下命令:
go run receive_logs.go
当然了,你最后还要发出日志才行:
go run emit_log.go
使用rabbitmqctl list_bindings命令可以直接查看所有的绑定,如运行2个receive_logs.go程序你就会看到如下输出:
rabbitmqctl list_bindings
Listing bindings ...
logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []
logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []
...done.
实际效果:
分别开启两个控制台,均监听相同队列,同时收到消息并打印了,说明两个随机的队列均收到了logs交换器发来的消息,发送方略。
rabbitmq消息队列——"发布订阅"的更多相关文章
- Spring Data Redis实现消息队列——发布/订阅模式
一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现. 定义:生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...
- redis实现消息队列&发布/订阅模式使用
在项目中用到了redis作为缓存,再学习了ActiveMq之后想着用redis实现简单的消息队列,下面做记录. Redis的列表类型键可以用来实现队列,并且支持阻塞式读取,可以很容易的实现一个高性 ...
- 【转】redis 消息队列发布订阅模式spring boot实现
最近做项目的时候写到一个事件推送的场景.之前的实现方式是起job一直查询数据库,看看有没有最新的消息.这种方式非常的不优雅,反正我是不能忍,由于羡慕本身就依赖redis,刚好redis 也有消息队列的 ...
- 使用EasyNetQ组件操作RabbitMQ消息队列服务
RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue)的开源实现,是实现消息队列应用的一个中间件,消息队列中间件是分布式系统中重要的组件,主要解决应用耦合, ...
- (五)RabbitMQ消息队列-安装amqp扩展并订阅/发布Demo(PHP版)
原文:(五)RabbitMQ消息队列-安装amqp扩展并订阅/发布Demo(PHP版) 本文将介绍在PHP中如何使用RabbitMQ来实现消息的订阅和发布.我使用的系统依然是Centos7,为了方便, ...
- rabbitMQ消息队列1
rabbitmq 消息队列 解耦 异步 优点:解决排队问题 缺点: 不能保证任务被及时的执行 应用场景:去哪儿, 同步 优点:保证任务被及时的执行 缺点:排队问题 大并发 Web nginx 1000 ...
- RabbitMQ 消息队列 应用
安装参考 详细介绍 学习参考 RabbitMQ 消息队列 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. M ...
- (十一)RabbitMQ消息队列-如何实现高可用
原文:(十一)RabbitMQ消息队列-如何实现高可用 在前面讲到了RabbitMQ高可用集群的搭建,但是我们知道只是集群的高可用并不能保证应用在使用消息队列时完全没有问题,例如如果应用连接的Rabb ...
- (十二)RabbitMQ消息队列-性能测试
原文:(十二)RabbitMQ消息队列-性能测试 硬件配置 宿主机用的联想3850X6的服务器四颗E7-4850v3的处理器,DDR4内存,两块1.25TB的pcie固态.在宿主机上使用的事esxi5 ...
随机推荐
- ElasticSearch学习-centos下安装
1.安装java运行环境(jre) //这里我安装了jdk 其实只需要安装jre就可以了 0)cd /usr;mkdir /usr/java; cd java 1)wget http://downlo ...
- jquery实现手风琴效果
html----accordion.html <!DOCTYPE html> <html lang="en"> <head> <meta ...
- 「2014-3-11」HTTP 初步探究
网络上存在很多资源,也持续不断地生成新的资源.为了新建.获取和操作这些资源,引来了两个问题:如何定位资源,如何对他们进行操作.第一个问题引申出了 URI / URL 即 uniform resourc ...
- 使用OFBIZ 时,使用的键入提示。
对商品的键入提示 ,效果如图(当输入关键字时,会提示出相应的数据) 首先要引入相应的插件 页面字段 js方法
- 04、AngularJS的ng-bind、多个控制器和apply
这篇,讲一下angularjs的ng-bind指令,多个控制器,以及手动触发angularjs的脏检查,我直接把代码贴,顺着代码讲. <!DOCTYPE html> <html> ...
- 使用属性android:onClick,出现异常NoSuchMethodException
在Activity中注册点击事件有两种方式,setOnClickListener或在xml中设置控件的android:onClick="gotoSecond"属性,在Activit ...
- Lua 栈的理解
提到C++与lua互调,不可不提栈. 栈是C++和Lua相互通讯的一个地方. 首先这个栈并不是传统意义上的栈(传统的栈需要放同一种数据类型,但在网上的某些资料说,每个栈元素是一个联合体). 栈从上向下 ...
- 基于AutoCAD的ObjectARX之NET扩展(mcnetarx)-AcdbEntMake
1.创建一个结果缓冲区. 2.调用AcdbEntMake创建对象. 示例: ' 创建文字实体 Dim rb As ResultBuffer = New ResultBuffer rb.Add(New ...
- Mysql运行SQL文件 错误Incorrect table definition;there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause
问题描述 想从服务器上DOWN下数据库.操作:先把数据库转存为SQL文件,然后在本地利用navicate运行SQL文件,出现错误信息: Incorrect table definition;there ...
- 扫描二维码判断移动设备(Android/ios),以及判断是否微信端扫描
<section class="download"> <a href="apk地址" class="android" st ...