RabbitMQ最佳实践

RabbitMQ最佳实践,第1张

有些应用程序需要非常高的吞吐量,而其他一些应用程序却正在发布批处理作业,这些作业可能会延迟一段时间。在设计系统时,目标应该是最大限度地将性能和可用性结合起来,这对您的特定应用程序是有意义的。错误的体系结构设计决策或客户端错误,可能会损坏中间件或影响吞吐量。

您的发布服务器可能会停止运行,或者由于内存使用过多而导致服务器崩溃。本系列文章重点关注rabbitmq的最佳实践。应做和不应做两种不同使用类别的最佳实践相混合;高可用性和高性能(高吞吐量)。我们将讨论队列大小、常见错误、延迟队列、预取值、连接和通道、HIPE和集群中的节点数。这些通常都是最佳实践规则,基于我们在使用rabbitmq时获得的经验。

队列中的许多消息会对RAM的使用造成很大的负担。为了释放RAM,rabbitmq将(页面输出)消息刷新到磁盘。此过程会降低排队速度。当有许多消息需要分页取出时,分页过程通常会花费时间并阻止队列处理消息。许多消息可能会对中间件的性能产生负面影响。

当有许多消息重启集群时,也是费时的,因为必须重建索引。重新启动后,在群集中的节点之间同步消息也需要时间。

在rabbitmq 36中添加了一个名为lazy queues的功能。懒惰队列是消息自动存储到磁盘上的队列。只有在需要时才将消息加载到内存中。对于懒惰的队列,消息直接进入磁盘,因此RAM的使用被最小化,但是吞吐时间将花费更长的时间。

我们已经看到,懒惰的队列以更好的可预测性的方式,创建了一个更稳定的集群。要让您的消息不出现警告,请刷新到磁盘。你不会突然被一个性能冲击问题所困扰。如果您一次发送大量消息(例如处理批处理作业),或者如果您认为您的消费者一直无法跟上发布者的速度,我们建议您启用延迟队列。

对于经常受到消息峰值冲击的应用程序,以及要求吞吐量比其他任何东西都重要的应用程序,可以推荐的另一做法是设置队列的最大长度。这样可以通过丢弃来自队列头部的消息来保持队列的简短性,从而使队列永远不会超过max-length设置。

队列在rabbitmq中是单线程的,一个队列可以处理大约50k条消息/秒。如果您有多个队列和消费者,您可以在多核系统上获得更好的吞吐量。如果在底层节点上拥有与核心一样多的队列,那么您将获得最佳吞吐量。

rabbitmq管理接口为集群中的每个队列收集和计算度量。如果您有数千个活动队列和使用者,这可能会减慢服务器的运行速度。如果队列太多,CPU和RAM的使用也可能受到负面影响。

队列性能受限于一个CPU核心。因此,如果将队列拆分到不同的核心,您将获得更好的性能;如果您拥有rabbitmq集群,您也可以将他们拆分到不同的节点。

rabbitmq队列绑定到最初声明它们的节点。即使您创建了一个rabbitmq中间件集群,所有路由到特定队列的消息都将转到该队列所在的节点。您可以在节点之间平均地手动拆分队列,但缺点是您需要记住队列的位置。

如果您有多个节点或具有多个核心的单节点集群,我们建议使用两个插件来帮助您:

当您想要在生产者和消费者之间共享队列时,为队列命名是很重要的,但是如果您使用临时队列,则不重要。相反,您应该让服务器使用一个随机的队列名称,而不是你自己命名一个——或者修改rabbitmq策略。

客户机连接可能会失败,并可能留下未使用的资源(队列),留下许多队列可能会影响性能。自动删除队列有三种方法:

在 Erlang VM 的内部队列每个队列均使用用了一个优先级别,他们耗费了一些资源。在大多数情况下,不超过5个优先级就足够了。

一个常见的问题是如何处理发送到rabbitmq的消息的palyload(消息大小)。当然,您不应该在消息中发送非常大的文件信息,但是每秒的消息数是一个比它本身的消息大小更大的瓶颈。发送多个小消息可能是一个坏的选择。一个更好的办法是将它们捆绑成一个更大的消息,让消费者将其拆分。但是,如果捆绑多条消息,则需要记住这可能会影响处理时间。如果其中一条捆绑消息失败,是否需要重新处理所有这些消息?如何设置这个取决于带宽和体系结构。

