RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现)
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语言实现)的更多相关文章
- RabbitMQ官方教程二 Work Queues(GOLANG语言实现)
RabbitMQ官方教程二 Work Queues(GOLANG语言实现) 在第一个教程中,我们编写了程序来发送和接收来自命名队列的消息. 在这一部分中,我们将创建一个工作队列,该队列将用于在多个wo ...
- RabbitMQ官方教程一Hello World(GOLANG语言实现)
介绍 RabbitMQ是消息中间件:它接受并转发消息. 您可以将其视为邮局系统:将要发送的邮件放在邮箱中时, 可以确保邮递员最终将邮件传递给收件人. 以此类推,RabbitMQ是一个邮箱,一个邮局和一 ...
- RabbitMQ入门教程(三):Hello World
原文:RabbitMQ入门教程(三):Hello World 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog. ...
- 译: 3. RabbitMQ Spring AMQP 之 Publish/Subscribe 发布和订阅
在第一篇教程中,我们展示了如何使用start.spring.io来利用Spring Initializr创建一个具有RabbitMQ starter dependency的项目来创建spring-am ...
- RabbitMQ官方教程五 Topic(GOLANG语言实现)
在上一教程中,我们改进了日志记录系统. 我们没有使用只能进行虚拟广播的fanout交换器,而是使用直接交换器,并有可能选择性地接收日志. 尽管使用直接交换改进了我们的系统,但它仍然存在局限性-它不能基 ...
- RabbitMQ官方教程四 Routing(GOLANG语言实现)
在上一教程中,我们构建了一个简单的日志记录系统. 我们能够向许多消费者广播日志消息. 在本教程中,我们将向其中添加功能-我们将使仅订阅消息的子集成为可能. 例如,我们将只能将严重错误消息定向到日志文件 ...
- 译:3.RabbitMQ Java Client 之 Publish/Subscribe(发布和订阅)
在上篇 RabbitMQ 之Work Queues (工作队列)教程中,我们创建了一个工作队列,工作队列背后的假设是每个任务都交付给一个工作者. 在这一部分,我们将做一些完全不同的事情 - 我们将向多 ...
- RabbitMQ 之 订阅模式 Publish/Subscribe
模型图 我们之前学习的都是一个消息只能被一个消费者消费,那么如果我想发一个消息 能被多个消费者消费,这时候怎么办? 这时候我们就得用到了消息中的发布订阅模型 在前面的教程中,我们创建了一个工作队列,都 ...
- RabbitMQ+PHP教程
RabbitMQ+PHP 教程一(Hello World) RabbitMQ+PHP 教程二(Work Queues) RabbitMQ+PHP 教程三(Publish/Subscribe) Rabb ...
随机推荐
- StringSequences
题意: 给出两个长度不超过\(50\)的字符串\(S, T\),每次可以在\(S\)中插入一个字符,把每次操作后的\(S\)写成一个序列,问有多少种不同的序列. 注意到我们可以把\(S\)拆分成一段一 ...
- 讲解SQL数据库语句
前言 大家好,我是 Vic,今天给大家带来讲解SQL数据库语句的概述,希望你们喜欢 数据库语句 create database teach; use teach; create table `teac ...
- Maven 异常 druid jar冲突
异常: 十二月 25, 2017 11:04:41 下午 org.apache.tomcat.util.digester.SetPropertiesRule begin 警告: [SetPropert ...
- php md5算法
<!DOCTYPE html> <html> <body> <?php $str = "Shanghai"; echo md5($str) ...
- python3.x中lambda表达式的处理与python2不一样
lambda表达式,在python2中的表达式和python3不同,原来只要: >>> map(lambda x: x ** 2, [1, 2, 3, 4, 5]) ...
- 20190710用控制台启动一个wcf服务
快速阅读 如何用控制台启动一个wcf服务,已经wcf的配置和在类库中如何实现 . wcf类库 用vs新建一个类库,引用system.ServiceModel 定义接口实现服务契约和操作契约 [Serv ...
- Ubuntu 18.04.1 安装mysql 5.7.27
sudo apt-get update sudo apt-get install mysql-server 配置 sudo mysql_secure_installation 参考文档 Ubuntu1 ...
- SQL语句简单增删改查
常用数据类型 Int:整数,长度没有作用 Varchar:字符串,varchar(3)表示最多存放3个字符,1个中文或英文或符合都占1个字符 Decimal:小数,decimal(5,2)表示共存5位 ...
- JVM 主动类和被动类的使用
主动使用和被动使用Demo 1.创建工程一个Gradle工程 下一步 下一步 点击完成 2.创建类 public class MyTest1 { public static void main(Str ...
- (转)AutoML 与轻量模型大列表: awesome-AutoML-and-Lightweight-Models
Awesome-AutoML-and-Lightweight-Models 原文:http://bbs.cvmart.net/articles/414/zi-yuan-automl-yu-qing-l ...