RabbitMQ总结

https://blog.csdn.net/a745233700/article/details/115060109

1.什么是消息队列:

  • 消息队列的优点:

    • 解耦:嫁给你系统按照不同的业务功能拆分出来,消息生产者只管把消息发布到MQ中而不用管谁来取,消费者只管从MQ中取消息而不管是谁发布的。消息生产者和消费者都不知道对方的存在;
    • 异步:主流程只需要完成业务的核心功能;对于业务非核心功能,将消息放入到消息队列之中进行异步处理,减少请求的等待,提高系统的总体性能
    • 限流/削峰:将所有请求都写到消息队列中,消费服务器按照滋生能够处理的请求数从队列中拿到请求,防止请求并发过高将系统搞崩溃
  • 消息队列的缺点:

    • 系统的可用性降低:系统引用的外部依赖越多,越容易挂掉,如果MQ服务器挂掉,那么可能会导致整套系统崩溃。这时就要考虑如何保证消息队列高可用了
    • 系统复杂度提高:加入消息队列之后,需要保证消息没有重复消费,如何处理消息丢失的情况,如何保证消息传递的有序性等问题。
    • 数据一致性的问题:A系统处理完了直接返回成功了,使用者都以为你这请求就成功了,但是问题是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,就会导致不一致了。
  • 消息队列的选型:

    • 中小型软件公司,建议选RabbitMQ:一方面,erlang语言天生具备高并发的特性,而且管理界面用起来十分方便。代码是开源的,而且社区活跃。
    • 大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量,针对rocketMQ,大型软件公司有能力对rokectMQ进行定制化开发。至于kafaka,如果是大数据领域的实时计算,日志采集功能,肯定首选kafaka。

2. RabbitMQ的构造

在这里插入图片描述

  • 生产者Publisher:生产消息,就是投递消息的一方。消息包含两个部分;消息体(payload)和标签(label)
  • 消费者Consumer:消费消息,也就是接收消息的一方。消费者连接到RabbitMQ服务器,并订阅到队列上。消费者消费消息时,只消费消息体,丢弃标签。
  • Broker服务结点:表示消息队列服务实体,一般情况下一个Broker可以看作一个RabbitMQ服务器
  • Queue:消息队列,用来存放消息。一个消息可投入一个或多个队列。多个消费者可以订阅同一队列,这时队列中的消息会背平摊(轮询)给多个消费者进行处理。
  • Exchage:交换器,接受生产者发送的消息,根据路由键将消息路由绑定到队列上
  • Rounting Key:路由关键字,用于指定这个消息的路由规则,需要与交换器类型和绑定键来奶和使用才能最终生效。
  • Connection:网络连接,比如一个TCP连接,用于连接到具体broker
  • Channel:信道,AMQP命令都是在信道中进行的,不管是发布消息,订阅队列还是接收消息,这些动作都是通过信道完成。因为建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接,一个TCP连接可以用多个信道。客户端可以建立多个channel,每个channel表示一个会话任务。
  • Message:消息,由消息头和消息体组成。消息是不透明的,而消息头则是一系列可选属性组成的,这些属性包括routing-key(路由键),priority(相对其他消息的优先权),delivery-mode(指出该消息可能需要持久性存储)等
  • Virtual host:虚拟主机,用于逻辑隔离,表示一批独立的交换器,消息队列和相关对象。一个Virtual host可以拥有若干个Exchage和queue,同一个virtual host不能由同名的Exchage或queue。最重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制。当然从RabbiitMQ的全局角度,vhost可以作为不同权限的隔离手段。

3. Exchange交换器的类型

Exchange分发消息时,根据类型的不同,分发策略有区别,目前共有四种类型:direct,fanout,topic,headers

  • direct:消息中的路由键(RoutingKey)如果和Bingding中的bindingKey完全匹配,交换器就将消息发送到对应的队列中
  • fanout:把所有发送到fanout交换器的消息路由到所有绑定该交换器的队列中,fanout类型转发消息是最快的
  • topic:通过模式匹配的方式对消息进行路由,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上