每个连接使用大约100kb的RAM(如果使用TLS,甚至更多)。数千个连接可能是rabbitmq服务器的沉重负担。在最坏的情况下,服务器可能由于内存不足而崩溃。AMQP协议有一种称为“多路复用”的机制,它“复用”单个TCP连接。它建议每个进程只创建一个TCP连接,并在这个唯一一个连接的基础上为不同的线程使用多个通道。连接也应该是长连接的。AMQP连接的握手过程非常复杂,至少需要7个TCP数据包(如果使用了TLS,则需要更多)。

相反,如果需要,可以更频繁地打开和关闭通道。如果可能的话,甚至通道也应该是长寿命的,例如,在每个发布信息线程中复用相同的通道。每次发布信息时不用打开频道。最佳实践是复用连接,使用各通道在一个连接的基础上实现多路复用。理想情况下,每个进程只能有一个连接,然后在应用程序中为每个线程使用一个通道,而每个channel 复用同一个连接即可。

您还应该确保不在线程之间共享通道,因为大多数客户机不保证通道是线程安全的(因为这样会对性能产生严重的负面影响)。

确保不要在线程之间共享通道,因为大多数客户机不会使通道线程安全(因为这样会对性能产生严重的负面影响)。

为发布者和消费者区分连接以获得高吞吐量。当发布服务器向服务器发送太多要处理的消息时,rabbitmq可以对TCP连接施加反向压力。如果消费者使用相同的TCP连接,服务器可能不会从客户机接收消息确认。因此,消费性能也会受到影响。而随着消费速度的降低,服务器将不堪重负。

具有大量连接和通道的另一个影响为rabbitmq管理接口的性能。对于每个连接和通道性能,指标必须收集、分析和显示度量。

在连接失败的情况下,传输中的消息可能会丢失,并且可能需要重新传输此类消息。Acknowledgements 让服务器和客户机知道何时重新传输消息。客户机可以在收到消息时对其进行确认,也可以在客户机完全处理完消息后对其进行确认。Acknowledgement 具有性能影响,因此为了实现最快的吞吐量,应该禁用手动确认。

接收重要消息的消费应用程序在完成需要对其进行的任何 *** 作之前不应确认消息,这样未处理的消息(工作进程崩溃、异常等)就不会丢失。

发布确认,是相同的事情,但用于发布。服务器收到来自发布服务器的消息时会进行确认。发布确认也会影响性能。但是,应该记住,如果发布者至少需要处理一次消息,就需要这样做。

所有未确认的消息必须驻留在服务器上的RAM中。如果您有太多未确认的消息,您将耗尽内存。限制未确认消息的一个有效方法是客户端预取的消息数做出相关设置。在预取部分了解有关预取的更多信息。

如果您不能承受丢失任何消息的代价,请确保您的队列声明为“持久”,并且您的消息以传递模式“持久”发送。

为了避免在中间件中丢失消息,需要为中间件重新启动、中间件硬件故障或中间件崩溃时做好准备。为了确保消息和中间件定义在重新启动后仍然存在,我们需要确保它们在磁盘上。在中间件重新启动期间,不持久的消息、交换和队列将会被丢失。

持久性消息更重,因为它们必须写入磁盘。请记住,即使您发送的是临时消息,懒惰的队列也会对性能产生相同的影响。对于高性能-请使用瞬态消息。

您可以通过amqps连接到rabbitmq,这是用tls包装的amqp协议。由于所有流量都必须加密和解密,因此TLS会影响性能。为了获得最大的性能,我们建议使用vpc对等,那么流量是私有的,并且是独立的,不涉及AMQP客户机/服务器。

在cloudamqp中,我们将rabbitmq服务器配置为只接受快速但安全的加密密码并确定其优先级。

预取值用于指定多少条消息将同时被发送给消费者。它被用来从你的消费者那里得到尽可能多的东西(饱和工作)。

From RabbitMQcom: “The goal is to keep the consumers saturated with work, but to minimise the client's buffer size so that more messages stay in Rabbit's queue and are thus available for new consumers or to just be sent out to consumers as they become free”

来自rabbitmqcom:“我们的目标是让消费者饱和工作,但要最大限度地减小客户机的缓冲区大小,因此更多的消息被留在Rabbit的队列中,从而对新的消费者可用,或者发送给那些变得空闲的消者。”

