什么是消息队列?
消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,消费者只负责从消息队列中取出数据处理。
什么是 RabbitMQ ?
RabbitMQ 是一个消息代理:它接受并转发消息。
您可以将其视为邮局:当您将要投递的邮件放入邮箱时,您可以确定邮递员最终会将邮件递送给收件人。在这个类比中,RabbitMQ 是一个邮箱、一个邮局和一个信递员。
为什么要使用 RabbitMQ ?
RabbitMQ 的组成
- 消息生产者连接到RabbitMQ Broker,创建connection,开启channel。
- Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列。
- Queue:消息队列,存储消息的队列。
- Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。
- Consumer:消息消费者。消费队列中存储的消息。
客户端 (Client)
Client是一个Dart AMQP库中的类,用于连接到AMQP(高级消息队列协议)服务器。在这个代码中,它被用于连接到消息队列MQ服务器。
part of dart_amqp.client;
abstract class Client {
factory Client({ConnectionSettings? settings}) =>
_ClientImpl(settings: settings);
// Configuration options
ConnectionSettings get settings;
ConnectionSettings({
this.host = "127.0.0.1",
this.port = 5672,
this.virtualHost = "/",
this.authProvider = const PlainAuthenticator("guest", "guest"),
this.maxConnectionAttempts = 1,
this.reconnectWaitTime = const Duration(milliseconds: 1500),
TuningSettings? tuningSettings,
this.tlsContext,
this.onBadCertificate,
}) : tuningSettings = tuningSettings ?? TuningSettings();
- host: AMQP服务器的主机名或IP地址,默认为"127.0.0.1"。
- port: AMQP服务器的端口号,默认为5672。
- virtualHost: 虚拟主机名称,默认为"/"。
- authProvider: 用于身份验证的AuthProvider对象,默认为PlainAuthenticator("guest", "guest"),即使用用户名"guest"和密码"guest"进行身份验证。
- maxConnectionAttempts: 最大连接尝试次数,默认为1。
- reconnectWaitTime: 重新连接等待时间,默认为1500毫秒。
- tuningSettings: AMQP协议参数调整设置对象,默认为TuningSettings()。
- tlsContext: 用于TLS/SSL连接的SecurityContext对象,默认为null。
- onBadCertificate: 当连接到使用自签名证书的AMQP服务器时,可以使用该函数处理证书错误。默认为null。
通道 (Channel)
在Dart AMQP库中,Channel类用于在AMQP服务器上打开通道。通过使用通道,可以在不创建多个TCP连接的情况下执行多个操作。这样可以减少网络开销并提高性能。
/// Allocate and initialize a new [Channel]. Return a [Future] to be completed with
/// the new [Channel]
///分配并初始化一个新的[Channel]。返回一个[Future],以新的[Channel]完成
Future<Channel> channel();
import 'package:dart_amqp/dart_amqp.dart';
void main() async {
Client client = Client(settings: ConnectionSettings(host: "localhost"));
// Setup a channel with a direct exchange and queue.
Channel channel = await client.channel();
Exchange exchange = await channel.exchange("my_exchange", ExchangeType.DIRECT);
Queue queue = await channel.queue("my_queue");
await exchange.bind(queue, "my_routing_key");
// Publish a message.
exchange.publish("Hello, RabbitMQ!", "my_routing_key");
// Consume the message.
Consumer consumer = await queue.consume();
consumer.listen((AmqpMessage message) {
print("Received: ${message.payloadAsString}");
client.close();
});
}
交换机 (Exchange)
Future<Exchange> exchange(String name, ExchangeType type,
{bool passive = false,
bool durable = false,
bool noWait = false,
Map<String, Object> arguments});
- name: 交换机名称。
- type: 交换机类型,可以是ExchangeType.DIRECT、ExchangeType.FANOUT、ExchangeType.TOPIC或ExchangeType.HEADERS。
- passive: 如果为true,则不会创建交换机。如果交换机不存在,则会抛出异常。默认为false。
- durable: 如果为true,则声明的交换机将是持久的。默认为false。
- noWait: 如果为true,则不会等待服务器确认交换机声明。默认为false。
- arguments: 交换机的其他参数。默认为null。
该方法返回一个Future<Exchange>对象,表示声明的交换机对象。
exchange.bindPrivateQueueConsumer()
Future<Consumer> bindPrivateQueueConsumer(List<String>? routingKeys,
{String consumerTag, bool noAck = true});
用于创建一个私有队列,并将消费者绑定到该队列上。参数的含义如下:
- routingKeys: 路由键列表。如果指定了路由键,则只有与这些路由键匹配的消息才会被消费者接收。如果为null,则消费者将接收队列中的所有消息。
- consumerTag: 消费者标签。默认为null,表示由服务器自动生成标签。
- noAck: 如果为true,则消费者将自动确认收到的消息。如果为false,则需要手动确认。默认为true。
该方法返回一个Future<Consumer>对象,表示创建的消费者。
消费者 (Consumer)
part of dart_amqp.client;
abstract class Consumer {
/// Get the consumer tag.
String get tag;
/// Get the [Channel] where this consumer was declared.
Channel get channel;
/// Get the [Queue] where this consumer is bound.
Queue get queue;
/// Bind [onData] listener to the stream of [AmqpMessage] that is emitted by the consumer.
///
/// You can also define an optional [onError] method that will handle stream errors and an
/// [onDone] method to be invoked when the stream closes.
StreamSubscription<AmqpMessage> listen(
void Function(AmqpMessage event) onData,
{Function onError,
void Function() onDone,
bool cancelOnError});
/// Cancel the consumer and return a [Future<Consumer>] to the cancelled consumer.
Future<Consumer> cancel({bool noWait = false});
}
这是Dart AMQP库中Consumer抽象类,用于表示AMQP消费者。消费者是一个对象,它从队列中获取消息并将其传递给应用程序。它提供了以下方法和属性:
- tag: 获取消费者标签。
- channel: 获取声明此消费者的通道。
- queue: 获取此消费者绑定的队列。
- listen: 将onData监听器绑定到由消费者发出的AmqpMessage流。您还可以定义一个可选的onError方法来处理流错误,并定义一个onDone方法,在流关闭时调用。
- cancel: 取消消费者并返回一个Future<Consumer>对象,表示取消的消费者。如果noWait为true,则不会等待服务器确认取消操作。
RabbitMQ官方文档并没有提供Dart语言的版本。我们可以使用dart_amqp这个库来在Dart中使用RabbitMQ。这个库实现了AMQP协议,RabbitMQ也使用了的协议。
路由键(Routing Key)
这是一个字符串,用于标识消息的路由规则。当消息发送到交换机时,交换机会根据消息的路由键将消息路由到一个或多个绑定了该路由键的队列中。路由键和队列之间是一种多对多的关系,即一个路由键可以绑定到多个队列上,一个队列也可以绑定多个路由键。
路由键、交换机、队列的关系
交换机与队列的关系是多对多的,当生产者发送消息到交换机时,交换机会根据该消息的路由键将消息路由到同样绑定了该路由键的队列上。
在flutter中使用 RabbitMQ 的步骤
添加依赖
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get
):
dependencies:
dart_amqp: ^0.2.5
个人理解在flutter上流程:
-
连接到MQ服务器,打开通道Chaneel
//连接到RabbitMQ服务器
ConnectionSettings settings = new ConnectionSettings(
host: ServicesConfig.mqIp,
port: ServicesConfig.mqPort,
authProvider: new PlainAuthenticator(ServicesConfig.mqUser, ServicesConfig.mqPassword));
Client client = new Client(settings: settings);
Channel channel = await client.channel();
-
调用chaneel.exchange创建交换机Exchange
Exchange exchange = await channel.exchange(exchangeName, ExchangeType.TOPIC, durable: true);
创建成功后调用exchange.bindPrivateQueueConsumer()方法,创建一个绑定了相应路由键的私有队列,并将消费者绑定在这个队列上,随后消费者就可以监听消息
Consumer consumer = await exchange.bindPrivateQueueConsumer(_routingKeys);
consumer.listen((AmqpMessage message) {}
-
为队列绑定路由键
//使用 [routingKey] 将此队列绑定到 [exchange],并向绑定队列返回 [Future<Queue>]。 [routingKey] 参数不能为空或 null,除非 [exchange] 的类型为 [ExchangeType.FANOUT] 或 [ExchangeType .标题]。
对于任何其他 [exchange] 类型,传递空或 null [routingKey] 将导致 [ArgumentError]
被扔掉。
/// Bind this queue to [exchange] using [routingKey] and return a [Future<Queue>] to the bound queue.
///
/// The [routingKey] parameter cannot be empty or null unless [exchange] is of type [ExchangeType.FANOUT] or [ExchangeType.HEADERS].
/// For any other [exchange] type, passing an empty or null [routingKey] will cause an [ArgumentError]
/// to be thrown.
Future<Queue> bind(Exchange exchange, String routingKey,
{bool noWait, Map<String, Object> arguments});
queue.bind(exchange!, routingKey);
-
队列解绑路由键
/// Unbind this queue from [exchange] with [routingKey] and return a [Future<Queue>] to the unbound queue.
///
/// The [routingKey] parameter cannot be empty or null unless [exchange] is of type [ExchangeType.FANOUT] or [ExchangeType.HEADERS].
/// For any other [exchange] type, passing an empty or null [routingKey] will cause an [ArgumentError]
/// to be thrown.
Future<Queue> unbind(Exchange exchange, String routingKey,
{bool noWait, Map<String, Object> arguments});
queue.unbind(exchange!, routingKey);