RabbitMQ指南(二) 基本概念和开发
2.1 基本概念
下图是RabbitMQ的基本模型,模型中包括以下部分:生产者、交换机、队列和消费者。
生产者产生消息,并将消息发送至交换机,交换机根据一定的路由规则将消息发送至一个或多个消息队列中,消息的消费者从相应的消息队列中取数据,进行处理。
其中,交换机和队列位于RabbitMQ服务端,生产者和消费者属于RabbitMQ的客户端。
RabbitMQ的客户端建立与服务端的Socket长连接(Connection),并在其上建立轻量级的连接——信道(Channel),大部分的业务操作是在信道中进行的。有文章有形象的比喻:若连接是一根光缆,则信道就是光缆中的光纤。
RabbitMQ安装部署完毕,会已经创建好一些交换机,进入Web管理界面->Exchanges页签,可看到下图这些默认创建的交换机:
其中,默认交换机“(AMQP default)”最为特殊,它的名字是一个空字符串(””),不能被删除。所有创建的队列都会与之连接(称为“绑定”),且不能解绑。绑定使用的路由键(Routing Key)即为队列的名称。
本节的基本内容,即创建一个消息的发送方(生产者)、接收方(消费者)和与默认交换机绑定的队列,发送方通过默认交换机向该队列中发送一条消息,接收方从该队列中取出消息。
RabbitMQ支持多种编程语言的客户端,本文主要使用Java、C#和Python。在开发之前,需要做一定的准备工作。
2.2 Java开发
2.2.1 准备工作
新建一个Maven项目,对于Java程序,需引入RabbitMQ客户端所需的jar包。包括RabbitMQ客户端的jar包(本文版本5.4.3),以及日志所需的jar包(缺少该包会报错):
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
2.2.2 发送方
消息发送方的代码如下。发送方创建一个名为“Queue_Java”的队列,并向其中发送消息“This is Java’s message”:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Sender {
// RabbitMQ服务端地址
private static final String ADDRESS = "10.176.65.172";
// RabbitMQ默认监听端口为5672
private static final int PORT = 5672;
// 使用第一章创建的用户mqtester进行登录
private static final String USERNAME = "mqtester";
private static final String PASSWORD = "mqtester";
// 默认交换机的名字为空字符串
private static final String DEFAULT_EXCHANGE = "";
// 所要创建的队列名称
private static final String QUEUE_NAME = "Queue_Java";
public static void main(String[] args) throws Exception {
// 1.创建工厂类,设置服务端的地址、端口、用户名和密码
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(ADDRESS);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
// 2.建立连接
Connection connection = factory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.声明队列,该操作是幂等的,如果队列不存在则创建队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 5.发送消息
byte[] message = "This is Java's message".getBytes();
channel.basicPublish(DEFAULT_EXCHANGE, QUEUE_NAME, null, message);
// 6.关闭连接
channel.close();
connection.close();
}
}
为简洁起见,程序直接在主方法上抛出异常,实际使用时应当以try…catch…块处理异常。
从代码中可以看到,发送消息最后调用的是Channel类的basicPublish方法,该方法第一个参数为交换机的名称,第二个参数为路由键,第四个参数为所要发送的消息(以字节数组形式)。前文提到,默认交换机与队列绑定的路由键即为队列名称,故程序中以队列名称作为路由键入参,即可将消息发送至声明的队列中。
运行该段代码后,进入RabbitMQ的Web管理页面->Queues页签,可看到出现了创建的队列“Queue_Java”,并且该队列中有1条消息:
点击该队列名称,进入该队列的管理界面,在“Get Messages”下拉框中,点击获取消息“Get Message(s)”按键,可以看到这条消息就是程序发送的消息“This is Java’s message”:
2.2.3 接收方
接收方建立连接、信道、声明队列的过程与发送方相同。RabbitMQ服务端的队列收到消息后,异步将消息推送给消费者,故消费者通过回调方法处理接收到的消息。接收方代码如下:
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
public class Reciver {
private static final String ADDRESS = "10.176.65.172";
private static final int PORT = 5672;
private static final String QUEUE_NAME = "Queue_Java";
private static final String USERNAME = "mqtester";
private static final String PASSWORD = "mqtester";
public static void main(String[] args) throws Exception {
// 1.创建工厂类,设置服务端地址、端口、用户名和密码
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(ADDRESS);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
// 2.建立连接
Connection connection = factory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.声明队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 5.创建消费者,从队列中消费消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body);
System.out.println("Received: " + message);
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
在运行basicConsume()方法后,消费者开始监听队列,队列中有消息时,将回调消费者重写的handleDelivery()方法。运行该程序,可以看到控制台打印出生产者发送的“This is Java’s message”消息。再次查看Web管理端的队列,可看到队列Queue_Java中的消息数变为0了,之前发送的消息已经被消费:
2.3 C#开发
2.3.1 准备工作
下载RabbitMQ客户端C#版本,下载地址:
http://www.rabbitmq.com/releases/rabbitmq-dotnet-client
版本:rabbitmq-dotnet-client-3.6.14-dotnet-4.5.zip
压缩包中bin/RabbitMQ.Client.dll是C#开发需要的动态链接库文件。在Visual Studio中新建C#控制台程序,在项目的引用中添加RabbitMQ.Client.dll。
2.3.2 发送方
C#版本的发送方代码与Java流程相同,由于C#有using关键字,省去了关闭连接的代码。如下:
using RabbitMQ.Client;
using System;
using System.Text;
namespace RabbitMQDemoCSharp
{
public class Sender
{
// RabbitMQ服务端地址
private const string ADDRESS = "10.176.65.227";
// RabbitMQ默认监听端口为5672
private const int PORT = 5672;
// 使用第一章创建的用户mqtester进行登录
private const string USERNAME = "mqtester";
private const string PASSWORD = "mqtester";
// 默认交换机的名字为空字符串
private const string DEFAULT_EXCHANGE = "";
// 所要创建的队列名称
private const string QUEUE_NAME = "Queue_C#";
public static void Main(string[] args)
{
// 1.创建工厂类,设置服务端地址、端口、用户名和密码
var factory = new ConnectionFactory()
{
HostName = ADDRESS,
Port = PORT,
UserName = USERNAME,
Password = PASSWORD
};
// 2.建立连接
using (var connection = factory.CreateConnection())
{
// 3.建立信道
using (var channel = connection.CreateModel())
{
// 4.声明队列
channel.QueueDeclare(queue: QUEUE_NAME,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
// 5.发送消息
byte[] message = Encoding.UTF8.GetBytes("This is C#'s message");
channel.BasicPublish(exchange: DEFAULT_EXCHANGE,
routingKey: QUEUE_NAME,
basicProperties: null,
body: message);
}
Console.ReadLine();
}
}
}
}
2.3.3 接收方
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
namespace RabbitMQDemoCSharp
{
public class Receiver
{
private const string ADDRESS = "10.176.65.182";
private const int PORT = 5672;
private const string USERNAME = "mqtester";
private const string PASSWORD = "mqtester";
private const string QUEUE_NAME = "Queue_C#";
public static void Main(string[] args)
{
// 1.创建工厂类,设置服务端地址、端口、用户名和密码
var factory = new ConnectionFactory()
{
HostName = ADDRESS,
Port = PORT,
UserName = USERNAME,
Password = PASSWORD
};
// 2.建立连接
using (var connection = factory.CreateConnection())
{
// 3.建立信道
using (var channel = connection.CreateModel())
{
// 4.声明队列
channel.QueueDeclare(queue: QUEUE_NAME,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
// 5.创建消费者,接收消息
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var message = Encoding.UTF8.GetString(ea.Body);
Console.WriteLine("Received: " + message);
};
channel.BasicConsume(queue: QUEUE_NAME,
noAck: true,
consumer: consumer);
Console.ReadLine();
}
}
}
}
}
2.4 Python开发
2.4.1 准备工作
使用pip命令安装pika包:
pip install pika
2.4.2 发送方
import pika
# RabbitMQ服务端地址
ADDRESS = '10.176.65.172'
# RabbitMQ默认监听端口为5672
PORT = 5672
# 使用第一章创建的用户mqtester进行登录
USERNAME = 'mqtester'
PASSWORD = 'mqtester'
# 默认交换机的名字为空字符串
DEFAULT_EXCHANGE = ''
# 所要创建的队列名称
QUEUE_NAME = 'Queue_Python'
# 1.设置服务端地址、端口、用户名和密码
credentials = pika.PlainCredentials(username=USERNAME, password=PASSWORD)
# 2.建立连接
connection = pika.BlockingConnection(parameters=pika.ConnectionParameters(host=ADDRESS, port=PORT, credentials=credentials))
# 3.建立信道
channel = connection.channel()
# 4.声明队列
channel.queue_declare(queue=QUEUE_NAME, durable=False, exclusive=False, auto_delete=False, arguments=None)
# 5.发送消息
channel.basic_publish(exchange=DEFAULT_EXCHANGE, routing_key=QUEUE_NAME, body="This is Python's message")
# 6.关闭连接
channel.close()
connection.close()
2.4.3 接收方
import pika
# RabbitMQ服务端地址
ADDRESS = '10.176.65.172'
# RabbitMQ默认监听端口为5672
PORT = 5672
# 使用第一章创建的用户mqtester进行登录
USERNAME = 'mqtester'
PASSWORD = 'mqtester'
# 默认交换机的名字为空字符串
DEFAULT_EXCHANGE = ''
# 所要创建的队列名称
QUEUE_NAME = 'Queue_Python'
# 1.设置服务端地址、端口、用户名和密码
credentials = pika.PlainCredentials(username=USERNAME, password=PASSWORD)
# 2.建立连接
connection = pika.BlockingConnection(parameters=pika.ConnectionParameters(host=ADDRESS, port=PORT, credentials=credentials))
# 3.建立信道
channel = connection.channel()
# 4.声明队列
channel.queue_declare(queue=QUEUE_NAME, durable=False, exclusive=False, auto_delete=False, arguments=None)
# 5.定义回调方法,消费消息
def callback(ch, method, properties, body):
print('Received: ' + body.decode('utf8'))
channel.basic_consume(consumer_callback=callback, queue=QUEUE_NAME, no_ack=True)
channel.start_consuming()