【八股文】MQ篇

 1. 什么是MQ

消息队列中间件,是一种跨进程的通信机制,用于上下游传递消息。使用了MQ之后,消息发送上游只需要依赖MQ,不用依赖其他服务。

2. MQ的优点

异步、削峰、解耦

1. 异步
        不是很重要的操作,可以通过异步操作来实现。(当然线程池也可以实现,但没有解耦的特点)

2. 削峰
把请求放到队列里面,然后至于每秒消费多少请求,就看自己的服务器(一般是数据库)处理能力,等流量高峰下去了,你的服务也就没压力了(短暂的请求积压是允许的)

3. 解耦
每加一个下游操作,都需要改上游的代码,重新部署,太麻烦了;解耦的意思是,下游直接订阅MQ,上游不需要改代码,就很不错

3. MQ的缺点

1. 系统的可用性降低
万一MQ突然挂了,上下游就没办法交互了,导致上游不可用

2. 系统复杂性提高
如何保证没有重复消费呢?如何处理消息丢失的情况?怎么保证消息传递的顺序?(下面一一补充)

3. 一致性问题
上游无法知道下游的执行结果,这一点是很致命的,分布式事务问题,当然当然,RocketMQ除外了

4. 为什么要使用MQ

        我们公司本身的业务体量很小,所以直接单机一把梭啥都能搞定了,但是后面业务体量不断扩大,采用微服务的设计思想,分布式的部署方式,所以拆分了很多的服务,随着体量的增加以及业务场景越来越复杂了,很多场景单机的技术栈和中间件以及不够用了,而且对系统的友好性也下降了,最后做了很多技术选型的工作,我们决定引入消息队列中间件。

白话文:业务量增多了,业务场景复杂了,系统响应变慢了呀,但是单机的技术栈顶不住这个业务量

5.  MQ的架构设计

Broker:rabbitmq的服务节点 

Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中,生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费这都收到所有的消息进行消费(注意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑定多个队列,由多个消费者来订阅这些队列的方式)

Exchange:交换器。生产者将消息发送给Exchage,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其他处理。

RontingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则。这个路由Key需要与交换器类型或绑定建(BindingKey)联合使用才能最终生效。在交换器类型和绑定健固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。

Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定建,这样RabbitMQ就可以指定如何正确的路由到队列了。