匹配规则:

  • Routing Key和BindingKey为一个点号’.'分隔的字符串。比如java.xaioka.show
    BindingKey可以使用 * 和 # 用于左模糊匹配: * 匹配一个单词,# 匹配多个或者0个单词
  • headers:不依赖于路由键进行匹配,是根据发送消息内容中的headers属性进行匹配,除此之外headers交换器和direct交换器完全一致,但是性能差很多,目前几乎用不到。

4. 生产者发布消息的过程

  • Producer 先连接到Broker,建立连接Connection,开启一个信道channel
  • Producer声明一个交换器并设置好相关属性
  • Producer声明一个队列并设置好相关属性
  • Producer通过路由键将交换器和队列绑定起来
  • Producer发送消息到Broker,其中包含路由键,交换器等信息
  • 交换器根据接收到的路由键查找匹配的队列
  • 如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者
  • 关闭信道

5. 消费者接收消息过程

  • Consumer先连接到Broker,建立连接Connection,开启一个信道channel
  • 向Broker请求消费相应队列中的消息,接收消息
  • 消费者确认收到的消息,ack
  • RabbitMQ从队列中删除已经确定的消息。
  • 关闭信道

6. 如何保证消息不被重复消费?

  正常情况下,消费者在消费后,会给消息队列发送一个确认,消息队列接收后就知道消息已经被成功消费了,然后就从队列中删除该消息,也就不会将该消息在发送给其他消费者了。不同消息队列发出的确认消息形式不同,RabbitMQ是通过发送一个ACK确认消息。但是因为网络故障,消费者发出的确认并没有传到消息队列,导致消息队列不知道该消息已经被消费,然后再次发送给了其他消费者,从而造成重复消费的情况。
  重复消费问题的解决思路是:保证消息的唯一性,即使多次传输,也不让消息的多次消费带来影响,也就保证消息幂等性;幂等性指一个操作执行任意多次所产生的影响均与第一次执行的影响相同。

  • 改造业务逻辑,使得在重复消费时,也不影响最终的结果。
  • 基于数据库的唯一主键进行约束,消费以后,到数据库中做一个insert操作,如果出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

7.如何保证消息不丢失,进行可靠传输?

对于消息的可靠性传输,每种MQ都要从三个角度来分析:生产者丢数据,消息队列丢数据,消费者丢数据。以RabbitMQ为例:

7.1生产者丢数据

RabbitMQ提供事务机制确认机制两种模式来确保生产者不丢消息

  • 事务机制:
    发送消息前,开启事务,然后发送消息,如果发送过程中出现什么异常,事务就回滚,如果发送成功则提交事务。该方式的缺点是生产者发送消息会同步阻塞,等待发送结果是成功还是失败,导致生产者发送消息的吞吐量下降
  • 确认机制
    生产环境常用的是confirm模式。生产者将信道channel设置成confirm模式,一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID,一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个确认给生产者(包含消息的唯一ID),这样生产者就知道消息已经正确到达目的队列了。如果RabbitMQ没能处理该消息,也会发送一个Nack消息给你,这时就可以进行重试操作。
    confirm模式最大的好处在于它是异步的,一旦发布消息,生产者就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认后,生产者便可以通过回调的方法来处理该确认消息。

7.2 消息队列丢数据

  处理消息队列丢数据的情况,一般是开启持久化硬盘。持久化配置可以和生产者的confirm机制配合使用,在消息持久化磁盘后,再给生产者发送一个ack信号,这样的话,如果消息持久化磁盘之前,即使RabbitMQ挂掉了,生产者也会因为收不到ack信号而再次重发消息。