rabbitmq的默认预取设置为客户端提供了一个不受限制的缓冲区,这意味着rabbitmq在默认情况下会将尽可能多的消息发送给任何看起来准备接受它们的客户机。发送的消息由rabbitmq客户端库(在使用者中)缓存,直到对其进行处理。预取限制了在确认消息之前客户端可以接收的消息数。所有预取的消息都将从队列中删除,并且对其他使用者不可见。

A too small prefetch count may hurt performance since RabbitMQ is most of the time waiting to get permission to send more messages The image below is illustrating long idling time In the example, we have a QoS prefetch setting of 1 This means that RabbitMQ won't send out the next message until after the round trip completes (deliver, process, acknowledge) Round time in this picture is in total 125ms with a processing time of only 5ms

预取数太小可能会影响性能,因为rabbitmq大多数时间都在等待获得发送更多消息的许可。下图显示的是长时间的空转时间。在本例中,QoS预取设置为1。这意味着rabbitmq在往返完成(传递、处理、确认)之前不会发送下一条消息。中的整个周期时间总共为125ms,处理时间仅为5ms。

另一方面,大量的预取数可以接收队列中的大量消息并将其传递给同一个消费者,但是其他使用者却处于空闲状态。

如果您有一个或几个消费者快速处理消息,我们建议您一次预取多个消息。尽量让你的客户端繁忙。如果您一直有大约相同的处理时间,并且网络行为保持不变-您只需在客户机上为每个消息计算总的往返时间/处理时间,即可获得估计的预取值。

如果您有许多消费者,并且处理时间很短,我们建议预取值设置的应该比单个或少数使用者要低一些。太低的值会让消费者空转很多,因为他们需要等待消息到达。过高的值可能会使一个消费者忙碌,而其他消费者则处于空闲状态。

如果您有许多使用者和/或处理时间较长,我们建议您将预取计数设置为1,以便消息在所有消费者中均匀分布。

请注意,如果客户端自动确认消息,则预取值将不起作用。

一个典型的错误是有一个无限的预取,其中一个客户机接收所有的消息,耗尽内存并崩溃,然后所有的消息都被重新传递。

有关rabbitmq预取的信息,请参阅推荐的rabbitmq文档。

HIPE将以增加启动时间为代价增加服务器吞吐量。启用HIPE时,将在启动时编译rabbitmq。根据我们的基准测试,吞吐量增加了20-80%。HIPE的缺点是启动时间也增加了很多,大约1-3分钟。在rabbitmq的文档中,hipe仍然被标记为实验性的。

如果您需要高可用性,请不要启用HIPE。

当您用一个节点创建一个cloudamqp实例时,您将得到一个具有高性能的单个节点。一个节点将为您提供 最高的性能 ,因为消息不需要在多个节点之间进行镜像。

当您使用两个节点创建一个CloudAMQP实例时,与单个节点的相比,您将获得一半的性能。节点位于不同的可用性区域,队列在可用性区域之间自动镜像。两个节点将为您提供 高可用性 ,因为一个节点可能崩溃或被标记为受损,但另一个节点仍将启动并运行,准备接收消息。

当您使用三个节点创建一个CloudAMQP实例时,与单个节点的相同计划大小相比,您将获得1/4的性能。节点位于不同的可用性区域,队列在可用性区域之间自动镜像。您也可以暂停少数组件-与允许每个节点响应相比,通过关闭少数组件,您减少了重复传递。暂停少数组件是三节点集群中的一种分区处理策略,它可以防止由于网络拆分而导致数据不一致。

我们在cloudamqp集群中注意到的一个常见错误是,用户创建了一个新的vhost,但忘记为新的vhost启用一个ha策略。如果没有HA策略,则不会在节点之间同步消息。

直接交换是最快速。如果有许多bindings ,rabbitmq必须计算将消息发送到何处。

有些插件可能非常好用,但它们可能会消耗大量的CPU或RAM。因此,不建议将它们用于生产服务器。确保禁用不使用的插件。您可以通过CloudAmqp中的控制面板启用许多不同的插件。

将rabbitmq管理统计速率模式设置为detailed会严重影响性能,不应在生产中使用。

确保您使用的是最新推荐的客户端库版本