交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BingingKey做关联(多对多关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相应的队列。

信道:信道是建立在Connection之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,客户端紧接着可以创建一个AMQP信道(Channel),每个信道都会被指派一个唯一的ID。RabbitMQ队里每条AMQP指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允许所有的连接通过多条光纤束进行传输和接收。

BindingKey是Exchange和Queue绑定的规则描述,这个描述用来解析当Exchange接收到消息时,Exchange接收到的消息会带有RoutingKey这个字段,Exchange就是根据这个RoutingKey和当前Exchange所有绑定的BindingKey做匹配,如果满足要求,就往BindingKey所绑定的Queue发送消息,这样我们就解决了我们向RabbitMQ发送一次消息,可以分发到不同的Queue的过程

6.  交换器类型

RabbitMQ 常用的交换器类型有 Direct、Topic、Fanout、Headers 这四种。

1、 Direct 类型
        Direct 类型的交换器由路由规则很简单,它会把消息路由到那些 BindingKey 和 RoutingKey 完全匹配的队列中。

        Direct Exchange 是 RabbitMQ 默认的交换器模式,也是最简单的模式。它根据 RoutingKey 完全匹配去寻找队列。

2、 Topic 类型
        上面讲到 Direct 类型的交换器由规则是完全匹配 BindingKey 和 RoutingKey,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。Topic 类型的交换器在匹配规则上进行了扩展,它与 Direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:

(1)RoutingKey 为一个点号 “.” 分隔的字符串(被点号 “.” 分隔开的每一段独立的字符串称为一个单词),如:com.rabbitmq.client、java.util.concurrent、com.hidden.client;

(2)BindingKey 和 RoutingKey 一样也是点号 “.” 分隔的字符串;

(3)BindingKey 中可以存在两种特殊字符串星号 “*” 和井号 “#”,用于做模糊匹配,其中星号 “*” 用于匹配一个单词,井号 “#”用于匹配多个规则单词(0个或者多个单词);

3、 Fanout 类型
        消息广播的模式,即将消息广播到所有绑定到它的队列中,而不考虑 RoutingKey 的值(不管路键或是路由模式)。如果设置了 RoutingKey ,则 RoutingKey 依然被忽略。

4、 Headers 类型
        Headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。Headers 类型的交换器性能会很差,而且不实用,基本上不会看到它的存在。

7.  介绍死信队列、延时队列

1、死信队列
        DLX,全称为 Dead-Letter-Exchange,可以称为死信交换机,也有人称之为死信邮箱,当消息在一个正常队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就称之为死信队列。

        消息变成死信,可能由于以下原因:

                消息被拒绝;
                消息过期;
                队列达到最大长度;

        DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何队列上被指定。实际上就是设置某一个队列的属性,当这个队列中存在死信时,rabbitMQ就会自动的将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。

        想要使用死信队列,只需要在定义队列的时候设置队列参数【x-dead-letter-exchange 指定交换机】即可。

2、延时队列
        延时队列存储的对象是对应的延时消息。所谓 “延时消息” 是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。

        在RabbitMQ中并没有支持延时队列的功能,延时队列可以通过 【过期时间 + 死信队列】 来实现注意:创建的延时队列千万不要有消费者,一旦有消费者进行这个队列的消费,那么就不会是特定延时了,目的就是要这个队列里的消息TTL过时

        生产者 -->  交换机 -->  (路由键) --> 队列 --> (过期) -->  死信交换机 -->  (路由键) --> 死信队列 --> 消费者

8. MQ的使用场景

原文地址:https://blog.csdn.net/qq_35152037/article/details/80012961

1. 上游不关心执行结果(异步、解耦)

        1)例子
       拿下单来说,下单之后一般会有用户增加积分、发送短信等等操作,这些操作可以晚点  再发,对用户来说只要不影响正常下单就行

        2)原实现方案(一一调用关系,依次执行)
        下单之后,调用积分服务,增加积分,调用短信服务发送短信。

        导致的问题
                主业务耗时增加:下单时间增加了,而且随着附属业务功能越来越多,时间会越来越久
                系统可用性降低,万一下游的一个服务挂了,会导致下单失败
        3)MQ方案
                1.下单业务成功后,给MQ发送一个消息
                2.积分业务、短信业务等等去MQ上主动订阅《下单成功》消息

2. 上游关注执行结果,但执行时间很长

        1)例子(跨公网调用)
        下单操作,需要调用微信系统(跨公网调用,请求时间会比较长),延长了下单的时间

        2) MQ方案(回调网关+MQ)
        一般采用“回调网关+MQ”方案来解耦:

                1.调用方直接跨公网调用微信接口
                2.微信直接返回调用成功(此时并不代表返回成功)
                3.微信执行完成后,回调统一网关
                4.网关将返回结果发给MQ
                5.上游订阅MQ,收到结果通知

        疑点:之前调用微信接口不是靠网关的吗??不知道哪里增快了速度

        1. 为什么需要网关还需要MQ
        这里需要注意的是,不应该由回调网关来调用上游来通知结果,如果是这样的话,每次新增调用方,回调网关都需要修改代码,仍然会反向依赖,使用回调网关+MQ的方案,新增任何对微信支付的调用,都不需要修改代码啦

        白话文:加了MQ,每次新增调用方,网关这块(相当于上游服务)就不用改代码了,懂!

3. 数据驱动的任务依赖(任务的执行需要顺序)

        1)例子(task1->task2->task3)
        什么是任务依赖,举个栗子,互联网公司经常在凌晨进行一些数据统计任务,这些任务之间有一定的依赖关系,比如:

        task3需要使用task2的输出作为输入
        task2需要使用task1的输出作为输入
        这样的话,tast1, task2, task3之间就有任务依赖关系,必须task1先执行,再task2执行,载task3执行。

        2)原解决方案(cron人工排执行时间表)
        定时任务之间设置预留时间,确保上一个任务执行完再进行下一个任务

        1.每个任务执行的时间不确定,在任务之间需要有预留时间
        2.如果任务执行快了,但预留时间太多了,下一个任务没法很快执行,浪费时间

        导致的问题(时间资源的浪费)

