wlogic / rabbitmq-bundle
集成 php-amqplib 与 Symfony 及 RabbitMq。曾是 oldsound/rabbitmq-bundle。
Requires
- php: ^5.3.9|^7.0|^8.0
- php-amqplib/php-amqplib: ^3.0
- psr/log: ^1.0
- symfony/config: ^2.7|^3.0|^4.0|^5.0|^6.0
- symfony/console: ^2.7|^3.0|^4.0|^5.0|^6.0
- symfony/dependency-injection: ^2.7|^3.0|^4.0|^5.0|^6.0
- symfony/event-dispatcher: ^2.7|^3.0|^4.0|^5.0|^6.0
- symfony/yaml: ^2.7|^3.0|^4.0|^5.0|^6.0
Requires (Dev)
- phpunit/phpunit: ^4.8.35|^5.4.3
- symfony/debug: ^2.7|^3.0|^4.0|^5.0|^6.0
- symfony/serializer: ^2.7|^3.0|^4.0|^5.0|^6.0
Suggests
- symfony/framework-bundle: To use this lib as a full Symfony Bundle and to use the profiler data collector
Replaces
- dev-master
- 4.0
- 3.3
- 3.2
- 3.1
- 3.0
- v2.2.17
- v2.2.16
- v2.2.15
- v2.2.14
- v2.2.13
- v2.2.12
- v2.2.11
- v2.2.10
- v2.2.9
- v2.2.8
- v2.2.7
- v2.2.6
- v2.2.5
- v2.2.4
- v2.2.3
- v2.2.2
- v2.2.1
- v2.2
- v2.1
- v2.0
- 1.x-dev
- v1.14.4
- v1.14.3
- v1.14.2
- v1.14.1
- v1.14.0
- v1.13.0
- v1.12.0
- v1.11.2
- v1.11.1
- v1.11.0
- v1.10.0
- v1.9.0
- v1.8.0
- v1.7.0
- v1.6.0
- v1.5.0
- v1.4.1
- v1.4.0
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
- dev-timeout-patch-1
- dev-chore/code-update-for-php-80
- dev-compatibility-with-symfony-5.4
- dev-feature/fr-5
- dev-feature/fr-3
- dev-feature/fr-2
- dev-fix-compatibility-with-symfony-4.3
- dev-feature/travis
- dev-purge-fabric
This package is auto-updated.
Last update: 2024-09-02 17:22:46 UTC
README
关于
RabbitMqBundle 通过 RabbitMQ 和 php-amqplib 库将消息集成到您的应用程序中。
该组件实现了 Thumper 库中看到的一些消息模式。因此,从 Symfony 控制器向 RabbitMQ 发布消息就像
$msg = array('user_id' => 1235, 'image_path' => '/path/to/new/pic.png'); $this->get('old_sound_rabbit_mq.upload_picture_producer')->publish(serialize($msg));
稍后当您想从 upload_pictures
队列中消费 50 条消息时,只需在 CLI 上运行
$ ./app/console rabbitmq:consumer -m 50 upload_picture
所有示例都假设有一个正在运行的 RabbitMQ 服务器。
此组件在 Symfony Live Paris 2011 大会上展示。请参阅幻灯片 此处。
安装
对于 Symfony 框架 >= 2.3
使用 composer 需求组件及其依赖项
$ composer require php-amqplib/rabbitmq-bundle
注册组件
// app/AppKernel.php public function registerBundles() { $bundles = array( new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(), ); }
享受!
对于使用 Symfony Console、依赖注入和配置组件的控制台应用程序
如果您有一个用于运行 RabbitMQ 消费者的控制台应用程序,则不需要 Symfony HttpKernel 和 FrameworkBundle。从版本 1.6 开始,您可以使用依赖注入组件来加载此组件配置和服务,然后使用消费者命令。
在您的 composer.json 文件中需求组件
{
"require": {
"php-amqplib/rabbitmq-bundle": "~1.6",
}
}
注册扩展和编译器传递
use OldSound\RabbitMqBundle\DependencyInjection\OldSoundRabbitMqExtension; use OldSound\RabbitMqBundle\DependencyInjection\Compiler\RegisterPartsPass; // ... $containerBuilder->registerExtension(new OldSoundRabbitMqExtension()); $containerBuilder->addCompilerPass(new RegisterPartsPass());
警告 - 兼容性破坏更改
-
自 2012-06-04 以来,某些在 "producers" 配置部分声明的交换的默认选项已更改,以匹配 "consumers" 部分中声明的交换的默认选项。受影响的设置是
durable
从false
更改为true
,auto_delete
从true
更改为false
。
如果您依赖于以前的默认值,则必须更新您的配置。
-
自 2012-04-24 以来,ConsumerInterface::execute 方法的签名已更改
-
自 2012-01-03 以来,消费者执行方法获取整个 AMQP 消息对象,而不仅仅是主体。有关更多详细信息,请参阅 CHANGELOG 文件。
用法
在您的配置文件中添加 old_sound_rabbit_mq
部分
old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false connection_timeout: 3 read_write_timeout: 3 # requires php-amqplib v2.4.1+ and PHP5.4+ keepalive: false # requires php-amqplib v2.4.1+ heartbeat: 0 #requires php_sockets.dll use_socket: true # default false another: # A different (unused) connection defined by an URL. One can omit all parts, # except the scheme (amqp:). If both segment in the URL and a key value (see above) # are given the value from the URL takes precedence. # See https://rabbitmq.cn/uri-spec.html on how to encode values. url: 'amqp://guest:password@localhost:5672/vhost?lazy=1&connection_timeout=6' producers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} service_alias: my_app_service # no alias by default consumers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} queue_options: {name: 'upload-picture'} callback: upload_picture_service
在这里,我们配置连接服务和我们的应用程序将拥有的消息端点。在此示例中,您的服务容器将包含服务 old_sound_rabbit_mq.upload_picture_producer
和 old_sound_rabbit_mq.upload_picture_consumer
。后者期望存在一个名为 upload_picture_service
的服务。
如果您未指定客户端的连接,则客户端将寻找具有相同别名的连接。因此,对于我们的 upload_picture
,服务容器将寻找一个名为 upload_picture
的连接。
如果您需要添加可选的队列参数,则队列选项可以像这样
queue_options: {name: 'upload-picture', arguments: {'x-ha-policy': ['S', 'all']}}
另一个具有 20 秒消息 TTL 的示例
queue_options: {name: 'upload-picture', arguments: {'x-message-ttl': ['I', 20000]}}
参数值必须是数据类型和值的列表。有效数据类型是
S
- 字符串I
- 整数D
- 小数T
- 时间戳F
- 表格A
- 数组
根据您的需求调整 arguments
。
如果您想将队列绑定到特定的路由键,则可以在生产者或消费者配置中声明它
queue_options: name: "upload-picture" routing_keys: - 'android.#.upload' - 'iphone.upload'
重要提示 - 懒连接
在Symfony环境中,每个请求都会完全初始化所有服务,从2.3版本开始,你可以声明一个服务为懒加载(懒加载服务)。这个包目前还不支持新的懒加载服务特性,但你可以将连接配置中的lazy: true
设置为避免在每次请求中不必要地连接到消息代理。出于性能考虑,强烈推荐使用懒加载连接,尽管默认情况下禁用了懒加载选项,以避免可能破坏已使用此包的应用程序。
导入注意事项 - 心跳
将read_write_timeout
设置为心跳的2倍,这样你的套接字就会保持开启。如果你不这样做,或者使用不同的倍数,消费者套接字可能会超时。
动态连接参数
有时你的连接信息可能需要是动态的。动态连接参数允许你通过服务程序化地提供或覆盖参数。
例如,在一个场景中,连接的vhost
参数取决于你的白标应用程序当前的用户,并且你不想(或不能)每次都更改其配置。
在connection_parameters_provider
下定义一个实现ConnectionParametersProviderInterface
的服务,并将其添加到适当的connections
配置中。
connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: 'foo' # to be dynamically overridden by `connection_parameters_provider` connection_parameters_provider: connection_parameters_provider_service
示例实现
class ConnectionParametersProviderService implements ConnectionParametersProvider { ... public function getConnectionParameters() { return array('vhost' => $this->getVhost()); } ... }
在这种情况下,vhost
参数将由getVhost()
的输出覆盖。
生产者、消费者,什么?
在消息应用程序中,向代理发送消息的过程称为生产者,而接收这些消息的过程称为消费者。在你的应用程序中,你将拥有几个这样的过程,你可以在配置中的相应条目下列出它们。
生产者
生产者将被用来向服务器发送消息。在AMQP模型中,消息被发送到一个交换机,这意味着在生产者的配置中,你必须指定连接选项以及交换机选项,这通常将是交换机的名称和类型。
现在假设你想要在后台处理图片上传。在你将图片移动到最终位置后,你将向服务器发布以下信息
public function indexAction($name) { $msg = array('user_id' => 1235, 'image_path' => '/path/to/new/pic.png'); $this->get('old_sound_rabbit_mq.upload_picture_producer')->publish(serialize($msg)); }
如你所见,如果你的配置中有一个名为upload_picture的生产者,那么在服务容器中你将有一个名为old_sound_rabbit_mq.upload_picture_producer的服务。
除了消息本身,OldSound\RabbitMqBundle\RabbitMq\Producer#publish()
方法还接受一个可选的routing key参数和一个可选的附加属性数组。附加属性数组允许你更改通过默认方式构建PhpAmqpLib\Message\AMQPMessage
对象时使用的属性。这样,例如,你可以更改应用程序头。
你可以使用setContentType
和setDeliveryMode
方法来分别设置消息的内容类型和消息的投递模式。内容类型的默认值是text/plain
,投递模式的默认值是2
。
$this->get('old_sound_rabbit_mq.upload_picture_producer')->setContentType('application/json');
如果你需要一个用于生产者的自定义类(它应该继承自OldSound\RabbitMqBundle\RabbitMq\Producer
),你可以使用class
选项
... producers: upload_picture: class: My\Custom\Producer connection: default exchange_options: {name: 'upload-picture', type: direct} ...
下一个拼图是要有一个消费者,它会从队列中取出消息并相应地处理它。
延迟发布
有时你需要在大量处理结束后发布消息,例如,如果你想要确保在发布消息之前所有数据库条目都已写入,那么你可以使用延迟发布方法。这需要你使用生产者的delayedPublish
方法并注入ProducerManager
服务来实际发布消息。
$this->get('old_sound_rabbit_mq.upload_picture_producer')->delayedPublish(serialize($msg));
然后实际发布消息
$this->get('old_sound_rabbit_mq.producer_manager')->publishDelayedMessages();
ProducerManager
服务是一个单例,可以在应用程序的任何地方使用它来发布待处理的消息
消费者
消费者将连接到服务器并启动一个 循环,等待处理传入的消息。这种消费者的指定 回调 将决定其行为。让我们回顾一下上面的消费者配置
consumers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} queue_options: {name: 'upload-picture'} callback: upload_picture_service
正如我们所看到的,回调 选项引用了一个 upload_picture_service。当消费者从服务器接收到消息时,它将执行这样的回调。如果您需要指定不同的回调以进行测试或调试,则可以在此处更改它。
除了回调之外,我们还指定了要使用的连接方式,就像我们使用 生产者 一样。其余选项是 exchange_options 和 queue_options。exchange_options 应该与 生产者 使用的一样。在 queue_options 中,我们将提供 队列名称。为什么?
正如我们所说的,AMQP中的消息是发布到 交换机 的。这并不意味着消息已经到达 队列。为了实现这一点,我们首先需要创建这样的 队列,然后将它绑定到 交换机 上。酷的地方在于,您可以绑定多个 队列 到一个 交换机,这样一条消息就可以到达多个目的地。这种方法的优点是实现了生产者和消费者的 解耦。生产者不关心有多少消费者会处理他的消息。它只需要消息到达服务器。这样我们就可以在不改变控制器代码的情况下,扩展每次上传图片时执行的操作。
现在,如何运行消费者?有一个命令可以执行,如下所示
$ ./app/console rabbitmq:consumer -m 50 upload_picture
这是什么意思?我们正在执行 upload_picture 消费者,告诉它只消费 50 条消息。每次消费者从服务器接收到消息时,它都会执行配置的回调,将 AMQP 消息作为 PhpAmqpLib\Message\AMQPMessage
类的实例传递。可以通过调用 $msg->body
来获取消息体。默认情况下,消费者将无限循环地处理消息(在某种定义的 无限 意义上)。
如果您想确保消费者在 Unix 信号上立即执行完成,可以使用带有标志 -w
的命令。
$ ./app/console rabbitmq:consumer -w upload_picture
然后消费者将立即执行完成。
要使用带有此标志的命令,您需要安装带有 PCNTL 扩展的 PHP。
如果您想设置消费者内存限制,可以使用标志 -l
来做到这一点。在下面的示例中,此标志添加了 256 MB 的内存限制。消费者将在达到 256MB 前停止 5 MB,以避免 PHP 允许的内存大小错误。
$ ./app/console rabbitmq:consumer -l 256
如果您想清除队列中等待的所有消息,可以执行此命令来清空该队列
$ ./app/console rabbitmq:purge --no-confirmation upload_picture
要删除消费者的队列,请使用此命令
$ ./app/console rabbitmq:delete --no-confirmation upload_picture
消费者事件
这可以在许多场景中非常有用。有 3 个 AMQPEvents
CONSUME 事件
class OnConsumeEvent extends AMQPEvent { const NAME = AMQPEvent::ON_CONSUME; /** * OnConsumeEvent constructor. * * @param Consumer $consumer */ public function __construct(Consumer $consumer) { $this->setConsumer($consumer); } }
假设您需要在新的应用程序部署时暂停/停止消费者。您可以监听 OnConsumeEvent (\OldSound\RabbitMqBundle\Event\OnConsumeEvent),并检查新的应用程序部署。
处理消息之前
class BeforeProcessingMessageEvent extends AMQPEvent { const NAME = AMQPEvent::BEFORE_PROCESSING_MESSAGE; /** * BeforeProcessingMessageEvent constructor. * * @param AMQPMessage $AMQPMessage */ public function __construct(Consumer $consumer, AMQPMessage $AMQPMessage) { $this->setConsumer($consumer); $this->setAMQPMessage($AMQPMessage); } }
在处理 AMQPMessage 之前引发的事件。
处理消息之后
class AfterProcessingMessageEvent extends AMQPEvent { const NAME = AMQPEvent::AFTER_PROCESSING_MESSAGE; /** * AfterProcessingMessageEvent constructor. * * @param AMQPMessage $AMQPMessage */ public function __construct(Consumer $consumer, AMQPMessage $AMQPMessage) { $this->setConsumer($consumer); $this->setAMQPMessage($AMQPMessage); } }
在处理 AMQPMessage 之后引发的事件。如果处理消息将引发异常,则不会引发事件。
空闲消息
<?php class OnIdleEvent extends AMQPEvent { const NAME = AMQPEvent::ON_IDLE; /** * OnIdleEvent constructor. * * @param AMQPMessage $AMQPMessage */ public function __construct(Consumer $consumer) { $this->setConsumer($consumer); $this->forceStop = true; } }
当wait
方法因超时而没有接收到消息而退出时,会触发此事件。为了使用此事件,消费者必须配置idle_timeout
。配置后,默认情况下,进程会在空闲超时后退出。您可以通过在监听器中设置$event->setForceStop(false)
来防止这种情况。
空闲超时
如果您需要在一段时间内没有消息从您的队列中传递时设置超时,可以在秒中设置idle_timeout
。idle_timeout_exit_code
指定消费者在空闲超时时应返回的退出代码。如果不指定,消费者将抛出PhpAmqpLib\Exception\AMQPTimeoutException
异常。
consumers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} queue_options: {name: 'upload-picture'} callback: upload_picture_service idle_timeout: 60 idle_timeout_exit_code: 0
优雅的最大执行超时
如果您希望消费者在运行一段时间后优雅地退出,请设置graceful_max_execution.timeout
(以秒为单位)。"优雅退出"意味着消费者将在当前运行的任务完成后或立即退出,当等待新任务时。graceful_max_execution.exit_code
指定消费者在优雅的最大执行超时时应返回的退出代码。如果不指定,消费者将以状态0
退出。
此功能与supervisord配合使用非常出色,它可以允许定期清理内存泄漏、重新连接数据库/rabbitmq等。
consumers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} queue_options: {name: 'upload-picture'} callback: upload_picture_service graceful_max_execution: timeout: 1800 # 30 minutes exit_code: 10 # default is 0
公平分发
您可能已经注意到分发仍然没有完全符合我们的期望。例如,在有两个工作者的情况下,当所有奇数消息都很重而偶数消息都很轻时,一个工作者将一直忙碌,而另一个工作者几乎不做任何工作。RabbitMQ对此一无所知,仍然会均匀地分发消息。
这是因为RabbitMQ只在消息进入队列时分发消息。它不会查看消费者未确认的消息数量。它只是盲目地将每第n条消息分发到第n个消费者。
为了解决这个问题,我们可以使用带有prefetch_count=1设置的basic.qos方法。这告诉RabbitMQ一次不要给工作者超过一条消息。换句话说,在工作者处理并确认了之前的消息之前,不要向其发送新消息。相反,它将消息发送到下一个不忙碌的工作者。
来源:https://rabbitmq.cn/tutorials/tutorial-two-python.html
请注意,实现公平分发会增加延迟,这会影响性能(请参阅这篇博客文章)。但实现它允许您在队列增加时动态地横向扩展。您应根据博客文章的建议,根据处理每条消息所需的时间以及您的网络性能来评估prefetch_size的正确值。
使用RabbitMqBundle,您可以像这样为每个消费者配置qos_options
consumers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} queue_options: {name: 'upload-picture'} callback: upload_picture_service qos_options: {prefetch_size: 0, prefetch_count: 1, global: false}
回调
以下是一个示例回调
<?php //src/Acme/DemoBundle/Consumer/UploadPictureConsumer.php namespace Acme\DemoBundle\Consumer; use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface; use PhpAmqpLib\Message\AMQPMessage; class UploadPictureConsumer implements ConsumerInterface { public function execute(AMQPMessage $msg) { //Process picture upload. //$msg will be an instance of `PhpAmqpLib\Message\AMQPMessage` with the $msg->body being the data sent over RabbitMQ. $isUploadSuccess = someUploadPictureMethod(); if (!$isUploadSuccess) { // If your image upload failed due to a temporary error you can return false // from your callback so the message will be rejected by the consumer and // requeued by RabbitMQ. // Any other value not equal to false will acknowledge the message and remove it // from the queue return false; } } }
如您所见,这只需要实现一个方法:ConsumerInterface::execute。
请注意,您的回调必须注册为普通Symfony服务。在那里,您可以注入服务容器、数据库服务、Symfony记录器等。
有关消息实例的更多详细信息,请参阅https://github.com/php-amqplib/php-amqplib/blob/master/doc/AMQPMessage.md。
摘要
仅发送消息就需要做很多工作,让我们总结一下,以获得更好的概述。这是我们需要生产/消费消息的内容
- 在配置中添加消费者/生产者的条目。
- 实现您的回调。
- 从CLI启动消费者。
- 将发布消息的代码添加到控制器中。
就是这样!
审计/日志
这是一项要求,以便能够追踪接收/发布的消息。为了启用此功能,您需要将 "enable_logger" 配置添加到消费者或发布者中。
consumers: upload_picture: connection: default exchange_options: {name: 'upload-picture', type: direct} queue_options: {name: 'upload-picture'} callback: upload_picture_service enable_logger: true
如果您愿意,还可以使用 monolog 的不同处理器处理队列的日志,通过引用通道 "phpamqplib"。
RPC 或回复/响应
到目前为止,我们只是向消费者发送了消息,但如果我们想要从他们那里获取回复呢?为了实现这一点,我们必须将 RPC 调用实现到我们的应用程序中。这个包使我们在 Symfony 中实现此类事情变得非常容易。
让我们在配置中添加一个 RPC 客户端和服务器。
rpc_clients: integer_store: connection: default #default: default unserializer: json_decode #default: unserialize lazy: true #default: false direct_reply_to: false rpc_servers: random_int: connection: default callback: random_int_server qos_options: {prefetch_size: 0, prefetch_count: 1, global: false} exchange_options: {name: random_int, type: topic} queue_options: {name: random_int_queue, durable: false, auto_delete: true} serializer: json_encode
有关完整的配置参考,请使用 php app/console config:dump-reference old_sound_rabbit_mq
命令。
这里有一个非常有用的服务器:它向其客户端返回随机整数。用于处理请求的回调将是 random_int_server 服务。现在让我们看看如何从我们的控制器中调用它。
首先,我们必须从命令行启动服务器。
$ ./app/console_dev rabbitmq:rpc-server random_int
然后,将以下代码添加到我们的控制器中。
public function indexAction($name) { $client = $this->get('old_sound_rabbit_mq.integer_store_rpc'); $client->addRequest(serialize(array('min' => 0, 'max' => 10)), 'random_int', 'request_id'); $replies = $client->getReplies(); }
如你所见,如果我们的客户端 ID 是 integer_store,那么服务名称将是 old_sound_rabbit_mq.integer_store_rpc。一旦我们获取了这个对象,我们就会通过调用 addRequest
来向服务器发送一个请求,它期望三个参数
- 要发送到远程过程调用的参数。
- RPC 服务器的名称,在我们的案例中 random_int。
- 我们调用的请求标识符,在这种情况下 request_id。
我们发送的参数是 min 和 max 的值,用于 rand()
函数。我们通过序列化一个数组来发送它们。如果我们的服务器期望 JSON 信息或 XML,我们将在这里发送此类数据。
最后一部分是获取回复。我们的 PHP 脚本将阻塞,直到服务器返回一个值。变量 $replies 将是一个关联数组,其中每个来自服务器的回复都将包含在相应的 request_id 键中。
默认情况下,RPC 客户端期望响应被序列化。如果您的工作服务器返回非序列化的结果,则将 RPC 客户端 expect_serialized_response 选项设置为 false。例如,如果 integer_store 服务器未序列化结果,客户端将设置如下
rpc_clients: integer_store: connection: default expect_serialized_response: false
您还可以为请求设置秒数的过期时间,在此之后,消息将不再由服务器处理,客户端请求将简单地超时。设置消息的过期时间仅适用于 RabbitMQ 3.x 及以上版本。有关更多信息,请访问 https://rabbitmq.cn/ttl.html#per-message-ttl。
public function indexAction($name) { $expiration = 5; // seconds $client = $this->get('old_sound_rabbit_mq.integer_store_rpc'); $client->addRequest($body, $server, $requestId, $routingKey, $expiration); try { $replies = $client->getReplies(); // process $replies['request_id']; } catch (\PhpAmqpLib\Exception\AMQPTimeoutException $e) { // handle timeout } }
正如你所猜到的,我们也可以进行 并行 RPC 调用。
并行 RPC
假设为了渲染某个网页,你需要执行两个数据库查询,一个需要 5 秒完成,另一个需要 2 秒 –非常昂贵的查询。如果你按顺序执行它们,那么你的页面将在大约 7 秒后准备好交付。如果你并行执行它们,那么你的页面将在大约 5 秒内被提供。使用 RabbitMqBundle,我们可以轻松地执行此类并行调用。让我们在配置中定义一个并行客户端和另一个 RPC 服务器
rpc_clients: parallel: connection: default rpc_servers: char_count: connection: default callback: char_count_server random_int: connection: default callback: random_int_server
然后这段代码应该放在我们的控制器中
public function indexAction($name) { $client = $this->get('old_sound_rabbit_mq.parallel_rpc'); $client->addRequest($name, 'char_count', 'char_count'); $client->addRequest(serialize(array('min' => 0, 'max' => 10)), 'random_int', 'random_int'); $replies = $client->getReplies(); }
与上一个例子非常相似,我们只是多了一个 addRequest
调用。我们还提供了有意义的请求标识符,这样我们稍后就可以更容易地找到我们想要的回复,在 $replies 数组中。
直接回复到客户端
为了启用 直接回复到客户端,您只需在客户端的 rpc_clients 配置中启用 direct_reply_to 选项即可。
此选项在进行RPC调用时会使用伪队列 amq.rabbitmq.reply-to。在RPC服务器上不需要进行任何修改。
多个消费者
有许多队列进行逻辑分离是一种好习惯。使用简单的消费者时,您必须为每个队列创建一个工作器(消费者),当处理许多变化时可能会很困难(忘记在您的supervisord配置中添加一行?)。这也有助于处理小型队列,因为您可能不希望拥有与队列一样多的工作者,并希望将一些任务重新组合在一起,同时不失灵活性和分离原则。
多个消费者允许您通过同一消费者监听多个队列来处理此用例。
以下是设置具有多个队列的消费者的方法
multiple_consumers: upload: connection: default exchange_options: {name: 'upload', type: direct} queues_provider: queues_provider_service queues: upload-picture: name: upload_picture callback: upload_picture_service routing_keys: - picture upload-video: name: upload_video callback: upload_video_service routing_keys: - video upload-stats: name: upload_stats callback: upload_stats
回调现在在各个队列下指定,必须像简单消费者一样实现ConsumerInterface
。消费者中的所有queues-options
选项都适用于每个队列。
请注意,所有队列都在同一个交换器下,设置回调的正确路由由您自己决定。
queues_provider
是一个可选服务,它动态提供队列。它必须实现QueuesProviderInterface
。
请注意,队列提供者负责对setDequeuer
进行适当的调用,并且回调是可调用的(不是ConsumerInterface
)。如果提供队列的服务实现了DequeuerAwareInterface
,则在服务的定义中将添加对setDequeuer
的调用,当前DequeuerInterface
是MultipleConsumer
。
任意绑定
您可能会发现,您的应用程序有一个复杂的流程,并且您需要任意绑定。任意绑定场景可能包括通过destination_is_exchange
属性进行交换到交换的绑定。
bindings: - {exchange: foo, destination: bar, routing_key: 'baz.*' } - {exchange: foo1, destination: foo, routing_key: 'baz.*' destination_is_exchange: true}
rabbitmq:setup-fabric命令将在创建任意绑定之前声明由您的生产者、消费者和多消费者配置中定义的交换器和队列。然而,rabbitmq:setup-fabric将不会声明绑定中定义的附加队列和交换器。您需要确保交换器和队列已被声明。
动态消费者
有时您必须实时更改消费者的配置。动态消费者允许您根据上下文以编程方式定义消费者队列选项。
例如,在定义的消费者必须负责动态数量的主题,并且您不希望(或不能)每次都更改其配置的场景中。
定义一个实现QueueOptionsProviderInterface
的服务queue_options_provider
,并将其添加到您的dynamic_consumers
配置中。
dynamic_consumers: proc_logs: connection: default exchange_options: {name: 'logs', type: topic} callback: parse_logs_service queue_options_provider: queue_options_provider_service
示例用法
$ ./app/console rabbitmq:dynamic-consumer proc_logs server1
在这种情况下,proc_logs
消费者为server1
运行,并且它可以决定它使用的队列选项。
匿名消费者
现在,我们为什么需要匿名消费者呢?这听起来像是一种互联网威胁或类似的东西……继续阅读。
在AMQP中,有一种称为主题的交换类型,其中消息根据——您猜对了——消息的主题路由到队列。我们可以使用日志主题将应用程序的日志发送到RabbitMQ主题交换,其中主题是创建日志的主机名和日志的严重性。消息体将是日志内容,我们的路由键将是这样的
- server1.error
- server2.info
- server1.warning
- ...
由于我们不希望用无限的日志填充队列,所以当我们想监控系统时,我们可以启动一个消费者,该消费者创建一个队列并将它连接到基于某个主题的 logs 交换机,例如,我们想查看我们服务器报告的所有错误。路由键将类似于:#.error。在这种情况下,我们需要想出一个队列名称,将其绑定到交换机,获取日志,解绑它并删除队列。幸运的是,AMPQ 提供了一种在声明和绑定队列时提供正确选项来自动完成此操作的方法。问题是,你不想记住所有这些选项。因此,我们实现了 匿名消费者 模式。
当我们启动匿名消费者时,它会处理这些细节,我们只需考虑实现消息到达时的回调。之所以称为匿名,是因为它不会指定队列名称,但它将等待 RabbitMQ 为它分配一个随机的名称。
那么,如何配置和运行这样的消费者呢?
anon_consumers: logs_watcher: connection: default exchange_options: {name: 'app-logs', type: topic} callback: logs_watcher
在这里,我们指定交换机名称及其类型,以及当消息到达时应执行的回调。
这个匿名消费者现在能够监听与相同交换机链接且类型为 topic 的生产者。
producers: app_logs: connection: default exchange_options: {name: 'app-logs', type: topic}
要启动 匿名消费者,我们使用以下命令
$ ./app/console_dev rabbitmq:anon-consumer -m 5 -r '#.error' logs_watcher
与之前看到的命令相比,唯一的区别是指定了 路由键:-r '#.error'
。
批量消费者
在某些情况下,你可能希望获取一批消息,然后对它们进行一些处理。批量消费者将允许你为这种类型的处理定义逻辑。
例如:想象一下,你有一个队列,你收到一条消息用于将一些信息插入数据库,然后你意识到如果你批量插入比逐个插入要好得多。
定义一个实现 BatchConsumerInterface
的回调服务,并将消费者定义添加到你的配置中。
batch_consumers: batch_basic_consumer: connection: default exchange_options: {name: 'batch', type: fanout} queue_options: {name: 'batch'} callback: batch.basic qos_options: {prefetch_size: 0, prefetch_count: 2, global: false} timeout_wait: 5 auto_setup_fabric: false idle_timeout_exit_code: -2 keep_alive: false graceful_max_execution: timeout: 60
注意:如果将 keep_alive
选项设置为 true
,则忽略 idle_timeout_exit_code
,消费者进程将继续。
你可以实现一个批量消费者,它在一次返回中确认所有消息,或者你可以控制哪些消息要确认。
namespace AppBundle\Service; use OldSound\RabbitMqBundle\RabbitMq\BatchConsumerInterface; use PhpAmqpLib\Message\AMQPMessage; class DevckBasicConsumer implements BatchConsumerInterface { /** * @inheritDoc */ public function batchExecute(array $messages) { echo sprintf('Doing batch execution%s', PHP_EOL); foreach ($messages as $message) { $this->executeSomeLogicPerMessage($message); } // you ack all messages got in batch return true; } }
namespace AppBundle\Service; use OldSound\RabbitMqBundle\RabbitMq\BatchConsumerInterface; use PhpAmqpLib\Message\AMQPMessage; class DevckBasicConsumer implements BatchConsumerInterface { /** * @inheritDoc */ public function batchExecute(array $messages) { echo sprintf('Doing batch execution%s', PHP_EOL); $result = []; /** @var AMQPMessage $message */ foreach ($messages as $message) { $result[(int)$message->delivery_info['delivery_tag']] = $this->executeSomeLogicPerMessage($message); } // you ack only some messages that have return true // e.g: // $return = [ // 1 => true, // 2 => true, // 3 => false, // 4 => true, // 5 => -1, // 6 => 2, // ]; // The following will happen: // * ack: 1,2,4 // * reject and requeq: 3 // * nack and requeue: 6 // * reject and drop: 5 return $result; } }
如何运行以下批量消费者
$ ./bin/console rabbitmq:batch:consumer batch_basic_consumer -w
重要:批量消费者将没有 -m|messages 选项可用
STDIN 生产者
有一个命令从 STDIN 读取数据并将其发布到 RabbitMQ 队列。要使用它,首先你必须在配置文件中配置一个 producer
服务,如下所示
producers: words: connection: default exchange_options: {name: 'words', type: direct}
该生产者将向 words
直达交换机发布消息。当然,你可以根据需要调整配置。
然后,假设你想要发布一些 XML 文件的内容,以便它们由消费者农场处理。你可以使用以下命令来发布它们
$ find vendor/symfony/ -name "*.xml" -print0 | xargs -0 cat | ./app/console rabbitmq:stdin-producer words
这意味着你可以使用普通的 Unix 命令来组合生产者。
让我们分解一下这个单行命令
$ find vendor/symfony/ -name "*.xml" -print0
该命令将在 symfony 文件夹中查找所有 .xml
文件,并将文件名打印出来。然后,每个文件名都通过 xargs
传递给 cat
。
$ xargs -0 cat
最后,cat
的输出直接传递给我们的生产者,该生产者以这种方式调用
$ ./app/console rabbitmq:stdin-producer words
它只接受一个参数,即你在 config.yml
文件中配置的生产者名称。
其他命令
设置 RabbitMQ 体系结构
此包的目的是让您的应用程序生成消息并将它们发布到您配置的一些交换机上。
在某些情况下,即使您的配置正确,您生成的消息也可能不会路由到任何队列,因为没有队列存在。负责队列消费的消费者必须运行才能创建队列。
当消费者数量高时,为每个消费者启动命令可能是一场噩梦。
为了一次性创建交换、队列和绑定,并确保您不会丢失任何消息,您可以运行以下命令
$ ./app/console rabbitmq:setup-fabric
当需要时,您可以为您的消费者和生产者配置,使其假设 RabbitMQ 架构已经定义。为此,将以下内容添加到您的配置中
producers: upload_picture: auto_setup_fabric: false consumers: upload_picture: auto_setup_fabric: false
默认情况下,消费者或生产者在启动时会向 RabbitMQ 声明其所需的一切。使用时要小心,如果交换或队列未定义,将出现错误。当您更改任何配置时,您需要运行上面的 setup-fabric 命令来声明您的配置。
如何贡献
要贡献,只需提交一个带有您新代码的拉取请求,注意如果您添加了新功能或修改了现有功能,您必须在本 README 中说明它们的功能。如果您破坏了 BC,您也必须记录下来。此外,您必须更新 CHANGELOG。所以
- 记录新功能。
- 更新 CHANGELOG。
- 记录 BC 破坏性更改。
许可
见:资源/元/LICENSE.md
致谢
包结构和文档部分基于 RedisBundle