这里总结一些MQ(Message Queue,消息队列)的相关知识。
消息队列的优点
解耦
在传统模式下,系统之间的耦合性太强,比如系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码。
如果将消息写入消息队列,需要消息的系统自己从消息队列中订阅,在D系统接入的时候系统A也不需要做任何修改,达到了解耦的效果。
异步
在传统模式下,一些非必要的业务逻辑以同步的方式运行,需要等待上一个业务逻辑执行完毕才能开始执行下一个业务逻辑,耗费等待的时间。
如果将消息写入消息队列,非必要的业务逻辑就可以以异步的方式运行,加快了服务响应的速度。
削峰
在传统模式下,当并发量大的时候,所有的请求都会直接怼到数据库,造成数据库连接异常,甚至宕机。
如果将消息写入消息队列,则系统A可以慢慢地按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。
消息队列的缺点
我们引入一个技术,要对这个技术的弊端有充分的认识,才能做好预防。一个使用了MQ的项目,如果连MQ的缺点都没有考虑过,就把MQ引进去了,那就会给自己的项目带来风险。
系统可用性降低
你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,系统也就挂了。用专业的术语来解释,就是系统的可用性降低了。
系统复杂性增加
加了MQ之后要多考虑很多方面的问题,比如数据的一致性问题、如何避免消息被重复消费,如何保证消息可靠传输等。因此,需要考虑的东西更多,系统复杂性也就随着增加了。
消息队列的选型
既然在项目中用了MQ,肯定事先要对业界流行的MQ进行调研,如果连每种MQ的优缺点都没了解清楚,随便选用了某种MQ,就容易给项目挖坑。另外,如果面试官问为什么项目上选用这种MQ的时候,你直接回答是领导决定的,这种回答就很LOW了。所以了解一下各种MQ的优缺点和使用的场景还是有必要的。
这里只简单说一下ActiveMQ、RabbitMQ、RocketMQ和Kafka四种MQ框架。
更新频率
要了解各个MQ方案的更新频率,可以上各个MQ的官方社区去看。
简单了解一下就是,ActiveMQ几个月才发一次版本,据说研究重心在他们的下一代产品Apollo;RabbitMQ的版本发布比ActiveMQ频繁很多;RocketMQ和Kafka也比ActiveMQ活跃的多。
性能对比
用一个表格简单比较下各个MQ的性能。
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
开发语言 | java | erlang | java | scala |
单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
时效性 | ms级 | us级 | ms级 | ms级以内 |
可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) |
功能特性 | 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 | MQ功能比较完备,扩展性佳 | 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。 |
MQ选型的简单总结
1.中小型软件公司,建议选RabbitMQ。一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。但是虽然RabbitMQ是开源的,国内能定制化开发erlang的程序员却很少。所幸的是RabbitMQ的社区十分活跃,通过社区可以解决开发过程中遇到的大部分Bug,这点对于中小型公司来说十分重要。不考虑RocketMQ和Kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以Kafka排除。不考虑RocketMQ的原因是,RocketMQ是阿里出品,一旦哪一天阿里放弃维护RocketMQ,中小型公司一般是抽不出人来进行RocketMQ的定制化开发的,因此不推荐。
2.大型软件公司,根据具体使用在RocketMQ和Kafka之间二选一。一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对RocketMQ,大型软件公司也可以抽出人手对RocketMQ进行定制化开发,毕竟国内有能力改Java源码的人,还是相当多的。至于Kafka,根据业务场景选择,如果有日志采集功能,肯定是首选Kafka了。具体该选哪个,还是要看具体的使用场景。
保证消息队列的高可用
前面说过了,引入消息队列后,系统的可用性会降低。在生产中,没人会使用单机模式的消息队列,因此了解消息队列的高可用是很必要的。实际上,要保证消息队列的高可用,需要对消息队列的集群模式有深刻了解。
RocketMQ保证消息队列的高可用
以RocketMQ为例,他的集群就有多master模式、多master多slave异步复制模式和多master多slave同步双写模式。
下面是一张RocketMQ多master多slave模式部署的架构图。
这样的实现方式其实和Kafka很像,只是Name Server集群在Kafka中是用Zookeeper代替,都是用来保存和发现Master和Slave用的。通信过程如下:Producer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server集群获取Topic路由信息,并向提供Topic服务的Broker Master建立长连接,且定时向Broker发送消息。Producer只能将消息发送到Broker Master,但是Consumer则不一样,它同时和提供Topic服务的Master和Slave建立长连接,既可以从Broker Master订阅消息,也可以从Broker Slave订阅消息。
Kafka保证消息队列的高可用
直接上Kafka的拓补架构图来看Kafka的集群模式。
如上图所示,一个典型的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保证消息队列的高可用
RabbitMQ也有普通集群和镜像集群模式,比较简单。
保证消息队列高可用性的简单总结
要保证消息队列的高可用,需要从集群上入手,具体就是当消息队列中的一个节点炸了,其他的节点还能继续运行,保证消息队列整体能正常运行。
另外,如果有相关的面试题,需要了解MQ集群架构并能叙述清楚架构中的逻辑关系。
保证消息不被重复消费
保证消息不被重复消费,也就是保证消息队列的幂等性。这个问题可以认为是消息队列领域的基本问题。
消息被重复消费的原因
无论是哪一种消息队列,造成消息被重复消费的原因都是类似的。在正常的情况下,消费者在消费消息完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,Kafka则实际上是有个offset的概念(每一个消息都有一个offset,Kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了)。而造成重复消费的原因,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。
消息被重复消费的解决方法
解决方法有很多,其实就是单机服务里幂等性的问题,这里简单列举几个方法。
1.唯一索引。如果拿到的消息是做数据库的insert操作,可以给这个消息做一个唯一主键,那么就算出现重复消费的情况,也会因为主键冲突而取消操作,有效避免数据库出现脏数据。
2.原生幂等。如果拿到的消息是做Redis的set的操作,因为在Redis中set操作本来就算幂等操作,即无论set几次结果都是一样的,也就不怕消息被重复消费的问题了。
3.全局状态机。准备一个第三方介质,来做一个消息消费的记录。以Redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V的形式写入Redis。在消费者开始消费前,先去Redis中查询有没有消费记录即可。
保证消息消费的可靠性传输
在使用消息队列的过程中,应该做到消息不能多消费,也不能少消费。如果无法做到可靠性传输,可能给公司带来千万级别的财产损失。
而要保证消息消费的可靠性传输,每种MQ都要从三个角度来分析,分别是生产者弄丢数据、消息队列弄丢数据和消费者弄丢数据。
这个内容太复杂了,暂时玩不转,等以后再补充,嘿嘿。
保证消息的顺序性
要保证消息的顺序性,可以通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(Kafka中就是Partition,RabbitMQ中就是Queue),然后只用一个消费者去消费该队列即可。
另外,如果为了吞吐量,有多个消费者去消费,这时候要保证消息的顺序性,其实只要一个简单的重试就好了。比如微博,可以分为发微博、写评论和删除微博这三个异步操作。如果这时候有一个消费者先执行了写评论的操作,但是这时候,微博都还没发,写评论一定是失败的,就可以等一段时间再重试。等另一个消费者,先执行发微博的操作后,再执行写评论的操作,就可以成功。
实际上,要保证消息的顺序性,大多数情况下只需要保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定的套路。
"慢慢大家会明白的,无法跟喜欢的人在一起,其实是人生的常态。"