一个用消息队列 的人,不知道为啥用 MQ,这就有点尴尬

消息队列

为何写这篇文章?

博主有两位朋友分别是小A和小B:css

  1. 小A,工做于传统软件行业(某社保局的软件外包公司),天天工做内容就是和产品聊聊需求,改改业务逻辑。再否则就是和运营聊聊天,写几个SQL,生成下报表。又或者接到客服的通知,某某功能故障了,改改数据,而后下班部署上线。天天过的都是这种生活,技术零成长。
  2. 小B,工做于某国企,虽然能接触到一些中间件技术。然而,他只会订阅/发布消息。通俗点说,就是调调API。对为何使用这些中间件啊?如何保证高可用啊?没有充分的认识。

庆幸的是两位朋友都颇有上进心,因而博主写这篇文章,帮助他们复习一下关于消息队列中间件这块的要点前端

复习要点

本文大概围绕以下几点进行阐述:java

  1. 为何使用消息队列
  2. 使用消息队列有什么缺点?
  3. 消息队列如何选型?
  4. 如何保证消息队列是高可用的?
  5. 如何保证消息不被重复消费?
  6. 如何保证消费的可靠性传输?
  7. 如何保证消息的顺序性?

咱们围绕以上七点进行阐述。须要说明一下,本文不是《消息队列从入门到精通》这种课程,所以只是提供一个复习思路,而不是去教大家怎么调用消息队列的API。建议对消息队列不了解的人,去找点消息队列的博客看看,再看本文,收获更大程序员

正文

一、为何要使用消息队列?

分析:一个用消息队列的人,不知道为啥用,这就有点尴尬。没有复习这点,很容易被问蒙,而后就开始胡扯了。
回答:这个问题,咱只答三个最主要的应用场景(不能否认还有其余的,可是只答三个主要的),即如下六个字:解耦、异步、削峰web

(1)解耦

传统模式:

传统模式的缺点面试

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

中间件模式:

中间件模式的的优势redis

  • 将消息写入消息队列,须要消息的系统本身从消息队列中订阅,从而系统A不须要作任何修改。

(2)异步

传统模式:

传统模式的缺点算法

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

中间件模式:

中间件模式的的优势sql

  • 将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

(3)削峰

传统模式

传统模式的缺点数据库

  • 并发量大的时候,全部的请求直接怼到数据库,形成数据库链接异常

中间件模式:

中间件模式的的优势

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

二、使用了消息队列会有什么缺点?

分析:一个使用了MQ的项目,若是连这个问题都没有考虑过,就把MQ引进去了,那就给本身的项目带来了风险。咱们引入一个技术,要对这个技术的弊端有充分的认识,才能作好预防。要记住,不要给公司挖坑!
回答:回答也很容易,从如下两个个角度来答

  • 系统可用性下降:你想啊,原本其余系统只要运行好好的,那你的系统就是正常的。如今你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。所以,系统可用性下降
  • 系统复杂性增长:要多考虑不少方面的问题,好比一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。所以,须要考虑的东西更多,系统复杂性增大。

可是,咱们该用仍是要用的。

三、消息队列如何选型?

先说一下,博主只会ActiveMQ,RabbitMQ,RocketMQ,Kafka,对什么ZeroMQ等其余MQ没啥理解,所以只能基于这四种MQ给出回答。
分析:既然在项目中用了MQ,确定事先要对业界流行的MQ进行调研,若是连每种MQ的优缺点都没了解清楚,就拍脑壳依据喜爱,用了某种MQ,仍是给项目挖坑。若是面试官问:"你为何用这种MQ?。"你直接回答"领导决定的。"这种回答就很LOW了。仍是那句话,不要给公司挖坑。
回答:首先,咱先上ActiveMQ的社区,看看该MQ的更新频率:

Apache ActiveMQ 5.15.3 Release
Christopher L. Shannon posted on Feb 12, 2018
Apache ActiveMQ 5.15.2 Released
Christopher L. Shannon posted on Oct 23, 2017
Apache ActiveMQ 5.15.0 Released
Christopher L. Shannon posted on Jul 06, 2017
省略如下记录
...

