有关如何组织点,队列以及如何正确命名它们的一些规则,以方便使用。
| exchange | type | binding_queue | binding_key | |-------------------------------------------------------| | user.write | topic | user.created.app2 | user.created |
概念AMQP (高级消息队列协议)是一种开放协议,用于在系统组件之间传输消息。
提供者 (发布者/生产者)-发送消息的程序。
订户 (消费者)-接受消息的程序。 通常,订户处于消息等待状态。
队列 -消息队列。
交换点 (Exchange)-队列的初始交换点,用于路由。
规则一
每个队列仅代表一种类型的作业。 不要在同一队列中混合使用不同类型的消息。 当遵守此规则时,我们可以清楚地命名呈现给他们的任务队列。
规则二
避免将邮件重新提交到队列。 如果您发现您的订户试图将任何消息重新发送到其他队列而不进行实际处理,则很可能是某些内容的设计不正确。 路由是交换点而不是队列的责任。
规则三
供应商无需了解任何有关队列的知识。 AMQP的主要思想之一是在点和队列之间划分责任,因此供应商无需担心将消息传递给订户。
示例和解决方案
假设您要设计与“自定义”相关的记录事件的点和队列。 记录事件将在一个或多个应用程序中触发,而这些消息将由其他一些应用程序使用。
| object | event | |------------------| | user | created | | user | updated | | user | deleted |
通常要问的第一个问题是一个对象的各种事件(在此示例中为“用户”对象),应该使用一个交换点来发布所有三个事件,还是应该对每个事件使用3个单独的点? 或者,简而言之,一个或多个交换点?
在回答这个问题之前,我想再问一个问题:在这种情况下,我们真的需要一个单独的观点吗? 如果我们将所有三种类型的事件抽象为“创建”事件,其子类型为“创建”,“更新”和“删除”,该怎么办?
| object | event | sub-type | |-----------------------------| | user | write | created | | user | write | updated | | user | write | deleted |
解决方案1
最简单的解决方案是创建“ user.write”队列,并通过全局交换点将用户记录事件中的所有消息发布到此队列。
决定2
当第二个应用程序(具有不同的处理逻辑)想要订阅队列中发布的任何消息时,最简单的解决方案将无法工作。 对多个应用程序进行签名后,我们至少需要一个“ fanout”类型的点,并绑定到多个队列。 因此,消息被发送到该点,并在每个队列中复制消息。 每个队列代表每个应用程序的处理作业。
| queue | subscriber | |-------------------------------| | user.write.app1 | app1 | | user.write.app2 | app2 | | exchange | type | binding_queue | |---------------------------------------| | user.write | fanout | user.write.app1 | | user.write | fanout | user.write.app2 |
如果每个订阅者都确实想处理“ user.write”事件的所有子类型,则第二种解决方案可以正常工作。 例如,如果订户的应用程序设计为仅存储事务日志。
另一方面,当某些订阅者不在您的组织范围内并且您只希望将某些特定事件通知给他们时,这不是很好,例如,app2应该收到有关用户创建的消息,而不应该知道更新和删除事件。
决定3
为了解决上述问题,我们必须从“ user.write”类型中提取“ user.created”事件。 具有主题类型的交换点可以为我们提供帮助。 发布消息时,我们将使用user.created / user.updated / user.deleted作为某个点的路由密钥,因此我们可以将通信密钥“ user。*”放入队列“ user.write.app1”和通信密钥“ user.created”在队列“ user.created.app2”中。
| queue | subscriber | |---------------------------------| | user.write.app1 | app1 | | user.created.app2 | app2 | | exchange | type | binding_queue | binding_key | |-------------------------------------------------------| | user.write | topic | user.write.app1 | user.* | | user.write | topic | user.created.app2 | user.created |
决定4
如果可能存在更多事件类型,则交换点的主题主题类型更为灵活。 但是,如果您清楚地知道事件的确切数量,则也可以使用“直接”类型来提高性能。
| queue | subscriber | |---------------------------------| | user.write.app1 | app1 | | user.created.app2 | app2 | | exchange | type | binding_queue | binding_key | |--------------------------------------------------------| | user.write | direct | user.write.app1 | user.created | | user.write | direct | user.write.app1 | user.updated | | user.write | direct | user.write.app1 | user.deleted | | user.write | direct | user.created.app2 | user.created |
我们回到“一点还是很多?”这个问题。 只要所有解决方案都只使用一点,它就可以正常工作,没什么不好。 在什么情况下我们需要几点?
决定5
让我们看一个示例,除了上述创建,更新和删除的事件之外,我们还有另一组事件:输入和输出-一组描述“用户行为”而不是“数据记录”的事件。 对于不同的事件组,可能需要完全不同的路由策略以及队列的键和名称的约定,为此,需要单独的交换点。
| queue | subscriber | |----------------------------------| | user.write.app1 | app1 | | user.created.app2 | app2 | | user.behavior.app3 | app3 | | exchange | type | binding_queue | binding_key | |--------------------------------------------------------------| | user.write | topic | user.write.app1 | user.* | | user.write | topic | user.created.app2 | user.created | | user.behavior | topic | user.behavior.app3 | user.* |
文章
RabbitMQ交换和队列设计权衡的免费翻译。