7.3 消费者丢失数据

  消费者丢失数据一般是因为采用了自动确认消息模式/该模式下,虽然消息还在处理中,但是消费者会自动发送一个确认,通知RabbitMQ已经收到消息,这时RabbitMQ就会离即将消息删除。这种情况下,如果消费者出现异常而未能处理消息,那就会丢失该信息。
  解决方案就是次啊用手动确认消息,设置autoAck=False,等到消息背真正消费后,再手动发送一个确认信号,即使中途消息没处理完,但是服务器宕机了,那么RabbitMQ就收不到发的ack,然后RabbitMQ就将这条消息重新分配给其他的消费者去处理
  但是RabbitMQ并没有使用超时机制,RabbitMQ仅通过与消费者的连接来确认是否需要重新发送消息,也就是说,只要连接不中断,RabbitMQ会给消费者足够长的时间来处理消息。另外,采用手动确认消息的方式,我们也需要考虑一下几种特殊情况:

  • 如果消费者收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被消费,然后重新分给下一个订阅者,所以存在消息重复消费的隐患
  • 如果消费者收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多消息

8.如果保证消息的有序性

   针对保证消息有序性的问题,解决方法就是保证生产者入队的顺序就是有序的,出队后的顺序消费则交给消费者去保证

  • 方法1:拆分queue,使得一个queue只对应一个消费者。由于MQ一般都能保证内部队列是先进先出的,所以把需要保持先后顺序的一组消息使用某种算法都分配到同一个消息队列中。然后只用一个消费者单线程去消费该队列,这样就能保证消费者是按照顺序进行消费的了。但是消费者的吞吐量会出现瓶颈。如果多个消费者同时消费一个队列,还是可能会出现顺序错乱的情况,这就相当于多线程消费了
  • 对于多线程消费同一个队列的情况,可以使用重试机制:比如有一个微博业务场景的操作,发微博,写评论,是你出微博,这三个异步操作。如果一个消费者先执行了写评论的操作,但是这时微博都还没发,写评论就一定是失败,等一段时间。等另一个消费者,先执行发微博的操作后,再执行,就可以成功。

9. 如何处理消息堆积?

场景题:几千万条数据在MQ里积压了七八个小时

9.1 出现问题的原因:

  消息堆积往往是生产者的生产速度与消费速度不匹配导致的。有可能就是消费者消费能力弱,渐渐地消息就积压了,也可能是因为消息消费失败反复重试造成的,也可能是消费端出了问题,导致不消费了或者消费及其慢。比如,消费端每次消费之后要写sql,结果mysql挂了,消费端hang住了不动了,或者消费者本地依赖的一个东西挂了,导致消费者挂了。

9.2 临时扩容

  • 先修复consumer的问题,确保其恢复消费速度,然后将现有的consumer都停掉
  • 临时创建原先N倍数量的queue,然后写一个临时分发数据的消费者程序,将该程序部署上去消费者队列中积压的数据;消费者之后不做任何耗时处理,直接均与轮询写入临时建立好的N倍数量的queue中
  • 借着,临时征用N倍的机器来部署consumer,每个consumer消费一个临时queue的数据
  • 等快速消费完积压数据之后,恢复原来部署架构,重新用consumer机器消费信息
    这种做法相当于临时将queue资源和consumer资源扩大N倍,以正常N倍速度消费

9.3 恢复队列中丢失的数据

  如果使用的是RabbitMQ,并且设置了过期时间,消息在queue里积压超过一定时间会被rabbitmq清理掉,导致数据丢失。这种情况下,实际上队列中没有什么消息挤压,而实丢了大量的消息。所以就不能说增加consumer消费积压数据了,这种情况可以采取“批量重导”的方案来进行解决。在流量低峰期,写一个程序,手动去查询丢失的那部分数据,然后将消息重新发送到mq里面,把丢失的数据补回来

9.4 MQ长时间未处理导致MQ写满的情况如何处理:

  如果消息积压在MQ里,并且长时间都没处理掉,导致MQ都快写满了,这种情况肯定是临时扩容方案执行太慢,这种时候只好采用“丢弃+批量重导”的方式来解决了。首先,临时写个程序,连接到mq里面消费数据,消费一个丢掉一个,快速消费掉积压的消息,降低MQ的压力,然后在流量低峰期时去手动查询重导丢失的这部分数据

