RabbitMQ 的请求/响应模式

来源:互联网 发布:mysql substring函数 编辑:程序博客网 时间:2024/05/24 01:02

如果你正在进行web服务编程,那么最常用的模式是请求-响应模式。这种模式总是由客户端发起,然后等待服务器端的响应。如果客户端想发送一些信息给服务器,或者客户端按照某些标准请求一些信息,那么这种模式非常适合。如果服务器想自己发送信息给客户端,那么这种模式就非常不适合。这时我们就必须依赖像长轮询或者web挂钩这样的对HTTP进行某种程度扩展的技巧了。

而对消息系统来说,最常用的模式是发送-接收模式。生产者节点发布一条信息,接下来这条信息会被传送给消费节点。这儿没有纯粹的客户端或者服务器的概念;节点可以是生产者,也可以是消费者,或者二者兼有。当一个节点想发送一些信息给另一个节点或者相反,这种模式都运行的非常好,不过,如果一个节点想按照某些标准向另一个节点请求信息,那么这种模式就不是很适合。

然而,这一切并不是完全做不到。我们可以模仿请求-应答模式:让客户端创建一个应答队列,这个队列存储客户端发送给服务器的查询消息的应答。客户端可以设置请求消息的reply_to属性字段为应答队列名。服务器检查reply_to子段,然后通过默认的整理中心把应答消息发布给应答队列,接着这个消息就由客户端接收。

request-response

请求端的实现很简单;它看起来就像标准的发送-接收模式。而对于应答端,我们可以有多个选择。如果你通过谷歌搜寻"RabbitMQ RPC"或者"RabbitMQ request response",你就发现有关应答队列的性质方面有几个不同的意见:

  • 每个请求是不是应该对应一个应答队列,或者客户端对多个请求是不是应该只维护一个应答队列?
  • 应答队列是不是应该是独享的,即只可用于一个通道,或者应该不是独享的?注意:当通道关闭的时候,独享队列应该删除,无论是有意,还是网络中断或者转发网关失效,都能引起连接丢失。

让我们看看这些意见的优点和缺点。

每个请求独享一个应答队列

这种情况下,每个请求创建一个应答队列。好处是实现起来简单。把响应与请求关联起来也没有问题,因为每个请求都有它自己对应的响应消费者。如果客户端与代理之间的连接在响应接收之前就断开了,那么代理就会清除掉剩余的应答队列,这时就会丢失响应的消息。

实现这个的主要问题是:倘若由于服务器问题引起服务器无法发布响应,那么我们必须清除所有的应答队列。

这种方式有很大的性能开销,因为它对每个请求都要创建一个新的队列和消费者。

每个客户端独享一个应答队列

这种情况下,每个客户端连接维护着由许多请求共享的应答队列。这减少了对每个请求都要创建队列和消费者所造成的性能开销,不过它增加了客户端需要跟踪应答队列并且把应答与各自对应的请求匹配方面的开销。处理这个的标准办法是使用关联id,服务器可以从响应所对应的请求里拷贝这个id。

再次说明一下,在客户端断开的时候,删除应答队列没有任何问题,因为转发网关会自动删除队列的。然而,这确实意味着断开的那个时刻的仍在传输的任何响应都将丢失。

永久应答队列

上面两种情形都存在这样的问题:如果客户端和转发网关之间的连接断开,并且响应还处在运行状态,那么这些响应就会丢失。这是因为它们使用的是独享型的队列,也就是说,当拥有这个队列的连接关闭的时候,转发网关必须删除这个队列。

针对这个问题的常见的解决办法就是使用非独享型的应答队列。不过这会引起一些管理开销。你需要采用某种方式命名应答队列,并把它与特定的客户端关联起来。问题是:客户端很难知道一个应答队列是属于自己的,还是属于另一个客户端的。不过随意地创建一个可把响应发送给不对应的客户端这样的环境却非常容易。你可能最终要手工创建和命名响应队列,这么做就没有了第一种情况下可根据消息选择代理的好处了。

EasyNetQ

对一个像EasyNetQ这样高级的可重用的库来说,不可能实现永久应答队列。因为没有明确的方法知道EasyNetQ库的具体实例是属于客户端应用的哪一种逻辑实例。这儿的“逻辑实例”我指的是可能停止的和可能已经启动的单个逻辑实例,与此对应的是同一个客户端具有两个独立的实例。

因此,我们必须使用独享型队列,并且要接受响应消息的偶尔丢失。实现超时机制是非常重要的,这样如果出现响应丢失,那么就向客户端应用抛出一个异常。理想情况下,客户端将捕捉这个异常,然后在适当的情况下重新发送这条消息。

目前EasyNetQ实现了"每个请求独享一个应答队列“这种模式,而且我们正在有计划地修改这种模式为”每个客户端独享一个应答队列“模式。要求把响应与请求匹配起来的开销不能太多,还要求既要高效又要易于管理。

我非常有兴趣听取其他人在用RabbitMQ实现请求-发送模式的经验。


0 0