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:[email protected]: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:[email protected]: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.

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

原文地址:https://www.cnblogs.com/zhouqi666/p/12044320.html

时间: 2024-10-09 02:04:19

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

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

在第一个教程中,我们编写了程序来发送和接收来自命名队列的消息. 在这一部分中,我们将创建一个工作队列,该队列将用于在多个worker之间分配耗时的任务. 工作队列(又称任务队列)的主要思路是避免立即执行资源密集型任务(比如耗时较长的邮件发送.文件处理等),而不得不等待它完成. 相反,我们安排任务在以后完成(异步完成). 我们将任务封装为消息并将其发送到队列. 在后台运行的工作进程将获取任务并最终执行作业. 当您运行许多worker时,他们将共享任务. 这个概念在Web应用程序中特别有用,因为在W

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

介绍 RabbitMQ是消息中间件:它接受并转发消息. 您可以将其视为邮局系统:将要发送的邮件放在邮箱中时, 可以确保邮递员最终将邮件传递给收件人. 以此类推,RabbitMQ是一个邮箱,一个邮局和一个邮递员. RabbitMQ与邮局之间的主要区别在于, 它不处理纸张,而是接收,存储和转发数据消息的二进制数据. 以下是RabbitMQ和消息发送的术语 Producer:生产者.负责生产消息. Queue:队列.负责存储消息.队列在RabbitMQ中充当邮箱的角色,消息传递到RabbitMQ中,只

RabbitMQ入门教程(三):Hello World

原文:RabbitMQ入门教程(三):Hello World 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/vbirdbest/article/details/78583480 分享一个朋友的人工智能教程(请以"右键"->"在新标签页中打开连接"的方式访问).比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. Hello World示例功能简介 功能描述

3.6.4 RabbitMQ教程四 - Publish/Subscribe

Publish/Subscribe发布/订阅 What This Tutorial Focuses On In the previous tutorial we created a work queue. The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we'll do something completely different -- we

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

在上一教程中,我们改进了日志记录系统. 我们没有使用只能进行虚拟广播的fanout交换器,而是使用直接交换器,并有可能选择性地接收日志. 尽管使用直接交换改进了我们的系统,但它仍然存在局限性-它不能基于多个条件进行路由. 在我们的日志记录系统中,我们可能不仅要根据严重性订阅日志,还要根据发出日志的源订阅日志. 您可能从syslog unix工具中了解了这个概念,该工具根据严重性(info / warn / crit ...)和工具(auth / cron / kern ...)路由日志. 这将为

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

在上一教程中,我们构建了一个简单的日志记录系统. 我们能够向许多消费者广播日志消息. 在本教程中,我们将向其中添加功能-我们将使仅订阅消息的子集成为可能. 例如,我们将只能将严重错误消息定向到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息. 绑定 在前面的示例中,我们已经在创建绑定. 您可能会想起类似的代码: err = ch.QueueBind( q.Name, // queue name "", // routing key "logs",

RabbitMQ(三) -- Publish/Subscribe

RabbitMQ(三) -- Publish/Subscribe `rabbitmq`支持一对多的模式,一般称为发布/订阅.也就是说,生产者产生一条消息后,`rabbitmq`会把该消息分发给所有的消费者. Exchanges 之前的教程中,仅仅使用了基本的消息模型: 生产者产生消息 把消息添加到消息队列 消费者接收消息 而在`rabbitmq完整的消息模型`中,并不是这样的.事实上,生产者并不知道消息是否发送到队列,而是把消息直接发送给`Exchanges`. `Exchanges`的功能理解

RabbitMQ学习(三).NET Client之Publish/Subscribe

3 Publish/Subscribe Sending messages to many consumers at once Python | Java | Ruby | PHP| C# 转载请注明出处:jiq?钦's technical Blog Publish/Subscribe (using the .NET Client) 前面的教程我们已经学习了如何创建工作队列,工作队列背后的假设是每一个任务都被准确地递送给一个worker进行处理.这里我们将介绍完全不同的模式,即一个消息可以递送给多

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

工作队列模式是直接在生产者与消费者里声明好一个队列,这种情况下消息只会对应同类型的消费者. 举个用户注册的列子:用户在注册完后一般都会发送消息通知用户注册成功(失败).如果在一个系统中,用户注册信息有邮箱.手机号,那么在注册完后会向邮箱和手机号都发送注册完成信息.利用MQ实现业务异步处理,如果是用工作队列的话,就会声明一个注册信息队列.注册完成之后生产者会向队列提交一条注册数据,消费者取出数据同时向邮箱以及手机号发送两条消息.但是实际上邮箱和手机号信息发送实际上是不同的业务逻辑,不应该放在一块处