在前两天阿里的面试中,面试官问了几个关于MQ的问题:
What
1.为什么要使用 MQ
2.使用了 MQ 之后有什么优缺点
3.怎么保证 MQ 消息不丢失
4.怎么保证 MQ 的高可用性
其实大家平时可能也有用到MQ,但是可能对于 MQ 的理解仅仅停留在会使用 API 能实现生产消息、消费消息就完事了。可能很多人都没有对 MQ 的一些问题思考过。
为什么需要消息队列(MQ)
其实MQ 的场景有很多,但是比较核心的有 3 个:异步处理、削峰填谷、应用解耦。
1.异步处理
用户注册后,需要发送注册邮件和注册短信。假设注册信息加入数据库需要30ms,发送注册邮件需要40ms。发送注册短信需要50ms。总共需要的时间就是30+40+50=120ms。可能用户会感觉太慢了
但是一旦加入 MQ 之后,系统只需要将客户信息放入数据库就可以直接返回给用户注册成功的信息。然后用MQ异步处理短信和邮箱的验证。而短信和邮箱的验证因为网络问题。用户是可以接受一定时间的延迟的。那么算下来用户感知到这个接口的耗时仅仅是30ms,节约了2/3的时间。用户体验那是倍儿爽。
2.削峰填谷
秒杀在我们的日常中相当常见。在秒杀的过程中,系统都发生了什么呢?假设我们的数据库每秒能处理最大的数据量是100条。但是在活动秒杀的时候,数据量激增到每秒一万条。这样一来服务器不堪重负就会gg掉。所以就要考虑优化我们的架构,而MQ正是解决办法之一!具体办法就是将秒杀的信心和数据放入消息队列。系统按照之前的处理速度来从容的处理这些数据。而不是直接将全部数据涌入系统,避免宕机问题的发生。
3.降低系统之间的耦合度
系统A下面有两个子系统,一个 B 一个 C。这两个子系统都高度依赖于系统A,也就是系统A有所改动的话,那么系统B、C也要做相应的改动。万一有一百个子系统呢?想想都累啊。。
所以这时候就要引入MQ作为中间件了。这样一来子系统只依赖消息而不再依赖于具体的接口。只要 A 系统把消息扔给 MQ 就不用管了。这样即使系统 A 增加或者修改服务的时候。都不会影响其子系统。
通过上面的分析,知道了为什么要使用 MQ,以及使用了 MQ 有什么好处。知其所以然,明白了自己的系统为什么要使用 MQ。就不会出现“我们Leader要用 MQ 我们就用了”这样的事情了。
使用了 MQ 之后有什么优缺点
一个使用了MQ的项目,如果连为什么要用MQ问题都没有考虑过,那就给自己的项目带来了风险。我们引入一个新的技术,也要对这个技术的弊端有充分的了解,这样才能做好预防。要记住,不要给自己挖坑!
1.系统可用性降低
你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不就GG了。因此,系统可用性降低
2.系统复杂性增加
要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
3.数据一致性问题
本来好好的,A 系统调用BC子系统接口,如果子BC系统出错了,会抛出异常,返回给A系统,让A系统知道,这样的话就可以做回滚操作了,但是使用了 MQ 之后,A 系统发送完消息就完事了,就认为成功了。但是刚好 C 在系统写数据库的时候失败了,但是 A 认为 C 已经成功了?这样一来数据就不一致了。
怎么保证 MQ 消息不丢失
一条MQ消息从产生到消费,有没有可能失败?在哪些环节可能失败,如何处理?
1.消息生产失败
一般来说,从生产者到MQ中间件是通过网络调用的,是网络调用就有可能存在失败。消息队列通常使用确认机制,来保证消息可靠传递:当你代码调用发送消息的方法,消息队列的客户端会把消息发送到Broker,Broker接受到消息会返回客户端一个确认。只要Producer收到了Broker的确认响应,就可以保证消息在生产阶段不会丢失。有些消息队列在长时间没收到发送的确认响应后,会自动重试,如果重试再失败,就会一返回值或者异常方式返回给客户端。所以在编写发送消息的代码,需要正确处理消息发送返回值或者异常,保证这个阶段消息不丢失。
2.MQ处理存储失败
消息到达消息中间件之后,通常是会被存储起来的,只有被写入到磁盘中,消息才是真正地被存储,不会丢失。但是,大部分MQ中间件并不是收到消息就立马写入磁盘的,只是由于磁盘的写入速度相对于内存,现得慢得多得多,所以,像Kafka这样的消息系统,是会把消息写到缓冲区中,异步写入磁盘,如果机器在中途突然断电,是有可能会丢失消息的。为了解决这个问题,大部分的MQ都是采用分布式部署,消息会在多台机器上写入缓存中成功才会返回给业务方成功,由于多台机器同时断电的可能性较低,我们可以认为这是比较低成本又可靠的方案。
3.消费者处理失败
一般的MQ都有MQ重试机制,如果处理失败,就会尝试重复消费这个MQ。这个带来的问题就是,MQ可能已经成功消费了,但是在通知MQ中间件的时候失败了,这个时候带来的结果就是消息重复消费。同理,在生产者重试的时候,也会遇到消息重复消费的问题。这个时候,就要求我们尽量把接口设计得有幂等性,这个时候即便是重复消费,也不用担心什么问题了
怎样保证MQ的高可用性
RabbitMQ 是比较有代表性的,因为是基于主从做高可用性的,我们就以他为例子讲解第一种 MQ 的高可用性怎么实现。RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。
1.单机模式就是 demo 级别的,就是说只有一台机器部署了一个 RabbitMQ 程序。这个会存在单点问题,宕机就玩完了,没什么高可用性可言。一般就是你本地启动了玩玩儿的,没人生产用单机模式。
2.普通集群模式,意思是多台机器启动多个RabbitMQ实例,每个机器启动一个。你创建的queue,只会放在一个RabbitMQ实例上。但是每个实例会同步queue的元数据(可以理解queue的配置信息)。即使你消费的时候连接到了另一个实例,那么那个实例会从queue所在实例上拉取数据过来
其实并没有做到所谓的分布式,还是普通集群。因为会导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个queue所在实例消费数据。前者有数据拉取的开销,后者会导致单实例性能瓶颈。
如果放queue的实例宕机了,则会导致其他实例都没有办法进行拉取。如果你开启了消息持久化,让RabbitMQ落地存储消息的话,消息不一定会丢。得等这个实例恢复了,然后才可以继续从这个queue拉取数据。
3.镜像集群模式才是所谓的rabbitmq的高可用模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。
这样的话,好处在于你任何一个机器宕机了,没事儿,别的机器都可以用。
坏处在于这个性能开销也太大了吧,消息同步所有机器,导致网络带宽压力和消耗很重!
这么玩儿,就没有扩展性可言了,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue那么怎么开启这个镜像集群模式呢?
这里简单说一下,避免面试人家问你你不知道,其实很简单rabbitmq有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
看完本文有收获?请转发分享给更多人