保持最新稳定版本的rabbitmq和erlang。在为客户发布新的主要版本之前,我们通常会在很大程度上对其进行测试。请注意,在为新集群选择版本的下拉列表中,我们始终使用最推荐的版本作为所选选项(默认)。

Dead lettering和TTL是rabbitmq中的两个流行功能,应该谨慎使用。TTL和Dead lettering可以产生您没有预料到的性能影响。

使用x-dead-letter-exchange属性声明的队列将向指定的dead-letter-exchange 发送被拒绝、非确认或过期(带有ttl)的消息。如果您指定了x-dead-letter-routing-key,则消息的路由键将在dead lettered时更改。

通过使用x-message-ttl属性声明队列,如果消息在指定的时间内未被使用,则将从队列中丢弃消息。

使用RabbitMQ作为任务队列的轮子很少,基本都已停止更新(17年),这里推荐一个国人的修改版,最近才开始发布,但是经本人实测已经可以正常使用

项目地址(GitHub) scrapy-rabbitmq-scheduler

因是国人写的,所以README文件写的通俗易懂

pip install scrapy-rabbitmq-scheduler

在settingspy最后加入

<pre style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important;">

Copy

`# 指定项目的调度器

SCHEDULER = "scrapy_rabbitmq_schedulerschedulerSaaS"

RABBITMQ_CONNECTION_PARAMETERS = ' amqp://admin:pwd@xxxx:5672/'

DOWNLOADER_MIDDLEWARES = {

'scrapy_rabbitmq_schedulermiddlewareRabbitMQMiddleware': 999

}

ITEM_PIPELINES = {

'scrapy_rabbitmq_schedulerpipelinesRabbitmqPipeline': 300,

}` </pre>

这里与Scrapy原来的方式稍有不同

构造发送请求/接收RabbitMQ数据的方法名为 _make_request

我们必须重构该方法才可正常运行使用爬虫

该方法起到每次从队列中拿取数据后的解析数据并进行请求的作用

通常我们存放在队列中的一个数据为一个JSON/msgpack格式,里面包含了要请求的URl/该条数据所属ID等多个信息

必须要注意的是如果遇到跳转或你在setting中设置了返回状态码为xxx重新爬取,那么Scrapy会将需要重新爬取的url存放至你的队列中,此时队列中有两种格式的数据

当时被这个问题卡了一小时,网上是没有解决方法的,他生成的数据也是不能使用常规方法进行解码的,这常常令人一头雾水

该组件默认RabbitMQ持久化为True,因此请注意建立通道的时候将设置对齐否则会出现因为设置错误导致无法连接的问题

MQ全程为message queue,即消息队列。是一种跨进程、异步通信机制、用于上下游传递消息。RabbitMQ是由Erlang语言开发,基于 AMQP 协议(Advanced Message Queuing Protocol 高级消息队列协议)实现的消息队列,它是一种应用程序之间的通信方法,消息队列在实际开发应用中有着非常广泛的使用。下面主要介绍RabbitMQ的基础、架构以及在开发过程中遇到的一些常用问题,还有在面试过程中一些长问的问题。

RabbitMQ官网 : >

这次我使用的是RabbitTemplate

为什么Template不需要定义configuration文件来接收yml文件的参数?

这是个常识问题,我这里做个记录。。。

我都能忘记昨天吃了东西的,好在我喜欢做笔记。。。

我们的demo都是基于RabbitTemplate来写。。。

通过枚举ExchangeEnum、QueueEnum、BindingEnum动态维护和创建

1初始化交换机

2初始化队列

3交换机和队列绑定

1定义队列

2定义交换机

3交换机和队列绑定

4不定义超时队列的@RabbitListener,只定义超时接收队列的@RabbitListener

测试:

1AsyncRabbitTemplate定义

2测试

3监听方法

1消息回退:

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

2拒绝消息

void basicReject(long deliveryTag, boolean requeue) throws IOException;

3确认ack

void basicAck(long deliveryTag, boolean multiple) throws IOException;

4创建一个队列

5启动一个消费者,并返回服务端生成的消费者标识

6取消消费者订阅

7主动拉取队列中的一条消息

1队列参数

2消息参数

1、没有正确设置交换机和队列:在使用RabbitMQ时,需要先创建交换机和队列,并将它们绑定到一起。如果您没有正确设置交换机和队列,可能会导致消息无法正确发送到队列中。

