RabbitMQ

  • 在上一节中我们创建了工作队列,并且假设每一个任务都能够准确的到达对应的worker。在本节中我们将介绍如何将一个消息传递到多个消费者,这也就是所说的发布订阅模式
  • 为了验证该模式我们使用两个建立一个简单的打印系统,一个负责发出消息,另一个负责接收并打印。在该系统多个receiver中,其中一个直接将日志写入到硬盘,另一个负责从屏幕上查看日志
  • 在之前的简介中,我们可以作以下简单总结:
    • 生产者负责发送消息
    • 队列是一个存储消息的缓冲区
    • 消费者负责接收消息

RabbitMQ消息传递模型的核心思想是,生产者永远不会将任何消息直接发送到队列,实际上,通常生产者甚至不知道消息是否被传递到某个队列。

相反,生产者只能向交换器发送消息。交换器一边接收来自生产者发布的消息一边将消息放入到队列当中。可以通过exchangeType来设置交换器对消息的处理,比如拼接到指定的队列,或是拼接到多个队列中,或是丢弃。

exchange Type有以下几种:direct,topic,headers,fanout。我们先使用最后一种创建相应的交换器并取名logs:

err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)

fanout模式就是广播所有接收到的消息到它已知的所有队列当中

使用以下命令可以罗列RabbitMQ中所有的交换器:
sudo rabbitmqctl list_exchanges

在之前的例子中我们没有使用交换器但是依旧可以发送消息到队列当中,说明我们已经使用了默认的交换器,我们可以看下以前的代码:

err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})

在这里我们使用了默认的交换器:消息将被依据routering_key指定的名字路由到队列中.

一旦我们定义好了交换器,则可以在生产者发送消息的时候使用:

err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
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
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})

临时队列

我们想要获取所有日志消息不只是子集,同时我们只对当前的信息流感兴趣,为了解决这个问题我们需要两个东西:

首先,我们需要一个新的空的队列不管我们是否有链接Rabbit,我们可以使用一个随机名字创建一个队列,或是让系统指定给我们

其次,一旦我们断开与消费者的链接,队列必须自动删除。

在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类型的交换器和一个队列,接下来我们我们需要让交换器将消息发送到我们队列中,将交换器(exchange)和队列(queue)关联起来称为绑定

err = ch.QueueBind(
q.Name, // 队列名 name
"", // routing key
"logs", // 交换器名
false,
nil
)

经过以上关联之后,logs交换器就会将消息拼接到我们的队列当中。

罗列出所有的绑定:
rabbitmqctl list_bindings

完整代码如下:

emit.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", 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", // name
"fanout", // type
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
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message") log.Printf(" [x] Sent %s", body)
} func bodyFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "hello"
} else {
s = strings.Join(args[1:], " ")
}
return s
}

receive.go

package main

