MQ可以用在哪些地方,解决什么问题?

MQ
kafka是以吞吐量高而闻名,不过其数据稳定性一般,而且无法保证消息有序性。

阿里巴巴的RocketMQ基于Kafka的原理,利用Java代码打造,弥补了Kafka的缺点,继承了其高吞吐的优势,其客户端目前以Java为主。

RabbitMQ基于面向并发的语言Erlang开发,吞吐量不如Kafka,但是消息可靠性较好。也能有效的保证消息的有序性。因为Erlang的原因,集群搭建比较方便。支持多种协议,并且有各种语言的客户端,比较灵活。

相关资料:

这个问题其实时问3种MQ的差别,先看一张图:

在这里插入图片描述

ActiveMQ现在已经很少使用,社区不太活跃,放弃。

RabbitMQ并发能力强、消息延时低、高可用、管理界面丰富,并且最重要的是:社区非常活跃,出现BUG都能及时解决。

Kafka和RocketMQ的特点都是高吞吐量,但是kafka消息可靠性比较一般,而且消息不保证有序性。RocketMQ弥补了Kafka的缺点,不过是阿里开源,社区不太活跃,文档也不够丰富。

RabbitMQ可以 干嘛

1)解耦合

例如系统A执行完业务,系统B需要得到系统A的业务结果,此时可以系统A中调用系统B(系统A中耦合了系统B的业务)。

此时如果系统C、系统D都有类似需求,那么系统A的业务逻辑还要继续修改,违反了开闭原则。

在这里插入图片描述

此时,可以利用MQ来解耦,让商品微服务发送消息通知,而相关的其它系统监听MQ即可:
在这里插入图片描述

2)流量削峰

数据库的并发能力有限,往往称为业务执行的性能瓶颈。

例如我们的服务只能支持500的并发,然而又每秒1000甚至更高的服务流量涌入,服务肯定会崩溃的。

在这里插入图片描述

此时,利用MQ来作为缓冲,就像大坝一样,高并发流量涌入,先放到MQ中缓存起来,后续系统再慢慢取出并处理即可:

在这里插入图片描述

3)异步调用

如果一个业务执行中,需要调用多个其它服务,业务链路很长,同步调用的用时就时多个服务执行的总耗时,如图:

在这里插入图片描述

但是,我们如果再B系统执行完成后,利用MQ通知系统C和系统D去完成,直接返回结果给用户,就可以减少业务耗时。这样就把同步阻塞调用,变成了异步调用:

在这里插入图片描述

4)延迟队列

例如定时清理订单的需求,我们在下单完成后,如果用户超过半小时未支付,需要关闭订单。此时我们就需要发送延迟消息,即:发送消息半小时后,消费者才能收到消息,这就是延迟队列。

RabbitMQ的死信队列可以实现延迟队列效果,如图:

在这里插入图片描述

RocketMQ也可以实现延迟队列,但是RocketMQ的延迟队列的时间只能是固定时间间隔。

如何保证RabbitMQ的高可用

RabbitMQ

RabbitMQ底层基于Erlang语言,对分布式支持较好。并且官方也给出了搭建镜像机器的方式,可以把队列及其中的数据同步到镜像节点中,当队列所在节点故障时,镜像队列可以继续提供服务。

另外,MQ数据可以持久化,当节点恢复时,可以恢复数据。

RocketMQ

RocketMQ中的核心组件是NameServer和Broker,这两部分都可以建立主从集群,保证高可用:在这里插入图片描述

如何保证RabbitMQ的消息可靠性,防止消息丢失?

我们针对MQ消息丢失的几种不同情况,采用不同的应对方案:

  • 生产者发送消息时可能因为网络问题导致消息丢失:
    • 利用RabbitMQ提供的publisher confirm机制,参考文档
      • 生产者发送消息后,可以编写成功回调函数或失败超时回调函数
      • RabbitMQ接收消息成功并持久化会调用成功回调函数,通知消息的发送者
      • RabbitMQ接收消息时出现异常会调用失败回调函数,通知消息的发送者
      • 消息超时未发送成功也会调用失败回调函数
  • MQ宕机导致丢失消息:
    • 消息持久化,RabbitMQ会将消息持久化到磁盘,宕机重启可以恢复消息
    • 镜像集群,主从备份,避免引宕机导致消息丢失
  • 消费者丢失消息:避免引消费者异常或宕机导致消息丢失
    • 消费者的确认机制,在处理消息结束后,手动Acknowledge回执给MQ
    • MQ未接受到Acknowledge会认为消费失败,消息会保留在MQ

rabbitmq中消息消费后自动删除,不会永久保留,无法实现消息回溯。

如何防止MQ消息的重复消费?

消息重复消费产生的原因:

  • 因为网络故障,导致生产者确认机制失败,生产者重发消息
  • 因为网络故障,导致生产者确认机制失败,MQ重新投递消息

解决思路:业务处理时,保证处理消息接口的幂等性。

  • 能根据具体的业务或状态来确定的,在消费端通过业务判断是否执行过,例如新增订单,看看订单ID是否已经存在
  • 对于无法通过业务判断的,我们可以为每一条消息设置全局唯一id,保存到数据库消息表。消息处理前对ID进行判断即可

如何解决MQ的消息堆积问题?

RabbitMQ支持多消费者绑定同一队列,消息Broker会把消息轮询的发送给每一个消费者。通过同一个队列多消费者监听,实现消息的争抢,加快消息消费速度。

RabbitMQ也可以做集群,集群数据会分片效果,从而能堆积更多消息。

备选方案:也可以给单个消费者接收消息后放入队列,交给线程池去处理。

如何保证MQ消息的有序性?

