微服当中的消息中间件面试题

1.为什么要使用消息队列

   答:这个问题,咱只答三个最主要的应用场景(不可否认还有掐的,但是只答三个主要的),即以下六个字:解耦、异步、削峰

         (1)解耦

                

              传统模式:  

                传统模式的缺点:

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

        中间件模式的优点:

                   将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

          (2)异步
              传统模式:

                传统模式的缺点:

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

              中间件模式:

                中间件模式的优点:

           (3)削峰

               传统模式:

                传统模式的缺点:

                     并发量大的时间,所有的请求直接怼到数据库,造成数据库连接异常

               中间件模式:

                  中间件模式的优点:

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

2.使用了消息队列会有什么缺点

    答:

       1.系统可用性降低:你想呀,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加入个消息队列进去,那消息队列挂了,你的系统不是呵呵了。               因此,系统可用性会降低

       2.系统复杂性增加:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑               的东西更多,刺痛复杂性增大。

3.消息队列如何选型?

   答:首先,咱先上ActiveMQ的社区,看看该MQ的更新频率:

  1.  
    Apache ActiveMQ 5.15.3 Release
  2.  
    Christopher L. Shannon posted on Feb 12, 2018
  3.  
    Apache ActiveMQ 5.15.2 Released
  4.  
    Christopher L. Shannon posted on Oct 23, 2017
  5.  
    Apache ActiveMQ 5.15.0 Released
  6.  
    Christopher L. Shannon posted on Jul 06, 2017
  7.  
    省略以下记录

        我们可以看出,ActiveMQ几个月才发一次版本,据说研究重心在他们的下一代产品Apollo。

        接下来,我们再去RabbitMQ的社区去看一下,RabbitMQ的更新频率

  1.  
    RabbitMQ 3.7.3 release 30 January 2018
  2.  
    RabbitMQ 3.6.15 release 17 January 2018
  3.  
    RabbitMQ 3.7.2 release23 December 2017
  4.  
    RabbitMQ 3.7.1 release21 December 2017
  5.  
    省略以下记录
  6.  
    ...

         我们可以看出,RabbitMQ版本发布比ActiveMq频繁很多。至于RocketMQ和kafka就不带大家看了,总之也比ActiveMQ活跃的多。详情,可自行查阅。

特性 ActiveMQ RabbitMQ RocketMQ kafka
开发语言 java erlang java scala
单机吞吐量 万级 万级 10万级 10万级
时效性 ms级 us级 ms级 ms级以内
可用性 高(主从架构) 高(主从架构) 非常高(分布式架构) 非常高(分布式架构)
功能特性 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 MQ功能比较完备,扩展性佳 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。

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

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

4.如何保证消息不被重复消费

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

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

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

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

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

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

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

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

  • 生产者弄丢数据
  • 消息队列弄丢数据
  • 消费者弄丢数据

    (1)生产者丢数据
      从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。

       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会立即将消息删除,这种情况下,如果消费者出现异常       而未能处理消息,就会丢失该消息。

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

6..如何保证消息的顺序性

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

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

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

   

                      

猜你喜欢

转载自www.cnblogs.com/lingboweifu/p/11797927.html