import (
"github.com/streadway/amqp"
"log"
) func main() {
conn,err := amqp.Dial("amqp://guest:guest@localhost:5672/")
DealWithError(err,"Failed to connect to RabbitMQ")
defer conn.Close() ch,err := conn.Channel()
DealWithError(err,"Failed to open a channel")
defer ch.Close()
//声明交换器
ch.ExchangeDeclare(
"logs",
"fanout",
true,
false,
false,
false,
nil,
)
DealWithError(err,"Failed to declare an exchange")
//声明了队列
q,err := ch.QueueDeclare(
"", //队列名字为rabbitMQ自动生成
false,
false,
true,
false,
nil,
)
DealWithError(err,"Failed to declare an exchange")
//交换器跟队列进行绑定,交换器将接收到的消息放进队列中
err = ch.QueueBind(
q.Name,
"",
"logs",
false,
nil,
)
DealWithError(err,"Failed to bind a queue")
msgs,err := ch.Consume(
q.Name,
"",
true,
false,
false,
false,
nil,
)
DealWithError(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
} func DealWithError(err error,msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}

Go RabbitMQ(三)发布订阅模式的更多相关文章

  1. RabbitMQ的发布订阅模式(Publish/Subscribe)

    一.发布/订阅(Publish/Subscribe)模式 发布订阅是我们经常会用到的一种模式,生产者生产消息后,所有订阅者都可以收到.RabbitMQ的发布/订阅模型图如下: 1.该模式下生产者并不是 ...

  2. RabbitMQ入门-发布订阅模式

    兔子的Publish/Subscribe是这样的: 有个生产者P,X代表交换机,交换机绑定队列,消费者从队列中取得消息.每次有消息,先发到交换机中,然后由交换机负责发送到它已知的队列中. 生产者代码: ...

  3. rabbitmq (三) 发布/订阅

    rabbitmq的目的并不是让生产者把消息直接发到队列里面去, 这样不能实现解耦的目的,也不利于程序的扩展. 所以就有交换机(exchanges)的概念. 交换机有几种类型:direct, topic ...

  4. 第三节: List类型的介绍、生产者消费者模式、发布订阅模式

    一. List类型基础 1.介绍 它是一个双向链表,支持左进.左出.右进.右出,所以它即可以充当队列使用,也可以充当栈使用. (1). 队列:先进先出, 可以利用List左进右出,或者右进左出(Lis ...

  5. python使用rabbitMQ介绍三(发布订阅模式)

    一.模式介绍 在前面的例子中,消息直接发送到queue中. 现在介绍的模式,消息发送到exchange中,消费者把队列绑定到exchange上. 发布-订阅模式是把消息广播到每个消费者,每个消费者接收 ...

  6. RabbitMQ学习第三记:发布/订阅模式(Publish/Subscribe)

    工作队列模式是直接在生产者与消费者里声明好一个队列,这种情况下消息只会对应同类型的消费者. 举个用户注册的列子:用户在注册完后一般都会发送消息通知用户注册成功(失败).如果在一个系统中,用户注册信息有 ...

  7. RabbitMQ指南之三:发布/订阅模式(Publish/Subscribe)

    在上一章中,我们创建了一个工作队列,工作队列模式的设想是每一条消息只会被转发给一个消费者.本章将会讲解完全不一样的场景: 我们会把一个消息转发给多个消费者,这种模式称之为发布-订阅模式. 为了阐述这个 ...

  8. RabbitMQ六种队列模式-发布订阅模式

    前言 RabbitMQ六种队列模式-简单队列RabbitMQ六种队列模式-工作队列RabbitMQ六种队列模式-发布订阅 [本文]RabbitMQ六种队列模式-路由模式RabbitMQ六种队列模式-主 ...

  9. NetMQ(三): 发布订阅模式 Publisher-Subscriber

    ZeroMQ系列 之NetMQ 一:zeromq简介 二:NetMQ 请求响应模式 Request-Reply 三:NetMQ 发布订阅模式 Publisher-Subscriber 四:NetMQ ...

随机推荐

  1. 设置 ssh 使用public key 免密码登录

    第一步,生成自己公钥, 私钥 1: ssh-keygen -t rsa 2:   3: root@yjlml:~# ssh-keygen -t rsa 4: Generating public/pri ...

  2. SQL Server中CROSS APPLY和OUTER APPLY应用

    1.什么是Cross Apply和Outer Apply ? 我们知道SQL Server 2000中有Cross Join用于交叉联接的.实际上增加Cross Apply和Outer Apply是用 ...

  3. Window 10 单机配置MYSQL主从同步

    Master数据库:127.0.0.1:3306 Slave数据库:127.0.0.1:3307 Master操作 修改ini信息 Master MYSQL安装目录下,找到my.ini,在[mysql ...

  4. 对this的理解?

    回答一: (1) Js的this指向是不确定的,也就是说可以动态改变的.Call/apply 就是用于改变this指向的函数,这样设计可以让代码更加灵活,复用性更高 (2) This一般情况下,都是指 ...

  5. “全栈2019”Java异常第十七章:Error详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  6. django入门-模型-part2

    尊重作者的劳动,转载请注明作者及原文地址 http://www.cnblogs.com/txwsqk/p/6511177.html 完全翻译自官方文档 https://docs.djangoproje ...

  7. ElasticSearch5.0——IK词库加载

    Dictionary ConfigurationIKAnalyzer.cfg.xml can be located at {conf}/analysis-ik/config/IKAnalyzer.cf ...

  8. Java中将图片保存到数据库中

    在实际的开发中,我们可能需要将图片.影音等文件直接保存到数据库中,然后通过编程方式将数据读出进行使用.例如将读出的图片数据显示出来,将读出的电影文件播放出来. 二进制数据直接保存到文件和从文件中读出非 ...

  9. Linux 开机过程(转)

    Linux 开机过程 初始化 POST(加电自检)并执行硬件检查: 当 POST 完成后,系统的控制权将移交给启动管理器的第一阶段(first stage),它存储在一个硬盘的引导扇区(对于使用 BI ...

  10. 理解 atime,ctime,mtime (下)

    话不多说,开始下篇. # 前言 通过 "理解 atime,ctime,mtime (上)" 我们已经知道了atime 是文件访问时间:ctime是文件权限改变时间:mtime是文件 ...