介绍
RabbitMQ
是一个分布式的消息服务中间件: 它接收并转发消息。你可以将它理解成是一个邮局: 当信件放入邮箱,信件会先到邮局,再由邮局派邮递员发出去,,将信件送到你想送的人手里。RabbitMQ
就是这个邮局,邮箱,邮递员。
和邮局不同的是,RabbitMQ
并不会处理邮件。RabbitMQ
接收和存储的是信息的二进制数据。
基本概念
生产者: 发送消息的称之为消息的生产者。
图:
队列: 在 RabbitMQ
中,邮箱的名字称之为队列。尽管消息会在 RabbitMQ
和你的应用之间传递,但在整个传递的过程中,消息只存储在队列中。队列是一个大型的消息缓冲区,它只收到主机内存和磁盘大小的限制。消息生产者们可以将消息发送到同一条队列中,而消息的消费者则可以从同一条队列中获取消息
图:
消费者: 在 RabbitMQ
中,等待接收消息的被称为消费者。
图:
Note: 消费者、生成者和
RabbitMQ
不需要部署在同一台主机上,大部分情况也都是分开部署的。另外:一个应用可以即是生产者,也可以是消费者。
"Hello World"
demo 使用 Golang,Objective-C 和 Swift 可以看这里, 更多版本请看 官网
这一小节,会使用 2 个程序进行扮演消息的生成者和消费者。生产者发送一条消息,消费者接收并打印消息。关于更多 API。
下图中, "P" 表示生产者,"C" 表示消费者,而中间的箱子表示队列 - RabbitMQ
中消费者保留的消息缓冲区(产生了消息不一定要马上被消费)
Golang RabbitMQ Client
RabbitMQ
使用多种协议,本教程使用 AMQP 0-9-1
协议,开源且通用的消息传递协议。基本上每个语言都有其对应的 RabbtiMQ Client 。本教程使用的 Go amqp client
。
安装 ampq
go get github.com/rabbitmq/amqp091-go
Sending
消息产生者 send.go
和消费者 receive.go
, 产生者连接 RabbitMQ
并发送一个简单的文本消息。
在 send.go
,需要先导入第三方库
package main
import (
"context"
"log"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
创建一个检查 amqp 调用错误的函数
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s:%s", msg, err);
}
}
连接 RabbitMQ
服务
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbtitMQ")
defer conn.Close()
conn
是对于套接字(封装了 ip 和 port 的结构体)的抽象,并为我们处理协议版本和身份验证。接着创建 channel
,RabbitMQ
中大部分功能都基于此。
ch, err := conn.Channel()
failOnError(err, "Fail to open a channel")
defer ch.Close()
发送消息依赖队列,先创建一个队列
q, err := ch.QueueDeclare(
"hello", // name
false, // durable
false, // delete when unused
false, // exclusize
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
body := "Hello World!"
err = cn.PublishWithContext(
ctx,
"", // exchange
q.Name, // rounting key
false, // mandatory
false, // immediate
amqp.Publish {
ContentType: "text/plain",
Body: []byte(Body),
},
)
failOnError(err, "failed to publish a message")
log.Printf("[x] Sent %s\n", body)
声明队列是幂等(幂等: 调用一次或多次,产生的结果是一致的)的,不存在时才会创建。传递的消息是一个字节数组,所以可以传递任何数据
无法发送数据
如果你是第一次使用
RabbitMQ
,你可能会想为什么没有看到发送的消息。RabbitMQ
至少需要 200M 的空间,否则将无法接收数据。如果空间实在不够,可以通过修改disk_free_limit
中的配置来修改其限制 - 配置文件参考
Receiving
消费者会一直监听 RabbitMQ
中的新消息,接收并将其打印。
和 send.go
中一样,先添加 import 和 help 函数
package main
import (
"log"
amqp "github.com/rabbitmq/amqp091-go"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
准备工作和 send.go
中一致,需要注意的是队列名称要和 send.go
中,保持一致。
// 1. 连接 RabbitMQ
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect RabbitMQ")
defer conn.Close()
// 2. 创建 channel
cn, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer cn.Close()
// 3. 声明队列
q, err := cn.QueueDeclare(
"hello",
false,
false,
false,
false,
nil)
failOnError(err, "Failed to declare a queue")
需要注意的是, 在 receive.go
中也声明了队列,因为在实际场景中,我们可能先执行消费者的相关代码,所以我们要确保队列是存在(因为创建队列是幂等的,不会有实际的影响)
RabbitMQ
会异步发送消息,我们通过协程的 channel
读取接收到的消息。
Putting it all together
启动消息生产者
go run send.go
启动消息消费者
go run reveive.go
消费者会打印从 RabbitMQ
中接收到的消息,我们阻塞了消费者程序,所以它不会退出,而是一直接收 RabbitMQ
中的消息,可以尝试在另一个终端中启动另一个消息生产者