白话文:预留时间不好设定:1.每个任务执行的时间不确定,在任务之间需要有预留时间 2.如果任务执行快了,但预留时间太多了,下一个任务没法很快执行,浪费时间

        3)MQ方案
        1. 好处
        不需要预留buffer,上游任务执行完,下游任务总会在第一时间被执行
        解耦:依赖多个任务,被多个任务依赖都很好处理,只需要订阅相关消息即可
        有任务执行时间变化,下游任务都不需要调整执行时间(和第一点一样)

9. MQ如何选型

推荐看看:敖丙之消息队列(mq)是什么?

比较维度
1. 吞吐量

        1. 万级别(ActiveMQ、RabbitMQ)

        2. 十万级别(RocketMQ)

        3. 百万级别(Kafka)

2. 可用性(高可用)
        都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 Kafka也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用。

        1. 基于什么架构实现高可用

                基于主从架构(ActiveMQ、RabbitMQ)

                基于分布式架构(RocketMQ、Kafka)

3. 时效性(修改后立马见效的意思)
        RabbitMQ 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。(1毫秒=1000微秒)

        1. 微秒级(RabbitMQ)

        2. 毫秒级(ActiveMQ、RocketMQ、Kafka)

4. 消息丢失
ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。

        1. 小概率丢失(ActiveMQ、RabbitMQ)

        2. 不会丢失(RocketMQ、Kafka)

5. 总结
        1. ActiveMQ(不用了)
        ActiveMQ的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。

白话文:ActiveMQ老了(社区活跃度低),不推荐

        2. RabbitMQ(基于 erlang 开发,JAVA人员看不懂)
        RabbitMQ在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做 erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级??这还不高。。),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

白话文:对于并发量要求不是很高的话,选择RabbitMQ,因为性能最好,而且还年轻(社区活跃度低)

        3. RocketMQ(用JAVA开发,快乐来源)
        RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的

白话文:java开发的,公司有实力的话可以改源码,中年人(社区活跃度一般)

        4. Kafka(大数据、实时计算和日志采集)
        Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略,这个特性天然适合大数据实时计算以及日志收集。

白话文:大数据的日志采集用

10.  如何保证消息不丢失(重要)

消息丢失场景:

        生产者生产消息到RabbitMQ Server消息丢失;

        RabbitMQ Server存储的消息丢失;

        RabbitMQ Server到消费者消息丢失。

消息丢失从三个方面来解决:生产者确认机制、消费者手动确认消息、和持久化。

1、生产者确认机制

生产者发送消息到队列,无法确保发送的消息成功的到达server。

解决方法:

        事务机制。在一条消息发送之后会使发送端阻塞,等待RabbitMQ的回应,之后才能继续发送下一条消息。性能差。

        开启生产者确认机制,只要消息成功发送到交换机之后,RabbitMQ就会发送一个ack给生产者(即使消息没有Queue接收,也会发送ack)。如果消息没有成功发送到交换机,就会发送一条nack消息,提示发送失败。

        路由不可达消息:生产者确认机制只确保消息正确到达交换机,对于从交换机路由到Queue失败的消息,会被丢弃掉,导致消息丢失。对于不可路由的消息,有两种处理方式:Return消息机制和备份交换机。

2、消费者手动消息确认

        有可能消费者收到消息还没来得及处理MQ服务就宕机了,导致消息丢失。因为消息者默认采用自动ack,一旦消费者收到消息后会通知MQ Server这条消息已经处理好了,MQ 就会移除这条消息。

        解决方法:消费者设置为手动确认消息。消费者处理完逻辑之后再给broker回复ack,表示消息已经成功消费,可以从broker中删除。当消息者消费失败的时候,给broker回复nack,根据配置决定重新入队还是从broker移除,或者进入死信队列。只要没收到消费者的 acknowledgment,broker 就会一直保存着这条消息,但不会 requeue,也不会分配给其他 消费者。

