本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第四篇——路由。

这些教程涵盖了使用RabbitMQ创建消息传递应用程序的基础知识。

你需要安装RabbitMQ服务器才能完成这些教程,请参阅安装指南或使用Docker镜像

这些教程的代码是开源的,官方网站也是如此。

先决条件

本教程假设RabbitMQ已安装并运行在本机上的标准端口(5672)。如果你使用不同的主机、端口或凭据,则需要调整连接设置。

路由

(使用Go RabbitMQ客户端)

上一教程中,我们构建了一个简单的日志记录系统。我们能够向许多接收者广播日志消息。

在本教程中,我们将向它添加一个特性-我们将使它能够只订阅消息的一个子集。例如,我们将只能将关键错误消息定向到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。

绑定

在前面的示例中,我们已经在创建绑定。你可能会想起以下代码:

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

绑定是交换器和队列之间的关系。这可以简单地理解为:队列对来自此交换器的消息感兴趣。

绑定可以采用额外的routing_key参数。为了避免与Channel.Publish参数混淆,我们将其称为binding key。这是我们如何使用键创建绑定的方法:

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

绑定密钥的含义取决于交换器的类型。我们以前使用的fanout交换器只是忽略了这个值。

直连交换器

我们上一个教程中的日志系统将所有消息广播给所有消费者。我们希望扩展这一点,允许根据消息的严重性过滤消息。例如,我们可能希望将日志消息写入磁盘的脚本只接收严重错误,而不会在warning或info日志消息上浪费磁盘空间。

我们使用fanout交换器,这并没有给我们很大的灵活性——它只能进行无脑广播。

我们将使用direct交换器。direct交换器背后的路由算法很简单——消息进入其binding key与消息的routing key完全匹配的队列。

为了说明这一点,请考虑以下设置:

在此设置中,我们可以看到绑定了两个队列的direct交换器X。第一个队列绑定键为orange,第二个队列绑定为两个,一个绑定键为black,另一个为green

在这种设置中,使用orange路由键发布到交换器的消息将被路由到队列Q1。路由键为blackgreen的消息将转到Q2。所有其他消息将被丢弃。

多重绑定

用相同的绑定键绑定多个队列是完全合法的。在我们的示例中,我们可以使用绑定键blackXQ1之间添加绑定。在这种情况下,direct交换器的行为将类似fanout,并将消息广播到所有匹配的队列。带有black路由键的消息将同时传递给Q1Q2

发送日志

我们将在日志系统中使用这个模型。我们将发送消息到direct交换器,而不是fanout。我们将提供严重性(译注:通常我们使用日志级别划分日志信息的严重性)作为路由键。这样,接收脚本将能够选择其想要接收的日志级别。让我们首先关注发送日志。

与往常一样,我们需要首先创建一个交换器:

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

我们已经准备好发送一条消息:

err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // 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_direct", // exchange
severityFrom(os.Args), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})

为了简化问题,我们假设“严重性”可以是“info”、“warning”、“error”之一。

订阅

接收消息的工作方式与上一教程一样,但有一个例外——我们将为感兴趣的每种严重性(日志级别)创建一个新的绑定。

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") if len(os.Args) < 2 {
log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
os.Exit(0)
}
// 建立多个绑定关系
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_direct", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_direct", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}

完整示例

emit_log_direct.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() {
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_direct", // name
"direct", // 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_direct", // exchange
severityFrom(os.Args), // 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) < 3) || os.Args[2] == "" {
s = "hello"
} else {
s = strings.Join(args[2:], " ")
}
return s
} func severityFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "info"
} else {
s = os.Args[1]
}
return s
}

receive_logs_direct.go的代码:

package main

import (
"log"
"os" "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_direct", // name
"direct", // 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") if len(os.Args) < 2 {
log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
os.Exit(0)
}
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_direct", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_direct", // 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
}

如果你只想将“warning”和“err”(而不是“info”)级别的日志消息保存到文件中,只需打开控制台并输入:

go run receive_logs_direct.go warning error > logs_from_rabbit.log

如果你想在屏幕上查看所有日志消息,请打开一个新终端并执行以下操作:

go run receive_logs_direct.go info warning error
# => [*] Waiting for logs. To exit press CTRL+C

例如,要发出error日志消息,只需输入:

go run emit_log_direct.go error "Run. Run. Or it will explode."
# => [x] Sent 'error':'Run. Run. Or it will explode.'

