文章目录
- 1MQ如何保证消息幂等性?
- 2消息中间件的应用场景
- 3什么是消息队列?什么是RabbitMQ?
- 4为什么要使用消息队列?消息队列的优点
- 5消息队列的缺点,消息队列带来的问题
- 6消息过期时间,队列过期时间
- 7死信队列(DLX),变成死信的情况
- 8 RabbitMQ延时队列,优先级队列
- 9 RabbitMQ的持久化
- 10消息的有序性?
- 11惰性队列
- 12 RabbitMQ组件
- 13 RabbitMQ.工作模型
- 14 RabbitMQ:消费模式包括哪些?
- 15多个消费者监听一个队列,消息如何分发?
- 16 RabbitMQ如何保证消息的可靠性?
- 17如何避免消息重复投递或重复消费?
- 18 RabbitMQ消息的状态
- 19死信完成延迟队列
- 20 RabbitMQ集群
- 21 RabbitMQ如何保证消息的顺序性?
- 22消息积压在消息队列中会导致什么结果?产生的原因是?如何解决?
- 23 Kafka和RabbitMQ的对比
- 24 RabbitMQ的优缺点,Kafka的优缺点
1MQ如何保证消息幂等性?
- 乐观锁机制
- 基于Redis的原子操作
- 增加消息状态表。通俗来说就是一个账本,用来记录消息的处理状态,
每次处理消息之前, 都去状态表中查询一次,如果已经有相同的消息存在,那么不处理,可以防止重复发送
2消息中间件的应用场景
开发中消息队列通常有如下应用场景:
1、任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
2、应用程序解耦合
MQ 相当于一个中介,生产方通过 MQ 与消费方交互,它将应用程序进行解耦合。
3什么是消息队列?什么是RabbitMQ?
消息队列
使用队列来通信的组件,把要传输的消息放在队列中。
RabbitMQ
它是实现了高级消息队列协议(AMQP)的消息中间件。
4为什么要使用消息队列?消息队列的优点
1 - 系统解耦:解耦消息生产者和消费者之间的关系
2 - 异步调用:用户调用接口时,由于接口之间调用导致用时时间比较久,用户体验不好。
调用接口后将消息放入到MQ后就返回,用户体验好,最终一致性由MQ来保证
3 - 流量削峰:减少高峰时期对服务器压力,先把请求放到MQ中,系统根据实际能处理的并发量来消费请求
5消息队列的缺点,消息队列带来的问题
1 - 系统可用性降低。如果MQ挂了,整个系统就不能服务了
2 - 系统复杂性提高。消息丢失、消息重复消费、消息重复发送、消息顺序错乱等问题
3 - 一致性问题。将消息放到MQ后就返回给用户成功的信息,但是其他系统消费消息时,
若某个系统失败了,导致数据不一致
6消息过期时间,队列过期时间
消息过期时间
目前有两种方式设置消息的过期时间,一是通过队列属性设置,二是通过消息单独设置。
如果两种方式同时使用,以最小值为准。
队列过期时间
通过 channel queueDeclare 方法中的 expires 参数可以控制队列被自动删除前处于未使用状态的时间
。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并且在过期时间段内也未调用过Basic Get命令
7死信队列(DLX),变成死信的情况
DLX ,全称为 Dead-Letter-Exchange ,可以称之为死信交换器。
当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,
这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。
消息变成死信一般由于以下几种情况:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
8 RabbitMQ延时队列,优先级队列
延时队列
在rabbitmq中可通过过期时间(TTL)和死信队列(DLX)实现延时队列
优先级队列
可设置队列支持的最大优先级,然后发送消息时设置消息的优先级。
但是要注意这种情况只在消费者能力小于生产者,有消息堆积时有效。
9 RabbitMQ的持久化
RabbitMQ的持久化分为3个部分:交换器的持久化、队列的持久化和消息的持久化。
但是如果将所有消息持久化,将会严重影响rabbitmq的性能。
即使将交换器、队列和消息都设置了持久化,也不能保证消息100%不丢失。
- 从消费者端,防止消费者收到消息还没来得及处理就宕机的情况,需要将autoAck设置为false
- 从生产者断,防止发送到rabbitmq后还没来的及落盘rabbitmq就宕机的情况,
可以在生产者端引入事务机制或者发送方确认机制来保证消息己经正确地发送并存储RabbitMQ 中
(前提还要保证在调用 channel.basicPublish 方法的时候交换器能够将消息正确路由到相应的队列之中)或者引入镜像队列来保证高可用
10消息的有序性?
资料上说rabbitmq可以保证消息的有序,其实是不严谨的。
在不使用任何 RabbitM 高级特性 ,也没有消息丢失、网络故障之类异常的情况发生,并且只有一个消费者的情况下
如果有多个生产者同时发送消息,无法确定消息到达 Broker 的前后顺序,也就无法验证消息的顺序性。
需要业务方使用 RabbitMQ 之后做进一步的处理,比如在消息体内添加全局有序标识(类似SequenceID) 来实现。
11惰性队列
RabbitMQ 3.6.0 版本开始引入了惰性队列 lazy Queue 的概念。
惰性队列会尽可能地将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,
它的一个重要的设计目标是能够支持更长的队列即支持更多的消息存储
。当消费者由于各种各样的原因(比如消费者下线、宕机或者由于维护而关闭等〉
致使长时间内不能消费消息而造成堆积时惰性队列就很有必要了。
12 RabbitMQ组件
- Broker:一个Broker可以看做一个RabbitMQ服务器
- Connection:实际的连接(TCP连接)
- Channel:虚拟连接。一个Connection上可以有多个Channel(信道),信道没有数量限制,消息在信道上传输。
- Message
- Virtual host:虚拟broker。其内部含有独立的queue、exchange、binding,以及独立的权限系统
- Exchange:交换机。生产者将消息发送到交换机,由交换机将消息路由到一个或多个队列中。路由不到时,返回给生产者或直接丢弃(mandatory 为 true,返回消息给生产者;为 false 丢弃消息)
- Queue:存储消息的数据结构。可以设置长度,处于ready状态的消息会被计数,不统计处于unack的消息。消息重新入队会处于队头。多个消费者可以订阅同一个队列,队列中的消息会平摊给各个消费者处理
13 RabbitMQ.工作模型
- 简单队列(Simple Queue):生产者将消息投递到队列里,消息者从队列里取消息
- 工作队列(Worker Queue):一个生产者,一个队列,多个消费者,用于消息消费耗时的场景
- 发布订阅(fanout)
- Direct:消息根据路由键投递相应的队列中
- Topic:消息根据路由键和绑定键的匹配,投递到相应的队列中。如果多个键匹配成功,且目标队列是同一个队列,队列只会收到一条消息
- Headers:用键值对做匹配,匹配方式有all和any,不常使用
其中3-6是交换机的四种工作模式(四种交换机类型)
14 RabbitMQ:消费模式包括哪些?
推送(push)
- 通过
channel.basicConsume
将 channel(信道)设置为推模式,消息可用时自动将消息推送给消费者 - 消费者需要设置缓冲区缓存消息
- 实时性好(长连接)
拉取(pull)
- 使用
channel.basicGet
拉消息 - 轮询模型,消费者发送 get 请求获取消息,如果队列中没有消息,则获得空的回复
- 需要时才去拉取消息,实时性差,耗费资源(短连接)
Qos(质量服务)
消息发送给消费者后,默认是自动确认,如果消费者未能消费成功,则消息丢失。
通过显式确认可以保证只有当消息处理完成并收到Ack后才从队列中删除。
但是存在的问题是:
1)消息太多全部传给消费者,可能造成消费者内存爆满;
2)消息处理慢时,想让别的消费者一起处理,但是这些消息都被原来的消费者接收了,这些消息不会再发送给新添加的消费者
Qos可以解决上述问题,需要开启消息的显式确认,设置每次传输给消费者的消息条数为n,
消费者处理完n条消息后再获取n条消息进行处理;而新增消费者时,消息可以立即发送给新的消费者。
15多个消费者监听一个队列,消息如何分发?
默认轮训策略
公平分发(QoS):给空闲的消费者发送更多的消息(当消费者有x条消息没有响应时,不再给该消费者发消息)
16 RabbitMQ如何保证消息的可靠性?
生产者
confirm机制:
- 生产者生产的消息带有唯一 ID
- 消息被投递到目标队列后,发送Ack消息(包含消息的唯一ID)给生产者
- 有可能因为网络问题导致Ack消息无法发送到生产者,那么生产者在等待超时后,
会重传消息;或者RabbitMQ内部错误导致消息丢失,则发送nack消息 - 生产者收到Ack消息后,认为消息已经投递成功
队列自身不弄丢消息
队列开启持久化,消息的diliveryMode = 2
队列如何将消息可靠投递到消费者?
手动确认: - 队列将消息push给消费者(或消费者来pull消息)
- 消费者得到消息并做完业务逻辑
- 消费者发送Ack消息给队列 ,通知队列删除该消息(队列会一直等待直到得到ack消息,队列通过消费者的连接是否中断来确认是否需要重新发送消息,只要连接不中断,消费者有足够长的时间来处理消息,保证数据的最终一致性)
- 队列将已消费的消息删除
17如何避免消息重复投递或重复消费?
重复投递的原因:等待超时后,需要重试。
避免重复投递:消息生产时,生产者发送的消息携带一个Message ID
(全局唯一ID),
作为去重和幂等的依据,避免重复的消息进入队列
重复消费的原因:消费者接收消息后,在确认之前断开了连接或者取消订阅,
消息会被重新分发给下一个订阅的消费者。
避免重复消费:消息消费时,要求消息体中必须要有一个全局唯一ID,
作为去重和幂等的依据,避免同一条消息被重复消费
18 RabbitMQ消息的状态
alpha
:消息内容(包括消息体、属性和headers) 和消息索引都存储在内存中beta
:消息内容保存在磁盘中,消息索引保存在内存中gamma
:消息内容保存在磁盘中,消息索引在磁盘和内存中都有delta
:消息内容和索引都在磁盘中
19死信完成延迟队列
存储延迟消息,消息被发送后不想让消费者立即拿到消息,要等待特定时间后才能拿到消息。
实现方式
3.6.x之前,死信队列+TTL过期时间可以实现延迟队列。
3.6.x开始,延迟队列插件
应用场景
x天自动确认收货、自动默认评价。
12306购票支付确认页面,如果30分钟没有付款将自动取消订单。
20 RabbitMQ集群
-
普通集群
每台机器上启动一个RabbitMQ实例,而创建的queue只放在一个实例上,其他实例同步queue的元数据。
消费时如果连接到了另一个实例上,则实例会从queue所在的实例上拉取数据
多个实例服务一个queue镜像集群
RabbitMQ的元数据和queue里的消息都会存在于多个实例上,
每次消息进入队列,会自动把消息同步到多个实例的队列中
分为一个master和多个slave。所有的操作最终都会到master上操作- 生产者可任意选择一个节点连接,如果该节点不是master,则转发给master。
master向slave发送消息,收到半数以上回复后本地提交,再让slave提交 - 消费者可任意选择一个节点连接,如果该节点不是master,则转发给master。
消费者消费后进行 ack 确认,master收到ack后删除,并让slave删除。 - 如果master掉线,自动选出一个节点(slave中消息队列最长的节点)作为新的master
- 生产者可任意选择一个节点连接,如果该节点不是master,则转发给master。
21 RabbitMQ如何保证消息的顺序性?
- 只有一个消费者,可以保证顺序
- 多个队列,每个队列对应一个消费者,同一个用户的操作hash到同一个队列上
- 每个消息有一个全局ID,同时去关联一个parentMsgId,在前一条消息未消费时不处理下一条消息
22消息积压在消息队列中会导致什么结果?产生的原因是?如何解决?
- 原因:消费者消费速度慢,或者出现了问题
导致的结果:1)磁盘空间满了;2)海量消息堆积,消费者需要很长时间消费
解决办法:- 磁盘空间满的情况:在其他机器上建立临时的消息队列,
再写一个临时的消费者,把积压的消息放到临时队列里去 - 海量消息堆积的情况:修复消费者问题,停掉现有的消费者,
临时建立10倍的消息队列,再用一个临时的消费者将消息分发到临时消息队列中,
临时征用10倍的机器部署消费者。等积压消息消费完成后,再恢复成之前的架构
- 磁盘空间满的情况:在其他机器上建立临时的消息队列,
23 Kafka和RabbitMQ的对比
保证消息可靠性:
- Kafka
1)使用acks=all,Leader收到消息后,等待所有ISR列表的Follower返回后再发送ack给生产者(分区副本数和ISR最少副本数配置成大于1)
2)生产者使用同步发送消息(默认是异步,多个请求先放在缓冲区,再合并发送)
3)消费者使用手动提交offset,从而保证消息至少消费一次 - RabbitMQ:
24 RabbitMQ的优缺点,Kafka的优缺点
RabbitMQ的优缺点:
优点
- 延迟很低(微秒级)
- 可靠性:使用一些机制来保证可靠性, 如持久化、传输确认及发布确认等。
- 灵活的路由:对于典型的路由功能,RabbitMQ己经提供了一些内置的交换器来实现。
针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。 - 管理界面 : RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
- 高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
- 多种协议:RabbitMQ除了原生支持AMQP协议,还支持STOMP、MQTT等多种消息中间件协议。
- 支持多种语言:如Java、Python、Ruby、PHP、C#、JavaScript等。
- 插件机制 : RabbitMQ提供了许多插件,以实现从多方面进行扩展,也可以自定义插件。
缺点 - erlang开发,难以维护
- 相比较于其他消息中间件,吞吐量较低(万级)
Kafka的优点和缺点
优点: - 吞吐量十万级
- 可用性非常高(一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用)
- 消息可以做到0丢失
缺点: - topic增多会导致吞吐量大幅下降(几百个topic),如果要支持大规模topic,需要更多的机器资源
- 依赖ZooKeeper进行元数据管理(额外的复杂性)