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. 连接池--sp_reset_connection

    --当客户端使用连接池访问数据库时,客户端使用OPEN来重用数据库连接,使用CLOSE来断开数据库连接,但并不物理上新建和断开连接,因此可以提高程序运行速度并降低性能损耗. --ADO和ADO.NET ...

  2. Linux socat轻松实现TCP/UDP端口转发

    1.TCP端口转发 socat -d TCP4-LISTEN:,reuseaddr,fork TCP4: 2.UDP端口转发 socat -T UDP4-LISTEN:,reuseaddr,fork ...

  3. Easy前端正确删除datagrid的方式(避免直接删除索引没更新问题)

    在删除传参时,不要传索引来删除行 columns: [[ { title: '代码', field: 'Code', width: 100 }, { title: '名称', field: 'Name ...

  4. Winfrom PictureBox 设置图片自适应

    初始状态 Bitmap bm = new Bitmap(Image.FromStream(System.Net.WebRequest.Create(new Uri(result.Result)).Ge ...

  5. jQuery查找标签--选择器,筛选器,模态对话框, 左侧菜单栏

    查找标签 选择器: 基本选择器(同css) id选择器 $("#id") 标签选择器 $('tagName') class选择器 $(".className") ...

  6. PHP set_error_handler()函数的使用

    我们写程序,难免会有问题(是经常会遇到问题 ),而PHP遇到错误时,就会给出出错脚本的位置.行数和原因.有很多人说,这并没有什么大不了.确实,在调试程序阶段,这确实是没啥的,而且我认为给出错误路径是必 ...

  7. P5277 【模板】多项式开根(加强版)(bsgs or Cipolla)

    题面 传送门 题解 首先你得会多项式开根->这里 其次你得会解形如 \[x^2\equiv a \pmod{p}\] 的方程 这里有两种方法,一个是\(bsgs\)(这里),还有一种是\(Cip ...

  8. 【bzoj4998】星球联盟(并查集+边双)

    题面 传送门 题解 总算有自己的\(bzoj\)账号啦! 话说这题好像\(Scape\)去年暑假就讲过--然而我到现在才会-- \(LCT\)什么的跑得太慢了而且我也不会,所以这里是一个并查集的做法 ...

  9. PHP中define()和dirname(__FILE__)

    1,define() 函数定义一个常量.常量类似变量,不同之处在于: (1)在设定以后,常量的值无法更改 (2)常量名不需要开头的美元符号 ($) (3)作用域不影响对常量的访问 (4)常量值只能是字 ...

  10. VMware 虚拟机报错解决

    Could not apply the stored configuration for monitors none of the selected modes were compatible wit ...