咱们能够看出,ActiveMq几个月才发一次版本,听说研究重心在他们的下一代产品Apollo。
接下来,咱们再去RabbitMQ的社区去看一下,RabbitMQ的更新频率

RabbitMQ 3.7.3 release  30 January 2018
RabbitMQ 3.6.15 release  17 January 2018
RabbitMQ 3.7.2 release23 December 2017
RabbitMQ 3.7.1 release21 December 2017
省略如下记录
...

咱们能够看出,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了。具体该选哪一个,看使用场景。

四、如何保证消息队列是高可用的?

分析:在第二点说过了,引入消息队列后,系统的可用性降低。在生产中,没人使用单机模式的消息队列。所以,做为一个合格的程序员,应该对消息队列的高可用有很深入的了解。若是面试的时候,面试官问,大家的消息中间件如何保证高可用的?你的回答只是代表本身只会订阅和发布消息,面试官就会怀疑你是否是只是本身搭着玩,压根没在生产用过。请作一个爱思考,会思考,懂思考的程序员。
回答:这问题,其实要对消息队列的集群模式要有深入了解,才好回答。
以rcoketMQ为例,他的集群就有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式。多master多slave模式部署架构图(网上找的,偷个懒,懒得画):
image
其实博主第一眼看到这个图,就以为和kafka好像,只是NameServer集群,在kafka中是用zookeeper代替,都是用来保存和发现master和slave用的。通讯过程以下:
Producer 与 NameServer集群中的其中一个节点(随机选择)创建长链接,按期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 创建长链接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,可是 Consumer 则不同,它同时和提供 Topic 服务的 Master 和 Slave创建长链接,既能够从 Broker Master 订阅消息,也能够从 Broker Slave 订阅消息。
那么kafka呢,为了对比说明直接上kafka的拓补架构图(也是找的,懒得画)
image
如上图所示,一个典型的Kafka集群中包含若干Producer(能够是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等),若干broker(Kafka支持水平扩展,通常broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。Kafka经过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。
至于rabbitMQ,也有普通集群和镜像集群模式,自行去了解,比较简单,两小时即懂。
要求,在回答高可用的问题时,应该能逻辑清晰的画出本身的MQ集群架构或清晰的叙述出来。

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

分析:这个问题其实换一种问法就是,如何保证消息队列的幂等性?这个问题能够认为是消息队列领域的基本问题。换句话来讲,是在考察你的设计能力,这个问题的回答能够根据具体的业务场景来答,没有固定的答案。
回答:先来讲一下为何会形成重复消费?
  其实不管是那种消息队列,形成重复消费缘由其实都是相似的。正常状况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不一样的消息队列发送的确认信息形式不一样,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念,简单说一下(若是还不懂,出门找一个kafka入门到精通教程),就是每个消息都有一个offset,kafka消费过消息后,须要提交offset,让消息队列知道本身已经消费过了。那形成重复消费的缘由?,就是由于网络传输等等故障,确认信息没有传送到消息队列,致使消息队列不知道本身已经消费过该消息了,再次将该消息分发给其余的消费者。
  如何解决?这个问题针对业务场景来答分如下几点
  (1)好比,你拿到这个消息作数据库的insert操做。那就容易了,给这个消息作一个惟一主键,那么就算出现重复消费的状况,就会致使主键冲突,避免数据库出现脏数据。
  (2)再好比,你拿到这个消息作redis的set的操做,那就容易了,不用解决,由于你不管set几回结果都是同样的,set操做原本就算幂等操做。
  (3)若是上面两种状况还不行,上大招。准备一个第三方介质,来作消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录便可。

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

分析:咱们在使用消息队列的过程当中,应该作到消息不能多消费,也不能少消费。若是没法作到可靠性传输,可能给公司带来千万级别的财产损失。一样的,若是可靠性传输在使用过程当中,没有考虑到,这不是给公司挖坑么,你能够拍拍屁股走了,公司损失的钱,谁承担。仍是那句话,认真对待每个项目,不要给公司挖坑。
回答:其实这个可靠性传输,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据

RabbitMQ

(1)生产者丢数据
从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。
transaction机制就是说,发送消息前,开启事物(channel.txSelect()),而后发送消息,若是发送过程当中出现什么异常,事物就会回滚(channel.txRollback()),若是发送成功则提交事物(channel.txCommit())。
然而缺点就是吞吐量降低了。所以,按照博主的经验,生产上用confirm模式的居多。一旦channel进入confirm模式,全部在该信道上面发布的消息都将会被指派一个惟一的ID(从1开始),一旦消息被投递到全部匹配的队列以后,rabbitMQ就会发送一个Ack给生产者(包含消息的惟一ID),这就使得生产者知道消息已经正确到达目的队列了.若是rabiitMQ没能处理该消息,则会发送一个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信号,生产者会自动重发。
那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步
一、将queue的持久化标识durable设置为true,则表明是一个持久的队列
二、发送消息的时候将deliveryMode=2
这样设置之后,rabbitMQ就算挂了,重启后也能恢复数据
(3)消费者丢数据
消费者丢数据通常是由于采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rahbitMQ会当即将消息删除,这种状况下若是消费者出现异常而没能处理该消息,就会丢失该消息。
至于解决方案,采用手动确认消息便可。

kafka

这里先引一张kafka Replication的数据流向图
image
Producer在发布消息到某个Partition时,先经过ZooKeeper找到该Partition的Leader,而后不管该Topic的Replication Factor为多少(也即该Partition有多少个Replica),Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每一个Follower都从Leader中pull数据。
针对上述状况,得出以下分析
(1)生产者丢数据
在kafka生产中,基本都有一个leader和多个follwer。follwer会去同步leader的信息。所以,为了不生产者丢数据,作以下两点配置

  1. 第一个配置要在producer端设置acks=all。这个配置保证了,follwer同步完成后,才认为消息发送成功。
  2. 在producer端设置retries=MAX,一旦写入失败,这无限重试

(2)消息队列丢数据
针对消息队列丢数据的状况,无外乎就是,数据还没同步,leader就挂了,这时zookpeer会将其余的follwer切换为leader,那数据就丢失了。针对这种状况,应该作两个配置。

  1. replication.factor参数,这个值必须大于1,即要求每一个partition必须有至少2个副本
  2. min.insync.replicas参数,这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟本身保持联系

这两个配置加上上面生产者的配置联合起来用,基本可确保kafka不丢数据

(3)消费者丢数据
这种状况通常是自动提交了offset,而后你处理程序过程当中挂了。kafka觉得你处理好了。再强调一次offset是干吗的
offset:指的是kafka的topic中的每一个消费组消费的下标。简单的来讲就是一条消息对应一个offset下标,每次消费数据的时候若是提交offset,那么下次消费就会从提交的offset加一那里开始消费。
好比一个topic中有100条数据,我消费了50条而且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。
解决方案也很简单,改为手动提交便可。

ActiveMQ和RocketMQ

你们自行查阅吧

七、如何保证消息的顺序性?

分析:其实并不是全部的公司都有这种业务需求,可是仍是对这个问题要有所复习。
回答:针对这个问题,经过某种算法,将须要保持前后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。而后只用一个消费者去消费该队列。
有的人会问:那若是为了吞吐量,有多个消费者去消费怎么办?
这个问题,没有固定回答的套路。好比咱们有一个微博的操做,发微博、写评论、删除微博,这三个异步操做。若是是这样一个业务场景,那只要重试就行。好比你一个消费者先执行了写评论的操做,可是这时候,微博都还没发,写评论必定是失败的,等一段时间。等另外一个消费者,先执行写评论的操做后,再执行,就能够成功。
总之,针对这个问题,个人观点是保证入队有序就行,出队之后的顺序交给消费者本身去保证,没有固定套路。

总结

写到这里,但愿读者把本文提出的这几个问题,通过深入的准备后,通常来讲,能囊括大部分的消息队列的知识点。若是面试官不问这几个问题怎么办,简单,本身把几个问题讲清楚,突出如下本身考虑的全面性。
最后,其实我不太提倡这样突击复习,但愿你们打好基本功,作一个爱思考,懂思考,会思考的程序员

做者:孤独烟  出处: http://rjzheng.cnblogs.com/
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的 【推荐】