直接回复
概述
直接回复是一个功能,它允许 RPC(请求/回复)客户端采用类似于教程 6中演示的设计,而无需创建回复队列。
客户端使用显式声明的队列(包括长期存在的客户端命名队列和连接特定的独占队列)的请求-回复实现与直接回复一样有效,并且具有其优点,特别是对于具有长时间运行任务的工作负载。
动机
RPC(请求/回复)是使用像 RabbitMQ 这样的消息代理实现的常用模式。教程 6 演示了其与各种客户端的实现。 典型的做法是让 RPC 客户端发送路由到长期存在的(已知的)服务器队列的请求。 RPC 服务器从该队列中消费请求,然后使用客户端在 reply-to
标头中命名的队列向每个客户端发送回复。
客户端的队列从哪里来? 客户端可以为每个请求-响应对声明一个一次性队列。 但这是低效的;即使是未复制的队列,创建然后再删除也可能很昂贵(与发送消息的成本相比)。 在集群中尤其如此,因为所有集群节点都需要同意队列已创建,同意其类型、复制参数和其他元数据。
因此,客户端应为多个 RPC 请求创建一个单独的回复队列。
此回复队列的属性取决于用例
- 独占队列 通常在回复由单个客户端消费并在断开连接时删除时使用
- 非独占的长期存在的队列 更适合长时间运行的任务,确保即使客户端暂时断开连接,回复也能持久存在
直接回复消除了对回复队列的需求。 这有利于具有短生命周期队列和瞬态响应的请求-回复实现,但代价是放弃了对如何存储响应的所有控制。
使用直接回复,RPC 客户端将直接从其 RPC 服务器接收回复,而无需通过回复队列。“直接”在此处仍然意味着通过相同的通道和 RabbitMQ 节点;RPC 客户端和 RPC 服务器进程之间没有直接网络连接。
如何使用直接回复
要使用直接回复,RPC 客户端应
从伪队列
amq.rabbitmq.reply-to
以 no-ack 模式消费。 没有必要首先声明这个“队列”,尽管客户端如果愿意可以这样做。将其请求消息中的
reply-to
属性设置为amq.rabbitmq.reply-to
。
然后,RPC 服务器将看到一个带有生成名称的 reply-to
属性。 它应该发布到默认交换机 (""
),并将路由键设置为此值(即,就像它通常发送到回复队列一样)。 然后,消息将直接发送到客户端消费者。
如果 RPC 服务器将要执行一些昂贵的计算,它可能希望检查客户端是否已离开。 为此,服务器可以首先在一次性通道上声明生成的回复名称,以确定它是否仍然存在。 请注意,即使您使用 passive=false
声明“队列”,也无法创建它;声明只会成功(有 0 条消息准备就绪和 1 个消费者)或失败。
注意事项和限制
RPC 客户端必须以自动确认模式进行消费。 这是因为如果没有回复队列,回复消息在客户端断开连接或拒绝回复消息时无法返回。
RPC 客户端必须使用相同的连接和通道,既用于从
amq.rabbitmq.reply-to
消费,又用于发布请求消息。使用此机制发送的回复消息通常不具备容错能力;如果发布原始请求的客户端随后断开连接,它们将被丢弃。 假设是 RPC 客户端将重新连接并在这种情况下提交另一个请求。
名称
amq.rabbitmq.reply-to
在basic.consume
和reply-to
属性中被用作队列;但它不是。 它无法删除,并且不会出现在管理插件或rabbitmqctl list_queues
中。如果 RPC 服务器发布时设置了 mandatory 标志,则
amq.rabbitmq.reply-to.*
被视为不是队列;即,如果服务器仅发布到此名称,则该消息将被视为“未路由”;如果设置了 mandatory 标志,则将发送basic.return
。
何时使用直接回复
虽然客户端应使用长期存在的连接,但直接回复非常适合具有高连接流失率的工作负载,在这种情况下,客户端为单个 RPC 建立连接并在之后立即断开连接。 通过避免在元数据存储中创建队列元数据,直接回复可以减少开销和延迟。
对于具有长期存在的连接且客户端执行多个 RPC 的工作负载,与经典队列相比,直接回复的性能优势并不显著。 现代 RabbitMQ 版本已针对低延迟和最小资源使用量优化了经典队列,使其在这种情况下同样高效。