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

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

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

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

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

交换器

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

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

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

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

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

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

  1. err = ch.ExchangeDeclare(
  2. "logs", // name
  3. "fanout", // type
  4. true, // durable
  5. false, // auto-deleted
  6. false, // internal
  7. false, // no-wait
  8. nil, // arguments
  9. )

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

交换器列表

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

sudo rabbitmqctl list_exchanges

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

默认交换器

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

临时队列

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

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

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

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

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

  1. q, err := ch.QueueDeclare(
  2. "", // name
  3. false, // durable
  4. false, // delete when unused
  5. true, // exclusive
  6. false, // no-wait
  7. nil, // arguments
  8. )

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

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

绑定

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

  1. err = ch.QueueBind(
  2. q.Name, // queue name
  3. "", // routing key
  4. "logs", // exchange
  5. false,
  6. nil,
  7. )

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

监听绑定

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

  1. rabbitmqctl list_bindings

所有代码

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


  1. package main
  2. import (
  3. "log"
  4. "os"
  5. "strings"
  6. "github.com/streadway/amqp"
  7. )
  8. func failOnError(err error, msg string) {
  9. if err != nil {
  10. log.Fatalf("%s: %s", msg, err)
  11. }
  12. }
  13. func main() {
  14. // 连接RabbitMQ服务器
  15. conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
  16. failOnError(err, "Failed to connect to RabbitMQ")
  17. defer conn.Close()
  18. // 创建一个channel
  19. ch, err := conn.Channel()
  20. failOnError(err, "Failed to open a channel")
  21. defer ch.Close()
  22. // 声明交换器
  23. err = ch.ExchangeDeclare(
  24. "logs", // name
  25. "fanout", // type
  26. true, // durable
  27. false, // auto-deleted
  28. false, // internal
  29. false, // no-wait
  30. nil, // arguments
  31. )
  32. failOnError(err, "Failed to declare an exchange")
  33. body := bodyFrom(os.Args)
  34. err = ch.Publish(
  35. "logs", // exchange
  36. "", // routing key
  37. false, // mandatory
  38. false, // immediate
  39. amqp.Publishing{
  40. ContentType: "text/plain",
  41. Body: []byte(body),
  42. })
  43. failOnError(err, "Failed to publish a message")
  44. log.Printf(" [x] Sent %s", body)
  45. }
  46. func bodyFrom(args []string) string {
  47. var s string
  48. if (len(args) < 2) || os.Args[1] == "" {
  49. s = "hello"
  50. } else {
  51. s = strings.Join(args[1:], " ")
  52. }
  53. return s
  54. }

如您所见,建立连接后,我们声明了交换器。 由于禁止发布到不存在的交换器,因此此步骤是必需的。

如果没有队列绑定到交换,消息将丢失,但这对我们来说是可以的。 如果没有消费者在听,我们可以安全地丢弃该消息。

receive_logs.go的代码:

  1. package main
  2. import (
  3. "log"
  4. "github.com/streadway/amqp"
  5. )
  6. func failOnError(err error, msg string) {
  7. if err != nil {
  8. log.Fatalf("%s: %s", msg, err)
  9. }
  10. }
  11. func main() {
  12. // 连接RabbitMQ服务器
  13. conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
  14. failOnError(err, "Failed to connect to RabbitMQ")
  15. defer conn.Close()
  16. // 创建一个channel
  17. ch, err := conn.Channel()
  18. failOnError(err, "Failed to open a channel")
  19. defer ch.Close()
  20. // 声明交换器
  21. err = ch.ExchangeDeclare(
  22. "logs", // name
  23. "fanout", // type
  24. true, // durable
  25. false, // auto-deleted
  26. false, // internal
  27. false, // no-wait
  28. nil, // arguments
  29. )
  30. failOnError(err, "Failed to declare an exchange")
  31. // 声明队列
  32. q, err := ch.QueueDeclare(
  33. "", // name
  34. false, // durable
  35. false, // delete when unused
  36. true, // exclusive
  37. false, // no-wait
  38. nil, // arguments
  39. )
  40. failOnError(err, "Failed to declare a queue")
  41. // 绑定交换器
  42. err = ch.QueueBind(
  43. q.Name, // queue name
  44. "", // routing key
  45. "logs", // exchange
  46. false,
  47. nil,
  48. )
  49. failOnError(err, "Failed to bind a queue")
  50. msgs, err := ch.Consume(
  51. q.Name, // queue
  52. "", // consumer
  53. true, // auto-ack
  54. false, // exclusive
  55. false, // no-local
  56. false, // no-wait
  57. nil, // args
  58. )
  59. failOnError(err, "Failed to register a consumer")
  60. forever := make(chan bool)
  61. go func() {
  62. for d := range msgs {
  63. log.Printf(" [x] %s", d.Body)
  64. }
  65. }()
  66. log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
  67. <-forever
  68. }

如果要将日志保存到文件,只需打开控制台并键入:

  1. go run receive_logs.go > logs_from_rabbit.log

如果希望在屏幕上查看日志,请生成一个新的终端并运行:

  1. go run receive_logs.go

当然,要发送日志,请输入:

  1. go run emit_log.go

使用rabbitmqctl list_bindings,您可以验证代码是否确实根据需要创建了绑定和队列。 在运行两个receive_logs.go程序后,您应该看到类似以下内容:

  1. sudo rabbitmqctl list_bindings
  2. # => Listing bindings ...
  3. # => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []
  4. # => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []
  5. # => ...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. Spring入门(三)——AOP

    1. AOP aspect object programming ,简单来说就是把重复的代码抽取出来,然后再需要用到的地方进行切入,这里讲解基于接口的注解实现 2. 了解 关注点:即重复的代码 切面: ...

  2. contextMenuEvent

    #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDesktopWidget&g ...

  3. Mujin Programming Challenge 2017题解

    传送门 \(A\) 似乎并不难啊然而还是没想出来-- 首先我们发现对于一个数\(k\),它能第一个走到当且仅当对于每一个\(i<k\)满足\(x_i\geq 2i-1\),这样我们就可以把所有的 ...

  4. (23)打鸡儿教你Vue.js

    实例: 模板语法 vue-router,vuex以及调式方法介绍 打包部署: npm run build Webpack 目前无论在求职还是工作中,使用越来越普及.而想要学懂,学会Webpack更绝非 ...

  5. jquery中语法初学必备

    $(this).hide() - 隐藏当前元素 $("p").hide() - 隐藏所有段落 $(".test").hide() - 隐藏所有 class=&q ...

  6. docker技术入门(2)

    接上一篇文章 [容器技术]Docker容器技术入门(一) 今天接着上次聊一聊有关Docker网络.数据存储相关的技术点 Docker网络模式 01 Dokcer 通过使用 Linux 桥接提供容器之间 ...

  7. 消息队列Rabbit MQ 学习第一篇

    1 介绍  1.1RabbitMQ MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队 ...

  8. mysql 开放端口 外网访问

    mysql 开放端口 外网访问 作者: moyixi 时间: April 24, 2018 分类: 默认分类,数据库,mysql 前提: 如果是云服务器,请先把安全组件相应的开发 查看服务器的端口33 ...

  9. Tensorflow object detection API(1)---环境搭建与测试

    参考: https://blog.csdn.net/dy_guox/article/details/79081499 https://blog.csdn.net/u010103202/article/ ...

  10. springMvc---WebMvcConfigurationSupport

    该类提供了主要的 MVC 配置方法,通过直接继承 WebMvcConfiguration ,并在继承类上 加上 @EnableWebMvc 和 @Configuration 注解之后.便可以在子类中实 ...