3、消息持久化

        如果RabbitMQ服务异常导致重启,将会导致消息丢失。RabbitMQ提供了持久化的机制,将内存中的消息持久化到硬盘上,即使重启RabbitMQ,消息也不会丢失。

        消息持久化需要满足以下条件:

                消息设置持久化。
                Queue设置持久化。
                交换机设置持久化。

        当发布一条消息到交换机上时,Rabbit会先把消息写入持久化日志,然后才向生产者发送响应。一旦从队列中消费了一条消息的话并且做了确认,RabbitMQ会在持久化日志中移除这条消息。在消费消息前,如果RabbitMQ重启的话,服务器会自动重建交换机和队列,加载持久化日志中的消息到相应的队列或者交换机上,保证消息不会丢失。

消息重复消费怎么处理?

        消息重复的原因有两个:

                1.生产时消息重复,

                2.消费时消息重复。

        生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,这时候生产者就会重新发送这条消息,导致MQ会接收到重复消息。

        消费者消费成功后,给MQ确认的时候出现了网络波动,MQ没有接收到确认,为了保证消息不丢失,MQ就会继续给消费者投递之前的消息。这时候消费者就接收到了两条一样的消息。由于重复消息是由于网络原因造成的,无法避免。

        解决方法:发送消息时让每个消息携带一个全局的唯一ID,在消费消息时先判断消息是否已经被消费过,保证消息消费逻辑的幂等性。具体消费过程为:

        消费者获取到消息后先根据id去查询redis/db是否存在该消息;
        如果不存在,则正常消费,消费完毕后写入redis/db;
        如果存在,则证明消息被消费过,直接丢弃。

9. RocketMQ如何保证消息的顺序性

总结:1. 保证一对一 2. 自定义负载均衡模式(哈希取余) 3. 使用有序消费模式

1. 为什么会出现乱序(负载均衡)
        Broker中的每个Topic都有多个Queue,写入消息的时候会平均分配(负载均衡机制,默认轮询,也可以自定义)给不同的Queue,假如我们有一个消费者组ComsumerGroup,这个消费组中的每一台机器都会负责一部分Queue,那么就会导致顺序的乱序问题

2. 例子
        Producer先后发送了2条消息,一条insert,一条update,分别分配到2台Queue中,消费者组中的两台机器分别处理这两个Queue中的消息,这时候顺序是无法保证的

3. 如何解决
        1. 保证Producer、Queue、Comsumer是一对一对一的关系
                问题:
                1. 吞吐量降低
                消息队列的吞吐量降低(绝对不容忍这样的情况发生)

                2. 有阻塞的风险
                如果Comsumer服务炸了,后面的消息就无法消费,被阻塞了

        2. 把需要保持顺序消费的消息放到同一个Queue中,且让同一台机子处理
                自定义负载均衡模式,把这一批顺序消息有共同的唯一ID,把唯一ID与队列的数量进行          hash取余运算,保证这批消息进入到同一个队列

                存在的问题
                        还要考虑Comsumer消费失败的重试问题

        3. 使用有序消费的模式
                如果失败了会返回这个状态
                ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT:稍后消费

10. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?

1. 分析

这一系列问题的本质其实就是消费端出问题了!!!(消费慢或者不消费了)

2. 线上出问题怎么解决(修复Comsumer、紧急扩容)

思路:临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据

        1)修复consumer的BUG,确保其恢复消费速度,然后将现有cnosumer都停掉

        2)创建10倍新队列:新建一个topic,queue是原来的10倍,临时建立好原先10倍或者20倍的queue数量

        3)写一个临时的分发数据的consumer程序:这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue

目的:把大量消息均匀分发到新队列上

        4)Comsumer10倍扩容:临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据

        5)打完收工:等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息

3. 消息过期失效了怎么办?(人工介入,半夜执行代码)

        RabbitMQ的消息会有失效时间,写一个程序,把失效的消息ID找回来,等机子空闲的时候再手动塞进MQ里

4. 消息队列满了怎么办?(快速消费,假消费)

        快速消费掉所有消息,如假装消费

        (直接返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS),

        先把消息保存下来,等空闲的时候再手动塞进MQ里

【八股文】MQ篇

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值