10. 如何保证消息队列高可用?

RabbitMQ是基于主从(非分布式)做高可用性的,RabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式

单机模式

  一般没人生产用单机模式

普通集群模式

  普通集群模式用于提高系统的吞吐量,通过添加结点来线性扩展消息队列的吞吐量。也就是在多台机器上启动多个RabbitMQ实例,而队列queue的消息只会存放在其中一个RabbitMQ实例上,但是每个实例都同步queue的元数据(元数据是queue的一些配置信息,通过元数据,可以找到queue所在的实例)。消费的时候,如果连接到另外的实例,那么该实例就会从数据实际所在的实例上的queue拉取消息过来,就是说让集群中多个结点来服务某个queue的读写操作。

  • 缺点:无高可用,queue所在的结点宕机了,其他实例就无法从那个实例拉去数据;RabbitMQ内部也会产生大量的数据传输

镜像队列集群模式

  镜像对垒集群是RabbitMQ真正的高可用模式,集群中一般会包含一个主节点master和若干个从节点slave,如果master由于某种原因失效,那么按照slave加入的时间排序,“资历最老”的slave会被提升为新的master
  镜像队列下,所有的消息只会向master发送,再由master将命令的执行结果广播给slave,所以master和salve结点的状态是相同的。比如,每次写消息到queue时,master会自动将消息同步到各个slave实例的queue;如果消费者与slave建立连接并进行订阅消费,其实质上也是从master上获取消息,只不过看似从slave上消费而已,比如消费者与slave建立了TCP连接并执行Bssic.get操作,那么也是由slave将Basic.Get请求发往master,再由master准备好数据返回给slave,最后由slave投递给消费者

  • 缺点:性能开销大,消息需要同步到所有机器上,导致网络带宽压力和消耗很重;非分布式,没有扩展性,如果 queue 的数据量大到这个机器上的容量无法容纳了,此时该方案就会出现问题了

其他

  • 交换器无法根据自身类型和路由键找到符合条件队列时,有哪些处理方式:设置mandatory = true,代表返回消息给生产者;设置mandatory = false,代表直接丢弃
  • 消费者得到消息队列中的数据的方式:push 和 pull
  • 消息基于什么传输:由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。所以RabbitMQ 使用信道 channel 的方式来传输数据,信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。
  • 死信队列DLX: DLX也是一个正常的Exchange,和一般的Exchange没有任何区别。能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列出现死信(dead message,就是没有任何消费者消费)的时候,RabbitMQ就会自动将这条消息重新发布到Exchange上去,进而被路由到另一个队列。可以监听这个队列中的消息作相应的处理。消息变为死信的几种情况:
    • 消息被拒绝(basic.reject/basic.nack)同时 requeue=false(不重回队列)
    • TTL 过期
    • 队列达到最大长度,无法再添加
  • 延迟队列:存储对应的延迟消息,当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。在 RabbitMQ 中并不存在延迟队列,但我们可以通过设置消息的过期时间和死信队列来实现延迟队列,消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。
  • 优先级队列:优先级高的队列会先被消费,可以通过 x-max-priority 参数来实现。但是当消费速度大于生产速度且 Broker 没有堆积的情况下,优先级显得没有意义。
  • RabbitMQ 要求集群中至少有一个磁盘节点,其他节点可以是内存节点,当节点加入或离开集群时,必须要将该变更通知到至少一个磁盘节点。如果只有一个磁盘节点,刚好又是该节点崩溃了,那么集群可以继续路由消息,但不能创建队列、创建交换器、创建绑定、添加用户、更改权限、添加或删除集群节点。也就是说集群中的唯一磁盘节点崩溃的话,集群仍然可以运行,但直到该节点恢复前,无法更改任何东西。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值