RabbitMQ消息队列:"Hello, World!"

by LauCyun Sep 24,2017 22:19:34 11,872 views

RabbitMQ是一个消息代理。它的工作就是接收和转发消息。你可以把它想像成一个邮局:你把信件放入邮箱,邮递员就会把信件投递到你的收件人处。在这个比喻中,RabbitMQ就扮演着邮箱、邮局以及邮递员的角色。

RabbitMQ和邮局的主要区别在于,它处理纸张,而是接收、存储和发送消息(message)这种二进制数据。

下面是RabbitMQ和消息所涉及到的一些术语。

  • 生产(Producing)的意思就是发送。发送消息的程序就是一个生产者(producer)。我们一般用"P"来表示:

  • 队列(queue)就是存在于RabbitMQ中邮箱的名称。虽然消息的传输经过了RabbitMQ和你的应用程序,但是它只能被存储于队列当中。实质上队列就是个巨大的消息缓冲区,它的大小只受主机内存和硬盘限制。多个生产者(producers)可以把消息发送给同一个队列,同样,多个消费者(consumers)也能够从同一个队列(queue)中获取数据。队列可以绘制成这样(图上是队列的名称):

  • 在这里,消费(Consuming)和接收(receiving)是同一个意思。一个消费者(consumer)就是一个等待获取消息的程序。我们把它绘制为"C":

接下来我们用Python写两个小程序。一个发送单条消息的生产者(producer)和一个接收消息并将其输出的消费者(consumer)。传递的消息是Hello World

1 前言

下图中,“P”代表生产者,“C”代表消费者,中间的盒子代表为消费者保留的消息缓冲区,也就是我们的队列。

生产者(producer)把消息发送到一个名为hello的队列中。消费者(consumer)从这个队列中获取消息。

RabbitMQ库

RabbitMQ使用的是AMQP 0.9.1协议。这是一个用于消息传递的开放、通用的协议。针对不同编程语言有大量的RabbitMQ客户端可用。在这个系列教程中,RabbitMQ团队推荐使用Pika这个Python客户端。大家可以通过pip这个包管理工具进行安装:

pip install pika

2 生产者

我们第一个程序producing.py会发送一个消息到队列中。首先要做的事情就是建立一个到RabbitMQ服务器的连接。

credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(
    pika.ConnectionParameters(
        host="172.17.0.201",
        port=5672,
        credentials=credentials
    )
)
channel = connection.channel()

现在我们已经跟本地机器的代理建立了连接。

接下来,在发送消息之前,我们需要确认服务于消费者的队列已经存在。如果将消息发送给一个不存在的队列,RabbitMQ会将消息丢弃掉。下面我们创建一个名为hello的队列用来将消息投递进去。

channel.queue_declare(queue="hello", durable=True)  # 队列持久化

这时候我们就可以发送消息了,我们第一条消息只包含了Hello World!字符串,我们打算把它发送到hello队列。

在RabbitMQ中,消息是不能直接发送到队列中的,这个过程需要通过交换机(exchange)来进行。但是为了不让细节拖累我们的进度,这里我们只需要知道如何使用由空字符串表示的默认交换机即可。默认交换机比较特别,它允许我们指定消息究竟需要投递到哪个具体的队列中,队列名字需要在routing_key参数中指定。

channel.basic_publish(
    exchange='',
    routing_key="hello",
    body="hello world!"
    properties=pika.BasicProperties(delivery_mode=2, )
)

在退出程序之前,我们需要确认网络缓冲已经被刷写、消息已经投递到RabbitMQ。通过安全关闭连接可以做到这一点。

print("Sent 'Hello World!'")
connection.close()

发送不成功!

如果这是你第一次使用RabbitMQ,并且没有看到“Sent”消息出现在屏幕上,你可能会抓耳挠腮不知所以。这也许是因为没有足够的磁盘空间给代理使用所造成的(代理默认需要200MB的空闲空间),所以它才会拒绝接收消息。查看一下代理的日志文件进行确认,如果需要的话也可以减少限制。配置文件文档会告诉你如何更改磁盘空间限制(disk_free_limit)。

3 消费者

我们的第二个程序consuming.py,将会从队列中获取消息并将其打印到屏幕上。

这次我们还是需要要先连接到RabbitMQ服务器。连接服务器的代码和之前是一样的。

下一步也和之前一样,我们需要确认队列是存在的。我们可以多次使用queue_declare命令来创建同一个队列,但是只有一个队列会被真正的创建。

channel.queue_declare(queue='hello')

你也许要问: 为什么要重复声明队列呢 —— 我们已经在前面的代码中声明过它了。如果我们确定了队列是已经存在的,那么我们可以不这么做,比如此前预先运行了send.py程序。可是我们并不确定哪个程序会首先运行。这种情况下,在程序中重复将队列重复声明一下是种值得推荐的做法。

列出所有队列

你也许希望查看RabbitMQ中有哪些队列、有多少消息在队列中。此时你可以使用rabbitmqctl工具(使用有权限的用户):

sudo rabbitmqctl list_queues

(在Windows中不需要sudo命令)

rabbitmqctl list_queues

从队列中获取消息相对来说稍显复杂。需要为队列定义一个回调(callback)函数。当我们获取到消息的时候,Pika库就会调用此回调函数。这个回调函数会将接收到的消息内容输出到屏幕上。

def callback(ch, method, properties, body):
    print("Received %r" % body)

下一步,我们需要告诉RabbitMQ这个回调函数将会从名为"hello"的队列中接收消息:

channel.basic_consume(callback,
                      queue='hello',
                      no_ack=True)

要成功运行这些命令,我们必须保证队列是存在的,我们的确可以确保它的存在——因为我们之前已经使用queue_declare将其声明过了。

no_ack参数稍后会进行介绍。

最后,我们运行一个用来等待消息数据并且在需要的时候运行回调函数的无限循环。

print('Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

4 测试

producing.py的完整代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pika

credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(
    pika.ConnectionParameters(
        host="172.17.0.201",
        port=5672,
        credentials=credentials
    )
)
channel = connection.channel()

channel.queue_declare(queue="hello", durable=True)  # 队列持久化

channel.basic_publish(
    exchange='',
    routing_key="hello",
    body="hello world!"
    properties=pika.BasicProperties(delivery_mode=2, )
)

print("Sent 'Hello World!'")
connection.close()

consuming.py的完整代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pika

credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(
    pika.ConnectionParameters(
        host="172.17.0.201",
        port=5672,
        credentials=credentials
    )
)
channel = connection.channel()

channel.queue_declare(queue="hello", durable=True)  # 队列持久化

def callback(ch, method, properties, body):
    print("Received %r" % body)

channel.basic_consume(
    callback,
    queue="hello",
    no_ack=True
)

print('Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

现在我们可以在终端中尝试一下我们的程序了。

首先我们启动生产者,生产者程序每次执行后都会停止运行。

$ python producing.py
Sent 'Hello World!'

启动一个消费者,它会持续的运行来等待投递到达。

$ python consuming.py
Waiting for messages. To exit press CTRL+C
Received 'Hello World!'

成功了!我们已经通过RabbitMQ发送第一条消息。你也许已经注意到了,consuming.py程序并没有退出。它一直在准备获取消息,你可以通过Ctrl-C来中止它。

试下在新的终端中再次运行producing.py

我们已经学会如何发送消息到一个已知队列中并接收消息。

5 参考

Tags