因为之前的项目中有涉及到MQ的相关使用,当时架构已经设计好了,我只负责代码的编写,但是后来想想不应该是这么回事,所以把MQ的相关知识结合一些视频进行了整理。
关于技术选型
目前市面上主流的有四种MQ,分别为ActiveMQ,RabbitMQ,RocketMQ,以及Kafka。而用的比较多的分别为RabbitMQ以及Kafka。
- 吞吐量,ActiveMQ和RabbitMQ都是属于万级,而RocketMQ和Kafka属于十万级。这证明RocketMQ也是一种支持高吞吐的MQ,而Kafka最大的优点即为吞吐量高,这一点配合上大数据类的系统进行实时计算、日志采集等都会有不错的性能表现。
- topic,RocketMQ在同等机器下有不错的表现,可以支撑大量MQ,几百或几千个不等,而且性能也不会下降过多。而Kafka的topic从几十个到几百个的过程中吞吐量会大幅下降,如需大规模支持需要添加机器资源。
- 延迟,RabbitMQ的延迟是最低的,只有微妙水平,而其他三种大概在毫秒级别,之前看网上说区别不是太大,可能是就平时的业务场景而言吧。
- 高可用 Active以及Rabbit都是基于主从架构实现高可用,之后会进行详细回顾,而Rocket和Kafka基于分布式架构,之后也会对分布式与非分布式如何实现高可用进行区别。
- 消息可靠性 都有较低的概率造成消息不可靠,但是如今大部分架构都可以进行调优,将消息丢失,重复添加,乱序问题进行优化。
- 优劣势
在如今大量公司中,ActiveMQ都有得到使用,因为在前几年,ActiveMQ还是业内使用MQ的一个标准工具。但是近几年ActiveMQ的社区维护速度不断下降,且ActiveMQ大多数情况下主要是基于异步和解耦来使用的,在RabbitMQ和Kafka优异性能影响下,越来越多公司选择这两款MQ而非前者。
RabbitMQ优势:有较强的图形管理界面,吞吐量到万级,MQ的功能也比较完备,由erlang语言开发性能非常好,延迟低,但是问题也出在这,国内对于该块的文档较少,所以出现新bug的话想通过源码解决还是比较困难,好在社区活跃。
RocketMQ是阿里出品的品质自然没得说,基于阿里的各种业务场景吞吐分布式扩展等相关MQ功能也比较完备,源码为Java系,有专业MQ架构团队的公司可以考虑使用,因为毕竟阿里这些年技术发展太快了,很难保证人家不会有更优异性能的MQ开源出来,到时候可能会需要公司独立维护。类似换手机,看到更香的肯定会馋的吧~
Kafka仅提供较少的核心功能但却有超高的吞吐以及超低的延迟,这在大数据领域就比较吃香了,但是消息有可能被重复消费,可毕竟是大数据,所以那几条重复的消息造成的轻微影响可以忽略。
画重点的两款MQ是博主最近学习的,之后的相关讨论也将围绕这两者展开,毕竟两者在是否为分布式方面都有自己独特的优点,比较有代表性。
使用消息队列的原因?
项目里使用消息队列,主要的三点好处为解耦、异步和削峰。
- 解耦,系统的中有一个系统或模块与其他几个模块关联度过高,互相调用复杂但是不必同步调用接口的,则可使用MQ进行解耦。
- 异步,面对高延迟的接口,可使用异步机制进行优化,用户在调取接口时,将请求放入MQ后便直接返回页面,其余的一些耗时的数据处理就慢慢来吧~ 这样可以大幅度提高用户的体验,以及优化系统的性能。
- 削峰,在系统使用高峰期,MQ的压力会大幅度增加,比如MySql一半每秒处理2000个请求,但是如果用户使用高峰,涌入大量的SQL查询需求例如4000个,那这时MySQL可能就要扛不住了,削峰则是创建一个MQ将请求放入MQ中,一次从中取2000个请求,那么这样就能抗住如此大量的请求了,就算高峰中大量的请求挤压于MQ中,只要高峰一过,MySql还是会按照每秒2000条的速度进行消解,并且此时请求进入MQ的速度可能只有50条每秒,这样就可以达到削峰的目的了。
MQ的引入,虽然给系统带来了非常大的好处,但是前提是,能够对引入一个新的复杂架构所带来的问题进行有效解决,不然则仅仅是增加了系统的负担而已。
常见的一些需要解决的问题:
如何保证高可用
这边使用两种MQ举例
(1)RabbitMQ的高可用机制:
RabbitMQ有三种模式,单机模式(demo,练习用的),普通集群模式,镜像集群模式。
而RabbitMQ的高可用是基于镜像集群模式,剩下的两种模式感兴趣可以去百度下,挺多图解的。
镜像集群模式: 在普通集群模式中,生产者会将数据传入一个类似master的主机的queue中,这个主机保存实际数据和元数据(标记了一些queue的属性,具体位置等),而其他的节点则为follower,他们会自动从master节点中,pull出queue的元数据,消费者进行消费时可以任意选择节点,如果选择到follower节点,则会向master索要数据,然后再返回给消费者进行消费,问题可想而知,只有master存储实际的数据,如果master凉了,那系统就没了,普通集群模式没有高可用可言。 然而镜像集群模式下,follower在pull数据时,也会同步实际的数据,这就保证了,即使master挂了,那么系统也不会崩,因为每个follower都有实际数据,只要另外选取一个master然后在以后的工作中沿袭之前的策略就ok了,镜像集群模式是RabbitMQ的一个策略,如需打开可以通过RabbitMQ的管理界面,rabbitmq有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
(2)Kafka的高可用是基于0.8版本之后的replica副本策略,生产者只能写leader,follower会从leader中pull数据,消费者只能消费leader,如果宕机了,会从follower中重新选举。
重复消费
在Kafka中,可能出现重复消费问题,因为消费者消费过后提交offset是有一定时间间隔的,不是消费后立马就提交offset到zookeeper,如果消费者消费了数据但是还没来得及提交就挂了,zookeeper中没有最新的offset信息,消费者重启后会向kafka索要zookeeper中offset的下一条数据,所以这样就重复消费了。
我们需要做的是在进行消费行为前判断一下,数据库中是否已经存在记录,如果已经存在的话update一下就好,不能再add了。或者也可以使用redis的set幂等性加数据的单一键进行处理。
数据丢失
(1)RabbitMQ
生产者端数据在传输过程中可能存在丢失现象,rabbitMQ中防止数据丢失的方法一个是事务,一个是confirm,由于事务是同步的,confirm是异步的,所以生产中一般使用confirm机制确保消息到达。
RabbitMQ自身也可能将数据丢失,必须开启rabbitmq的持久化,就是消息写入之后会持久化到磁盘,哪怕是rabbitmq自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitmq还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。
具体步骤为:
第一个是创建queue的时候将其设置为持久化的,这样就可以保证rabbitmq持久化queue的元数据,但是不会持久化queue里的数据;
第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时rabbitmq就会将消息持久化到磁盘上去。 两个同时设置才能保证RabbitMQ数据不丢,但也可能在写磁盘时宕机,那数据就丢了。
confirm和持久化可以配合,即生产者传消息给消费者且消息接受并持久化完毕后再返回ack。
消费端主要是消费未完成,autoAck已经传给RabbitMQ了,系统认为已经消费可你却是宕机了,那这条数据就丢了,需要关闭autoAck,并且在消息消费完毕后手动调用api,这样就算宕机,没有发ack给RabbitMQ , 系统也会自动调度别的消费者来消费。
(2)Kafka
Kafka消费端的处理于RabbitMQ相似,关闭offset自动提交即可,改为手动传递。
Kafka本身的leader-follower机制可能会导致一些问题,此时需要设置4个参数
给这个topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本
在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower吧
在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了
在producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了
(3)生产者如果在producer端已经设置参数retries=MAX 那么是不会存在数据丢失问题的
消息乱序
(1)rabbitmq:一个queue,多个consumer,消费的快慢有不同,所以写库顺序也会不同,这不明显乱了
(2)kafka:一个topic,一个partition,一个consumer,内部多线程,之后与rabbitmq乱序类似,这不也明显乱了
那如何保证消息的顺序性呢?
(1)rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理(这一块内存操作还是有点懵)
(2)kafka:一个topic,一个partition,一个consumer,内部单线程消费,写N个内存queue,然后N个线程分别消费一个内存queue即可
持续积压问题
或由于写库操作中库宕机导致消费无法进行,数据挤压与MQ中,或消费端本身出现宕机问题,无法写库。
-
积压的数据量过多
修复consumer,确保其回复正常消费速度,然后将现有消费者停掉。新建一个topic,其中有10倍的patition,临时建立原先10倍 20倍的queue数量。
临时创建一个consumer进行消息分发,不做耗时处理,直接轮询写入建好的queue中。
临时征用10倍的机器来部署consumer,每一批消费一个queue的数据。 -
积压过久过期时间到了,消息没了,需要写程序将消息查回来,重新发送到mq
-
第一个问题出现,但是应急措施处理过慢,只能放弃现有数据,如2中处理,再之后进行数据找回