(这里是(emit_log_direct.go)和(receive_logs_direct.go)的完整源码)

继续学习教程5,了解如何根据模式监听消息。

RabbitMQ Go客户端教程4——路由的更多相关文章

  1. RabbitMQ Go客户端教程1——HelloWorld

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,共分为六篇,本文是第一篇--HelloWorld. 这些教程涵盖了使用RabbitMQ创建消 ...

  2. RabbitMQ Go客户端教程5——topic

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第五篇--topic. 这些教程涵盖了使用RabbitMQ创建消息传递 ...

  3. RabbitMQ Go客户端教程3——发布/订阅

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第三篇--发布/订阅. 这些教程涵盖了使用RabbitMQ创建消息传递 ...

  4. RabbitMQ Go客户端教程6——RPC

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第六篇--RPC. 这些教程涵盖了使用RabbitMQ创建消息传递应用 ...

  5. RabbitMQ Go客户端教程2——任务队列/工作队列

    本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第二篇--任务队列. 这些教程涵盖了使用RabbitMQ创建消息传递应 ...

  6. SpringBoot系列之RabbitMQ使用实用教程

    SpringBoot系列之RabbitMQ使用实用教程 @ 目录 1. 消息队列概述 1.1 MQ的概述 1.2 MQ目的地形式 2. 消息队列实现方式 2.1 常见MQ框架 2.2 MQ实现方式 3 ...

  7. RabbitMq C# .net 教程

    本文转载来自 [http://www.cnblogs.com/yangecnu/p/Introduce-RabbitMQ.html]写的很详细. 文件安装包官方DEMO下载地址是:http://pan ...

  8. vue教程3-01 路由、组件、bower包管理器使用

    vue教程3-01 路由.组件.包管理器 以下操作前提是 已经安装好node.js npm bower-> (前端)包管理器 下载: npm install bower -g 验证: bower ...

  9. C#基于RabbitMQ实现客户端之间消息通讯实战演练

    一.背景介绍和描述 MQ消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之一.何时需要消息队列?当你需要使用 ...

随机推荐

  1. docker中run和start的区别?

    docker run 后面指定的是一个镜像 而docker start指定的是一个容器 docker run是利用镜像生成容器,并启动容器,而docker start是启动一个之前生成过的容器

  2. AppiumForWin安装

    尝试安装Windows版本的Appium 参考:http://www.cnblogs.com/fnng/p/4540731.html 第一步:安装node https://nodejs.org/en/ ...

  3. Solution -「OurOJ 46544」漏斗计算

    \(\mathcal{Description}\)   Link.   定义一个运算结点 \(u\) 有两个属性:当前容量 \(x_u\).最大容量 \(V_u\).提供以下单元操作: I 读入一个整 ...

  4. 排查log4j不输出日志到文件的问题

    问题描述 项目使用Spring Boot框架,在pom文件中添加了如下配置: <dependency> <groupId>org.slf4j</groupId> & ...

  5. SonarQube之采购选型参考

    SonarQube是DevOps实践中主流的一款质量内建工具,过插件机制,Sonar 可以集成不同的测试工具,代码分析工具,以及持续集成工具,比如pmd-cpd.checkstyle.findbugs ...

  6. Spring中publish如何将多个Event和多个Listener进行无误差匹配

    从命令模式的维度理解Spring 之Application Event - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中通自定义简单的Event和Listener阐述了Spring中pu ...

  7. APK修改神器:插桩工具 DexInjector

    本文介绍了一个针对Dex进行插桩的工具,讲解了一下直接修改Dalvik字节码和Dex文件时遇到的问题和解决方法 作者:字节跳动终端技术-- 李言 背景 线下场景中,我们经常需要在APK中插入一些检测代 ...

  8. python中类的调用

    1 class Computer: # 创建类,类名首字母大写 2 screen = True # 类的属性 3 4 def start(self): # 创建实例方法,不要漏了 self 5 pri ...

  9. Django模板的继承与模板的导入

    目录 一:模版的继承 1.什么是模板继承? 2.使用继承流程原理 3.模板继承语法 二:模板的继承使用 1.案例需求 2.总结模板继承 三:模版的导入 1.模板导入 2.模板导入格式 3.模板导入使用 ...

  10. windbg调试命令

    重要 (1) windbg命令分为标准命令(40个左右),元命令(一百多个)和扩展命令. 标准命令提供最基本的调试功能,不区分大小写.如:bp g dt dv k等 元命令提供标准命令没有提供的功能, ...