2、没有正确配置生产者和消费者:在使用RabbitMQ时,需要正确配置生产者和消费者。如果您的生产者和消费者没有正确配置,可能会导致消息无法正确发送到队列中。

3、没有正确配置RabbitMQ服务:如果您没有正确配置RabbitMQ服务,可能会导致消息无法正确发送到队列中。您需要检查RabbitMQ服务的配置是否正确,并确保服务已经正确启动。

生产者先将消息投递一个叫队列的容器中,然后再从这个容器中取出消息,最后再转发给消费者。

消息队列是 Microsoft 的消息处理技术,它在任何安装 Microsoft Windows 的计算机组合中,为任何应用程序提供消息处理和消息队列功能,无论这些计算机是否在同一个网络上或者是否同时联机。

消息队列网络是能够相互间来回发送消息的任何一组计算机。网络中的不同计算机在确保消息顺利处理的过程中扮演不同的角色。它们中有些提供路由信息以确定如何发送消息,有些保存整个网络的重要信息,而有些只是发送和接收消息。

消息队列的类型介绍:

消息队列目前主要有两种类型:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息。包括消息队列键值、用户ID、组ID、消息队列中消息数目等等。

消息队列就是一个消息的链表,可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。

上篇我们说到了消息队列RabbitMQ的模式概念,那么这里将会针对模式使用SpringBoot联合RabbitMQ做一个案例,实现消息的生产和消费。

这一篇也是这个主题的最后一篇了,建议配合着看。助于理解。

博主会将Demo工程放在Gitee上,有兴趣的可以拉下来自己试试。

Gitee地址: >

RabbitMQ是一个分布式系统

一、使用rabbitmq时的系统架构图

通过路由键将交换机和队列进行绑定,从而实现消息的发送和接收。

二、rabbitmq基本概念

rabbitmq是AMQP协议的一个开源实现,所以其内部实际上也是AMQP中的基本概念,如下图所示:

1、Message(消息)

消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(传输模式,指出该消息可能需要持久化存储)等。

2、Publisher

消息生产者,也是一个向交换器发布消息的客户端应用程序,就是投递消息的程序。

3、Exchange

交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。消息交换机,它指定消息按什么规则,路由到哪个队列。

4、Routing Key

路由关键字,exchange根据这个关键字进行消息投递。

5、Binding(绑定)

用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

它的作用就是把exchange和queue按照路由规则绑定起来。

绑定其实就是关联了exchange和queue,或者这么说:queue对exchange的内容感兴趣,exchange要把它的Message deliver到queue。

6、Queue(消息队列)

消息的载体,每个消息都会被投到一个或多个队列,等待消费者连接到这个队列将其取走。它是消息的容器,也是消息的终点。

7、Connection

网络连接,例如一个TCP连接。

8、Channel(信道,通道)

消息通道,在客户端的每个连接里,可建立多个channel。

多路复用连接中的一条独立双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于 *** 作系统来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念以达到复用一条TCP连接的目的。

9、Consumer

消息消费者,表示一个从消息队列中取得消息的客户端应用程序,就是接受消息的程序。

10、Virtual Host

虚拟主机,表示一批交换器、消息队列和相关对象。一个broker里可以有多个vhost,用作不同用户的权限分离。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的rabbitmq服务器,拥有自己的队列、交换器、绑定和权限机制。

vhost是AMQP概念的基础,必须在连接时指定,rabbitmq默认的vhost是 / 。

11、Broker

表示消息队列服务器实体。它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输。

三、AMQP中的消息路由

生产者把消息发布到Exchange上,消息最终到达队列并被消费者接收,而Binding决定交换器的消息应该发送到那个队列。如下图所示:

四、Exchange类型

Exchange分发消息时根据类型的不同分发策略有区别,目前共有四种类型:direct、fanout、topic、headers。headers匹配AMQP消息的header而不是路由键,此外headers交换器和direct交换器完全一致,但性能差很多,目前几乎用不到。且看direct、fanout、topic这三种类型。

1、direct类型

消息中的路由键routing key如果和Binding中的binding key一致,交换器就将消息发到对应的队列中去。路由键与队列名完全匹配,如果一个队列绑定到交换器要求路由键为“dog”,则只转发routing key标记为“dog”的消息,不会转发“dogpuppy”等等。 它是完全匹配、单传播的模式。