某个业务发出了3条消息,要求这3条消息按照发送时的顺序执行。

  • 业务对并发要求不高:
    • 保证消息发送时有序同步发送
    • 保证消息发送被同一个队列接收,MQ本身是先进先出,保证消息有序
    • 保证一个队列只有一个消费者,避免多个消费者争抢导致顺序混乱
  • 业务同时对并发要求较高:
    • 满足上述第一个场景的条件
    • 可以有多个队列
    • 有时序要求的一组消息,通过hash方式分派到一个固定队列

如何利用RabbitMQ实现延迟队列

RabbitMQ中有一个死信队列设定:我们可以给一个队列设置过期时间,或者发送消息时给消息设置过期时间。过期的消息称为死信,队列会把死信转发给提前设置的死信交换机,而与死信交换机绑定的队列就可以拿到这些消息。

因为发送消息超过一定时间(过期)后,才会被队列拿到,这样就实现了延迟队列效果。

实现起来非常简单,不过也有一些缺陷:

  • 如果延迟消息过多,可能导致MQ的消息堆积过多
  • MQ消息无法删除,因此不能撤销延迟消息。

如果对上述问题有要求,可以利用Redis来实现延迟队列。

相关资料:

参考官方网站:https://www.rabbitmq.com/dlx.html

首先来看死信的概念。

死信

死信的英文是(Dead Letter),满足下列条件的消息被称为死信:

  • 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false。意思就是这个消息没有消费者需要了。
  • 消息是一个过期消息(TTL到期),到期可以是消息本身超时或者队列的TTL超时
  • 消息的长度超过了其被投递的队列最大限制

要实现延迟队列,我们肯定需要人为控制一个消息变为死信,因此我们一般采用上述的第二种方式:让一个消息在一段时间后过期,这种过期可以通过两种策略实现:

  • 队列TTL:通过x-message-ttl属性给消息所在队列设置TTL(Time To Live),当队列中的消息存在时间超过TTL后就自动成为死信
  • 消息TTL:消息的发送者在发送消息时,设置消息TTL属性。消息到达队列,TTL到期后成为死信、
  • 如果一个消息具有TTL,同时所在队列也具备TTL,时间长度较小的会生效

由上面的概念可以知道,一个消息是不是死信,最终是由消息所在的队列来判断和处理的。当一个消息被判定为死信,它所在的队列会做怎样的处理呢?

队列会把死信交给提前指定的死信交换机(Dead Letter Exchange)

如图:
在这里插入图片描述

死信交换机Dead Letter Exchanges

**死信交换机(Dead Letter Exchange)**其实就是一个普通交换机,也具备以前学习的交换机的所有特征,例如可以设置交换机类型为:topic、direct等。它负责把消息根据routing key转发给绑定的队列。

那什么样的交换机才可以叫死信交换机?需要队列在声明的时候,通过x-dead-letter-exchange属性指定一个交换机,被指定的交换机就是死信交换机(Dead Letter Exchange)。同时队列还可以指定一个x-dead-letter-routing-key(死信路由)作为死信的routing_key,死信交换机转发消息时会根据这个routing_key来转发消息。

死信交换机接收到消息以后,会根据消息的routing_key再次转发消息到绑定的队列,如果队列绑定到死信交换机时,会根据队列指定的x-dead-letter-routing-key来转发,如果队列没有绑定,则会根据消息来源时指定的routing_key来转发。

例如:现在publisher发送消息时指定routing_keyfoo,队列绑定死信交换机时指定了死信路由为:bar,则死信交换机转发时,会使用bar作为routing_key,如图:

在这里插入图片描述

现在publisher发送消息时指定routing_keyfoo,队列绑定死信交换机时没有指定**死信路由,则死信交换机转发时,会使用foo作为routing_key,如图:

在这里插入图片描述

现在,如果我们发送一个routingKey为foo的消息到达设置了过期时间为30秒的队列(图中的MessageQueue),30秒后消息过期,就会转发到死信交换机,然后就会发送到Queue1这个队列,我们的任务执行者监听Queue1,即可实现延迟队列了。

示例

接下来,我们通过示例来展示下死信队列,如图:
在这里插入图片描述

1)创建交换机

打开RabbitMQ的管理界面,然后先创建两个交换机:

  • normal.topic:一个普通的topic类型的交换机
  • dead.topic:一个普通topic类型的交换机,但是作为死信交换机来用
    在这里插入图片描述
    在这里插入图片描述
2)创建队列

然后创建两个队列:

  • dead.order.queue:死信队列,设置过期时间为20秒,
    • normal.topic交换机绑定,接收消息,routing_key为 order.evict
    • 指定x-dead-letter-exchangedead.topic这个死信交换机
    • 指定x-message-ttl设置消息过期时间
  • evict.order.queue:普通任务队列,接收死信交换机转发过来的消息,将来推送给消费者
    • dead.topic交换机绑定,接收消息,routing_key为 order.evict

死信队列:在这里插入图片描述
普通任务队列:在这里插入图片描述
最终:在这里插入图片描述

3)绑定普通队列与交换机

进入交换机界面,点击要绑定的交换机,例如:normal.topic在这里插入图片描述
在点开的界面填写要绑定的队列及routing_key:在这里插入图片描述
然后还要绑定evict.order.queuedead.topic这个交换机:在这里插入图片描述

4)测试发送消息

现在,向normal.topic交换机发送消息,指定routing_key为:order.evict在这里插入图片描述
可以看到dead.order.queue中已经有消息了:在这里插入图片描述
然后等待20秒后,看到消息到了evict.order.queue在这里插入图片描述

优缺点

RabbitMQ实现延迟队列的优缺点:

优点:

  • 实现简单
  • 可持久化
  • 高可用集群
  • 性能强
  • 实时性好

缺点:

  • 无法删除消息
  • 如果是时间跨度非常大并且频率高的任务,不太适合
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值