RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现)

在上一个教程中,我们创建了一个工作队列。 工作队列背后的假设是,每个任务都恰好交付给一个worker处理。 在这一部分中,我们将做一些完全不同的事情-我们将消息传达给多个消费者。 这种模式称为“发布/订阅”。

为了说明这种模式,我们将构建一个简单的日志记录系统。 它由两个程序组成-第一个程序将发出日志消息,第二个程序将接收并打印它们。

在我们的日志系统中,消费者每个运行副本都将获得消息。 这样,我们将能够运行一个消费者并将日志写入到磁盘。 同时我们将能够运行另一个消费者并在屏幕上查看日志。

本质上,已发布的日志消息将被广播到所有接收者。

交换器

在本教程的前面部分中,我们向队列发送消息和从队列接收消息。 现在是时候在Rabbit中引入完整的消息传递模型了。

让我们快速回顾一下先前教程中介绍的内容:

  • 生产者是发送消息的用户应用程序。
  • 队列是存储消息的缓冲区。
  • 使用者是接收消息的用户应用程序。

RabbitMQ消息传递模型中的核心思想是生产者从不将任何消息直接发送到队列。 实际上,生产者经常甚至根本不知道是否将消息传递到任何队列。

实际上,生产者只能将消息发送到交换器。 交换器的功能非常简单。 一方面,它接收来自生产者的消息,另一方面,将它们推入队列。 交换器必须确切知道如何处理收到的消息。 是否应将其附加到特定队列? 是否应该将其附加到许多队列中? 还是应该丢弃它。 这些规则由交换类型定义。

有几种交换类型可用:direct, topic, headers and fanout。 我们将集中讨论最后一个-fanout。 让我们创建这种类型的交换,并将其称为日志:

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

fanout非常简单。 正如您可能从名称中猜测的那样,它只是将接收到的所有消息广播到它知道的所有队列中。 而这正是我们日志记录所需要的。

交换器列表

要列出服务器上的交换器,您可以运行rabbitmqctl命令:

sudo rabbitmqctl list_exchanges

在此列表中,将有一些amq。*交换和默认(未命名)交换。 这些是默认创建的,但是您现在不太可能需要使用它们。

默认交换器

在本教程的前面部分中,我们对交换器一无所知,但仍然能够将消息发送到队列。 之所以可能,是因为我们使用的是默认交换器,该交换器由空字符串("")标识。

临时队列

您可能还记得以前我们使用的是具有特定名称的队列(还记得hello和task_queue吗?)。 能够命名队列对我们至关重要-我们需要将worker指向同一队列。 当您想在生产者和消费者之间共享队列时,给队列命名很重要。

但这不是我们的记录器的情况。 我们希望听到所有日志消息,而不仅仅是它们的一部分。 我们也只对当前正在发送的消息感兴趣,而对旧消息不感兴趣。 为了解决这个问题,我们需要两件事。

1.首先,无论何时连接到Rabbit,我们都需要一个全新的空队列。 为此,我们可以创建一个具有随机名称的队列,或者甚至更好-让服务器为我们选择一个随机队列名称。

2.其次,一旦我们断开了消费者的连接,队列将被自动删除。

在amqp客户端中,当我们以空字符串形式提供队列名称时,我们将使用生成的名称创建一个非持久化的队列:

q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)

当方法返回时,队列实例包含RabbitMQ生成的随机队列名称。 例如,它可能看起来像amq.gen-JzTY20BRgKO-HjmUJj0wLg。

当声明它的连接关闭时,该队列将被删除,因为它被声明为独占。

绑定

我们已经创建了一个fanout交换器和一个队列。 现在我们需要告诉交换器将消息发送到我们的队列。 交换和队列之间的关系称为绑定。

err = ch.QueueBind(
q.Name, // queue name
"", // routing key
"logs", // exchange
false,
nil,
)

从现在开始,日志交换将消息添加到我们的队列中。

监听绑定

您可以使用以下的命令列出现有的绑定

rabbitmqctl list_bindings

所有代码

产生日志消息的生产者与上一教程看起来没有太大不同。 最重要的变化是,我们现在希望将消息发布到日志交换器,而不是无名的消息交换器。 发送时我们需要提供一个routingKey,但是对于扇出交换,它的值将被忽略。 这是emit_log.go脚本的代码:


package main import (
"log"
"os"
"strings" "github.com/streadway/amqp"
) func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
} func main() {
// 连接RabbitMQ服务器
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
// 创建一个channel
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_logs.go的代码:

package main

import (
"log" "github.com/streadway/amqp"
) func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
} func main() {
// 连接RabbitMQ服务器
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
// 创建一个channel
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")
// 声明队列
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")
// 绑定交换器
err = ch.QueueBind(
q.Name, // queue name
"", // routing key
"logs", // 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") 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,您可以验证代码是否确实根据需要创建了绑定和队列。 在运行两个receive_logs.go程序后,您应该看到类似以下内容:

sudo rabbitmqctl list_bindings
# => Listing bindings ...
# => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []
# => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []
# => ...done.

结果的解释很简单:交换日志中的数据进入两个具有服务器分配名称的队列。 这正是我们的意图。

RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现)的更多相关文章

  1. RabbitMQ官方教程二 Work Queues(GOLANG语言实现)

    RabbitMQ官方教程二 Work Queues(GOLANG语言实现) 在第一个教程中,我们编写了程序来发送和接收来自命名队列的消息. 在这一部分中,我们将创建一个工作队列,该队列将用于在多个wo ...

  2. RabbitMQ官方教程一Hello World(GOLANG语言实现)

    介绍 RabbitMQ是消息中间件:它接受并转发消息. 您可以将其视为邮局系统:将要发送的邮件放在邮箱中时, 可以确保邮递员最终将邮件传递给收件人. 以此类推,RabbitMQ是一个邮箱,一个邮局和一 ...

  3. RabbitMQ入门教程(三):Hello World

    原文:RabbitMQ入门教程(三):Hello World 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog. ...

  4. 译: 3. RabbitMQ Spring AMQP 之 Publish/Subscribe 发布和订阅

    在第一篇教程中,我们展示了如何使用start.spring.io来利用Spring Initializr创建一个具有RabbitMQ starter dependency的项目来创建spring-am ...

  5. RabbitMQ官方教程五 Topic(GOLANG语言实现)

    在上一教程中,我们改进了日志记录系统. 我们没有使用只能进行虚拟广播的fanout交换器,而是使用直接交换器,并有可能选择性地接收日志. 尽管使用直接交换改进了我们的系统,但它仍然存在局限性-它不能基 ...

  6. RabbitMQ官方教程四 Routing(GOLANG语言实现)

    在上一教程中,我们构建了一个简单的日志记录系统. 我们能够向许多消费者广播日志消息. 在本教程中,我们将向其中添加功能-我们将使仅订阅消息的子集成为可能. 例如,我们将只能将严重错误消息定向到日志文件 ...

  7. 译:3.RabbitMQ Java Client 之 Publish/Subscribe(发布和订阅)

    在上篇 RabbitMQ 之Work Queues (工作队列)教程中,我们创建了一个工作队列,工作队列背后的假设是每个任务都交付给一个工作者. 在这一部分,我们将做一些完全不同的事情 - 我们将向多 ...

  8. RabbitMQ 之 订阅模式 Publish/Subscribe

    模型图 我们之前学习的都是一个消息只能被一个消费者消费,那么如果我想发一个消息 能被多个消费者消费,这时候怎么办? 这时候我们就得用到了消息中的发布订阅模型 在前面的教程中,我们创建了一个工作队列,都 ...

  9. RabbitMQ+PHP教程

    RabbitMQ+PHP 教程一(Hello World) RabbitMQ+PHP 教程二(Work Queues) RabbitMQ+PHP 教程三(Publish/Subscribe) Rabb ...

随机推荐

  1. 本地存储API

    一.定义 随着互联网的快速发展,基于网页的应用越来越普遍,同时也变得越来越复杂,为了满足各种各样的需求,会经常在本地存储大量的数据,HTML5规范提出了相关解决方案 本地存储设置读取方便,容量较大,s ...

  2. AtCoder Grand Contest 018题解

    传送门 \(A\) 根据裴蜀定理显然要\(k|\gcd(a_1,...,a_n)\),顺便注意不能造出大于\(\max(a_1,...,a_n)\)的数 int n,g,k,x,mx; int mai ...

  3. php单点登录SSO(Single Sign On)的解决思路

    一.什么是单点登录 解释:登录一个系统后,其它系统无需再次登录,即可进入. 二.举个例子: 你登录了淘宝,然后你进入天猫,发现你不用登录了.这时你要注意到,淘宝跟天猫可是完全不一样的域名. 你登录淘宝 ...

  4. P1378 油滴扩展——搜索小记

    P1378 油滴扩展 记得这道题好久以前(好像是上个学期?) 就想做了,但是看着里面的半径边界好像很难处理就没做(主要是当时刚学OI(菜还给自己找借口)): 今天上午一直研究SG函数,做的都自闭了,晚 ...

  5. python 装饰器demo

    本质就是一个函数,这个函数符合闭包语法结构,可以在函数不需要改变任何代码情况下增加额外功装饰器的返回值是一个函数的引用 功能:1.引入日志:2.函数执行时间统计:3.执行函数前预备处理:4.执行函数后 ...

  6. 简要描述Python的垃圾回收机制(garbage collection)

    这里能说的很多.你应该提到下面几个主要的点: Python在内存中存储了每个对象的引用计数(reference count).如果计数值变成0,那么相应的对象就会小时,分配给该对象的内存就会释放出来用 ...

  7. 5、vueJs基础知识05

    vue2.0相比于1.0的变化 1.在每个组件模板中,不再支持片段代码,需要一个根元素包裹 组件中模板: 之前: <template> <h3>我是组件</h3>& ...

  8. linux 9.故障修复和网络配置

    一.linux系统故障修复       1.不知道root密码的前提下 破解root密码 服务器必须在身边           grub引导菜单              按任意键进入->e ( ...

  9. PHP的ini_set函数用法

    PHP   ini_set用来设置php.ini的值,在函数执行的时候生效,脚本结束后,设置失效.无需打开php.ini文件,就能修改配置,对于虚拟空间来说,很方便. 函数格式:string   in ...

  10. strace命令 一

    简介 strace常用来跟踪进程执行时的系统调用和所接收的信号. 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核 ...