Driect exchange的路由算法非常简单:通过bindingkey的完全匹配,可以用下图来说明:

Exchange和两个队列绑定在一起,Q1的bindingkey是orange,Q2的binding key是black和green。

当Producer publish key是orange时,exchange会把它放到Q1上,如果是black或green就会到Q2上,其余的Message被丢弃。

2、fanout类型

每个发到fanout类型交换器的消息都会分到所有绑定的队列上去。fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。类似于子网广播,每台子网内的主机都获得了一份复制的消息。 fanout类型转发消息是最快的。  如下图所示:

3、topic类型

topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:#和,#匹配0个或多个单词,只能匹配一个单词。

对于Message的routing_key是有限制的,不能是任意的。格式是以点号“”分割的字符表。比如:”stockusdnyse”,“nysevmw”, “quickorangerabbit”。你可以放任意的key在routing_key中,当然最长不能超过255 bytes。对于routing_key,有两个特殊字符#和,#匹配0个或多个单词,只能匹配一个单词。如下图所示:

Producer发送消息时需要设置routing_key,routing_key包含三个单词和两个点号,第一个key描述了celerity(灵巧),第二个是color(色彩),第三个是物种。

在这里我们创建了两个绑定: Q1 的binding key 是”orange“; Q2 是 “rabbit” 和 “lazy#”:Q1感兴趣所有orange颜色的动物;Q2感兴趣所有rabbits和所有的lazy的。

例如:rounting_key 为 “quickorangerabbit”将会发送到Q1和Q2中。rounting_key 为”lazyorangerabbithujjddd”会被投递到Q2中,#匹配0个或多个单词。

五、ConnectionFactory、Connection、Channel

ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。

1、Connection

Connection是Rabbitmq的socket连接,它封装了socket协议相关部分逻辑。

2、ConnectionFactory

ConnectionFactory是connection的制造工厂。

3、Channel

Channel是我们与rabbitmq打交道的最重要的一个接口,大部分的业务 *** 作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

六、任务分发机制

1、Round-robin dispathching 循环分发

RabbbitMQ的分发机制非常适合扩展,而且它是专门为并发程序设计的,如果现在load加重,那么只需要创建更多的Consumer来进行任务处理。

2、Message acknowledgment 消息确认

为了保证数据不被丢失,RabbitMQ支持消息确认机制,为了保证数据能被正确处理而不仅仅是被Consumer收到,这就需要在处理完数据之后发送一个确认ack。

在处理完数据之后发送ack,就是告诉RabbitMQ数据已经被接收并且处理完成,RabbitMQ可以将消息从队列中移除了。如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer,这样就保证在Consumer异常退出情况下数据也不会丢失。

RabbitMQ没有用到超时机制,它仅仅通过Consumer的连接中断来确认该Message并没有被正确处理,一个消费者处理消息的时间再长也不会导致该消息被发送给其他消费者,即RabbitMQ给了Consumer足够长的时间来做数据处理。如果忘记ack,那么当Consumer退出时,Mesage会被重新分发,从而导致队列中的累积的消息越来越多,然后RabbitMQ会占用越来越多的内存。

3、Message durability 消息持久化

如果我们希望即使在rabbitmq服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置成可持久化的(durable),这样就可以保证绝大部分情况下我们的rabbitmq消息不会丢失。但依然解决不了小概率丢失事件的发生(例如rabbitmq服务器已经接收到了生产者的消息,但还没来得及持久化该消息时rabbitmq服务器就断电了)。如果也要将这种小概率事件管理起来就需要使用到事务了。要持久化队列需要在声明时指定durable=True;这里要注意,队列的名字一定要是Broker中不存在的,不然不能改变此队列的任何属性。队列和交换机有一个创建时候指定的标志durable,durable的唯一含义就是让具有这个标志的队列和交换机会在重启之后重新建立。

消息持久化包括3部分

(1)exchange持久化,在声明时指定durable => true

channelExchangeDeclare(ExchangeName,"direct", durable:true, autoDelete:false, arguments:null);//声明消息队列,且为可持久的

(2)queue持久化,在声明时指定durable => true

channelQueueDeclare(QueueName, durable:true, exclusive:false, autoDelete:false, arguments:null);//声明消息队列,且为可持久的

