消息队列面试要点

694 阅读10分钟
  • 为什么使用消息队列?

分析:一个用消息队列的人,不知道为啥用,这就有点尴尬、没有复习这点,很容易被问蒙,然后就开始胡扯了。

回答:这个问题,咱只答三个最主要的应用场景,即以下六个字:解耦、异步、削峰

(1)解耦

传统模式:

传统模式的缺点:系统间耦合性太强,如上图所示,系统A在代码中直接调用系统B和系统C的代码,
如果将来D系统接入,系统A还需要修改代码,过于麻烦!

中间件模式:

中间件模式的优点:将消息写入消息队列,需要消息的系统自己从消息队列中订阅后进行处理,
从而系统A不需要做任何修改。

(2)异步
传统模式:

传统模式的缺点:一些非必要的业务逻辑以同步的方式运行,太耗费时间。

中间件模式:

中间件模式的优点:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

(3)削峰
传统模式:

传统模式的缺点:并发量大的时间,所有的请求直接怼到数据库,造成数据库连接异常

中间件模式:

中间件模式的优点:系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。
在生产中,这个短暂的高峰期积压是允许的。

------------------------------------------------------------------------------------------------

  • 使用消息队列有什么缺点?

分析:分析:一个使用了MQ的项目,如果连这个问题都没有考虑过,就把MQ引进来了,那就给自己的项目带来了风险。我们引入一个技术,要对这个技术的弊端有充分的认识,才能做好预防。要记住,不要给公司挖坑!

回答:可以从2个角度来思考

  1. 系统可用性降低:本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加入个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性会降低
  2. 系统复杂性增加:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,刺痛复杂性增大。
  • 消息队列如何选型?

分析:既然在项目中用了MQ,肯定事先要对业界流行的MQ进行调研,如果连每种MQ的优缺点都没没了解清楚,就拍脑袋依据喜好来选用某种MQ,这是给项目挖坑。如果面试官问:你为什么用这种MQ?你直接回答:领导决定的。这种回答就是很LOW。还是那句话,不要给公司挖坑。

(1)中小型软件公司,建议选RabbitMQ.一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。正所谓,成也萧何,败也萧何!他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。不考虑rocketmq和kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除。不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐。

(2)大型软件公司,根据具体使用在rocketMq和kafka之间二选一。一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。

------------------------------------------------------------------------------------------------

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

分析:这个问题其实换一种问法就是,如何保证消息队列的幂等性?这个问题可以认为是消息队列领域的基本问题。换句话来说,是在考察你的设计能力,这个问题的回答可以根据具体的业务场景来答,没有固定的答案。

回答:先来说一下为什么会造成重复消费?
其实无论是哪种消息队列,造成重复消费原因其实都是类似的。正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发出的确认消息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offet的概念,简单说一下,就是每一个消息都有一个offset,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。

那造成重复消费的原因?,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。

如何解决?这个问题针对业务场景来答,分以下3种情况:

  1. 比如,你拿到这个消息做数据库的insert操作,那就容易了,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

  2. 再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。

  3. 如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis.那消费者开始消费前,先去redis中查询有没有消费记录即可。

------------------------------------------------------------------------------------------------

  • 如何保证消费的可靠性传输?

分析:我们在使用消息队列的过程中,应该做到消息不能多消费,也不能少消费。如果无法做到可靠性传输,可能给公司带来千万级别的财产损失。同样的,如果可靠性传输在使用过程中,没有考虑到,这不是给公司挖坑麽,你可以拍拍屁股走人,公司损失的钱,谁承担。还是那句话,认真对待每一个项目,不要给公司挖坑。

回答:其实这个可靠性传输,每种MQ都要从三个角度来分析:

  1. 生产者丢数据
  2. 消息队列丢数据
  3. 消费者丢数据

以RabbitMQ为例

(1)生产者丢数据

从生产者弄丢数据这个角度来看,RabbitMQ提供transactionconfirm模式来确保生产者不丢消息。

transaction模式就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。

然而,这种方式有个缺点:吞吐量下降。因为,按照经验,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。处理Ack和Nack的代码如下所示:

channel.addConfirmListener(new ConfirmListener() {  
                @Override  
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {  
                    System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
                }  
                @Override  
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
                    System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
                }  
            });  

(2)消息队列丢数据

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步

  1. 将queue的持久化标识durable设置为true,则代表是一个持久的队列
  2. 发送消息的时候将deliveryMode=2

这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据

(3)消费者丢数据

消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rabbitMQ会立即将消息删除,这种情况下,如果消费者出现异常而未能处理消息,就会丢失该消息。

至于解决方案,采用手动确认消息即可。

------------------------------------------------------------------------------------------------

  • 如何保证消息的顺序性?

分析:其实并非所有的公司都有这种业务需求,但是还是对这个问题要有所复习。

回答:针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只用一个消费者去消费该队列。
有的人会问:那如果为了吞吐量,有多个消费者去消费怎么办?

原作者:这个问题,没有固定回答的套路。比如我们有一个微博的操作,发微博、写评论、删除微博,这三个异步操作。如果是这样一个业务场景,那只要重试就行。比如你一个消费者先执行了写评论的操作,但是这时候,微博都还没发,写评论一定是失败的,等一段时间。等另一个消费者,先执行写评论的操作后,再执行,就可以成功。

总之,针对这个问题,我的观点是保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。

本文作者:

以rabbitMq为例,结合confirm机制,需要额外维护消息的顺序和维护消息的处理状态

每次在某个channel上投递成功后返回的消息唯一ID,把ID放入一个新的队列或者其他介质中

所有队列对应的消费者消费消息时只作insert到mysql的操作

处理mysql中的存储的未处理的消息,判断消息是否是已处理的消息的下一条消息,如果是则进行处理,反之不处理

原文链接:www.cnblogs.com/peterempero…