(3)消息持久化,在投递时指定delivery_mode => 2(1是非持久化)。

channelbasicPublish("", queueName, MessagePropertiesPERSISTENT_TEXT_PLAIN, msggetBytes());

如果exchange和queue都是持久化的,那么它们之间的binding也是持久化的;如果exchange和queue两者之间有一个持久化,一个非持久化,则不允许建立绑定。

注意:一旦创建了队列和交换机,就不能修改其标志了。例如创建了一个non-durable的队列,然后想把它改变成durable的,唯一的办法就是删除这个队列然后重新创建。

4、Fair dispath 公平分发

你可能也注意到了,分发机制不是那么优雅,默认状态下,RabbitMQ将第n个Message分发给第n个Consumer。n是取余后的,它不管Consumer是否还有unacked Message,只是按照这个默认的机制进行分发。那么如果有个Consumer工作比较重,那么就会导致有的Consumer基本没事可做,有的Consumer却毫无休息的机会,那么Rabbit是如何处理这种问题呢

通过basicqos方法设置prefetch_count=1,如下设置

channelbasic_qos(prefetch_count=1)

这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message,换句话说,在接收到该Consumer的ack前,它不会将新的Message分发给它。但是这种方法可能会导致queue满。当然,这种情况下你可能需要添加更多的Consumer,或者创建更多的virtualHost来细化你的设计。

5、分发到多个Consumer

Direct Exchange:直接匹配,通过Exchange名称+RountingKey来发送与接收消息。

Fanout Exchange:广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略Routing Key。

Topic Exchange:主题匹配订阅,这里的主题指的是RoutingKey,RoutingKey可以采用通配符,如:或#,RoutingKey命名采用英文句点来分隔多个词,只有消息将队列绑定到该路由器且指定RoutingKey符合匹配规则时才能收到消息。

Headers Exchange:消息头订阅,消息发布前为消息定义一个或多个键值对的消息头,然后消费者接收消息,同时需要定义类似的键值对请求头(如

x-mactch=all或者x_match=any),只有请求头与消息头匹配,才能接收消息,忽略RoutingKey。

默认的exchange:如果用空字符串去声明一个exchange,那么系统就会使用”amqdirect”这个exchange。我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去。如下:

channelBasicPublish("","TaskQueue", properties, bytes);

因为在第一个参数选择了默认的exchange,而我们声明的队列叫TaskQueue,所以默认的,它要新建一个也叫TaskQueue的routingKey,并绑定在默认的exchange上,导致了我们可以在第二个参数routingKey中写TaskQueue,这样它就会找到定义的同名的queue并把消息放进去。

如果有两个接收程序都是用了同一个的queue和相同的routingKey去绑定direct exchange的话,分发的行为是负载均衡的,也就是说第一个是程序1收到,第二个是程序2收到,以此类推。

如果有两个接收程序用了各自的queue,但使用相同的routingKey去绑定direct exchange的话,分发的行为是复制的,即每个程序都会收到这个消息的副本。行为相当于fanout类型的exchange。

多个queue绑定同一个key也是可以的,对于下图的例子,Q1和Q2都绑定了black,对于routing key是black的Message,会被deliver到Q1和Q2,其余的Message都会被丢弃。

七、RPC远程过程调用

MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。 但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。

RabbitMQ中实现RPC的机制如下图所示:

客户端发送请求(消息)时,在消息的属性(MessageProperties ,在AMQP 协议中定义了14种properties ,这些属性会随着消息一起发送)中设置两个值replyTo (一个Queue 名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue 中)和correlationId (此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行或执行失败)。

服务器端收到消息并处理,服务器端处理完消息后,将生成一条应答消息到replyTo 指定的Queue中 ,同时带上correlationId 属性,客户端之前已订阅replyTo 指定的Queue ,从中收到服务器的应答消息后,根据其中的correlationId 属性分析哪条请求被执行了,然后根据执行结果进行后续业务处理。

转发:>

以上就是关于RabbitMQ最佳实践全部的内容,包括:RabbitMQ最佳实践、Scrapy+rabbitMQ消息队列、一文带你了解RabbitMQ到底是个什么鬼!等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/10195321.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-06
下一篇 2023-05-06

发表评论

登录后才能评论

评论列表(0条)

保存