1.为什么用MQ?
从MQ的三个主要的作用进行回答。
解耦:当一个系统需要拓展一个新的模块时,在已有的模块基础上如果不使用MQ则需要单独针对新的模块编写新的接口来进行发送消息;系统模块的拓展和撤销更复杂,所以使用MQ后只需要同一将消息发往MQ,然后不同的模块消费对应的消息,不需要对已有的模块再进行复杂的更改或添加操作。
异步:使用MQ在用户请求系统A后,系统A只需要将消息发往消息队列后就可以响应用户的请求,不需要管其他系统模块从MQ中获取消息进行消费的耗时,这样可以提高用户体验,达到异步的消息处理。
削峰:当同一时间有大量的请求直接打到服务器后,需要同时进行大量的数据操作,这时可能会存在系统崩溃的现象,所以使用MQ直接来接收请求可以将大量的请求先打到MQ中,系统再按照最大所能处理的请求数进行消费,同一时间不能被消费的消息就暂时堆积在MQ中,这样可以缓解服务器的压力。
2.使用MQ的可能存在的问题?
问题1:系统的可用性降低,如果MQ崩溃则会导致整个系统的崩溃。
问题2:系统要考虑的问题变多,比如:重复消费,消息丢失,顺序错误等等
问题3:一致性问题,系统A发送消息到MQ,需要其他的BCD三个系统都消费成功后才能进行返回,结果,系统BC执行成功了,D执行失败了,导致给用户返回的成功,但是后台逻辑上错误,导致并没有完全执行完。
3.对比各个MQ的优缺点
特性 | ActiveMQ | RabbitMQ | RocketMQ(阿里开源) | Kafka |
单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。
一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic数量对吞吐量的影响 |
|
| topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降
这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降
所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 |
时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 |
可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 |
| 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 |
功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用
偶尔会有较低概率丢失消息
而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本
而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用
| erlang语言开发,性能极其好,延时很低;
吞吐量到万级,MQ功能比较完备
而且开源提供的管理界面非常棒,用起来很好用
社区相对比较活跃,几乎每个月都发布几个版本分
在国内一些互联网公司近几年用rabbitmq也比较多一些
但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。
而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。
而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障
日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景
而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码
还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展
同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量
而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略
这个特性天然适合大数据实时计算以及日志收集 |
4.引入消息队列后怎么保证高可用?
4.1RabbitMQ(非分布式MQ)
RabbitMQ的镜像集群模式:创建一个queue都会存在于多个实例上,每一节点都有这个queue的一个完整镜像,就是包含了这个queue的全部数据的消息,所以这种集群模式叫做镜像集群模式。任何一个节点宕机了,其他节点上还有完整的数据,别的consumer都可以到其他活着的节点上去消费数据。
缺点:不是分布式的,如果queue的数据量很大,大到这个机器上的容量无法容纳了,怎么办?
4.2kafka(分布式架构)
生产者发布消息后,对应的每个消息队列都有一个leader和follower,当leader宕机后kafka会自动识别出宕机的机器,然后通过选举将对应的follwer选举成leader这时候可以继续,因为follower和leader的数据是一样的所以可以继续为消费者进行提供消息进行消费,不会影响后续的系统流程。
5.消息重复问题和解决方案
问题出现的场景:
幂等性:通俗的说就是一个数据h或者一个请求,给你重复的来多次,系统必须保证对应的数据是不会变的,不能出错。例如一条数据重复出现两次,数据库在插入时只有一条数据,则就保证了系统的幂等性。
如何保证MQ重复消费的幂等性?
写一条数据的同时,往内存中Set中放一条数据,如果说再次拿到相同的一条数据的时候,先区判断内存Set中是否存在一条这样的数据,如果说有就不再往数据库进行插入。
通常幂等性的解决思路:(如何解决密等性需要结合具体业务来看)
- 在每次消费了一条MQ之后需要往一个地方插入一条消息进行记录,基于内存的数据结构或者redis都可以,然后下一次消费MQ的时候就进行一次去重操作,重复了就不再进行消费。
- 基于数据库的唯一键保证数重复数据不会重复插入多条。(对上线系统,就有这个问题,就是拿到数据的时候,每次重启可能会有重复的,因为kafka消费者消费之后还没来得及提交offset,重复的数据拿到以后进行插入,因为有唯一键的约束,所以重复数据只会插入报错,不会导致数据库中出现脏数据。)
6.MQ消息丢失怎么办?(如何保证消息的可靠性传输)
6.1RabbitMQ可能存在的消息丢失问题
从生产者--》MQ--》消费者都可能造成消息的丢失。
6.1.1保证生产者不丢失消息
方案1(不推荐):
rabbitmq支持事务,生产者在发送消息之前先打开一个事务,当发送消息发生异常,catch捕获异常之后进行channel.txRollback事务回滚,然后再次重试发送消息。
使用事务可能造成的问题:事务机制是同步的,生产者发送这个消息会同步阻塞卡住等待这个消息是发送成功还是发送失败,会导致生成者发送消息的吞吐量会降下来。
方案2(推荐):
confirm的模式进行确认消息是否发送成功。使用的是回调的机制,异步的模式,不会造成阻塞,生产者可以继续发送下一条消息,这样吞吐量不会受到影响。
6.1.2保证MQ不丢失消息
持久化操作:开启rabbitmq的持久化,就是消息写入后持久化到磁盘,哪怕是MQ挂掉,可以读取之前的持久化数据一般数据不会丢失。
6.1.3保证消费者不丢失消息
关闭消费者的autoAck机制,消费者通过自己判断消费者是否处理完,处理完后发送ack给MQ。
6.2kafka的消息丢失问题
6.2.1消费者丢失消息
唯一可能出现丢失的问题:消费者在消费时还没有消费完成,消费者自动提交了offset,让kafka自动以为你已经提交了这个消息,其实还未完成,这时消费者挂掉后再重启消息就会发生丢失。
解决方案(类似rabbitmq):手动提交offset
6.2.2kafka自己丢失消息
丢失的问题:当生产者发送数据1给broker1,然后broker1还没来得及同步就挂掉了,这是重新选举broker2变成了leader,但是这时broker2是没有数据1的,消费者也就不能对数据1进行消费。
解决大方案:
一般是要求起码设置如下4个参数:
- 给这个topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本
- 在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower吧
- 在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了
- 在producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了
我们生产环境就是按照上述要求配置的,这样配置之后,至少在kafka broker端就可以保证在leader所在broker发生故障,进行leader切换时,数据不会丢失
6.2.3生产者是否会丢失数据?
如果按照上述的参数设置了acks=all,这是生产者一定不会丢失数据,因为必须leader接受到消息并且所有的follower同步了消息才会认为这次的消息发送是成功的。如果没有满足条件生产者会重试无限次发送。
7.怎么保证MQ中的消息按顺序来执行
7.1针对RabbitMQ的消息错乱
RabbitMQ可能出现数据顺序错误的场景:
一个queue,多个consumer,这不明显乱了
即当生产者发送的消息的先后顺序是数据1,数据2,数据3,都发给queue1,然后再交给不同的消费者,这时消费者消费的速度不一样可能消费者2先消费完成就会造成数据2先插入数据库。
解决方案:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理
7.2针对kafka消息错乱
kafka消息错乱的场景:
一个topic,一个partition,一个consumer,内部多线程,这不也明显乱了
kafka保证消息的数序:
一个topic,一个partition,一个consumer,内部单线程消费,写N个内存queue,然后N个线程分别消费一个内存queue即可
8.消息队列其他问题
8.1如何快速处理挤压过多的消息
情景:消费者挂掉后再重启,这时会有很多已经挤压了的消息在MQ中,所以需要快速的处理之前已经挤压了的消息。
可以将原由的消费者进行修改,不再直接对数据库进行操作,只对MQ的数据进行并将处理得到的数据再发给新的partition(可以创建很多来进过度),然后再由很多消费者进行同时的消费,并写入数据库。这是处理速度就会很快。因为第一次的消费者处理之后并不用写入数据库就可以给MQ反馈已经消费了;后续的消费者继续处理需要消费的数据并不会影响之前的消费者处理的速度。
8.2消息满了怎么处理
类似上面的处理方式,可以将消费者改一下,将处理的消息重新写入一个临时的MQ中,然后后续再由其他的消费者进行消费,并写入数据库。
8.3消息过期
一般不会设置消息过期,如果设置了消息过期,只能另外写一个程序将消息过期的数据查出来并补上过期消息的消费。
9.如果让你开发消息队列中间件,如何来设计架构?
- 考虑mq的伸缩性,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下kafka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,这样就可以存放更多数据,提供更高的吞吐量了。
- 考虑mq数据要不要持久化?需要写入磁盘,这样才能保证挂了之后不会造成数据的丢失。写入时怎么进行写入磁盘?顺序写入,这样既保证了消息的顺序性,还减少了寻址的开销,达到提高性能的目的。
- 考虑mq的可用性?参考kafka的高可用的保障机制。多副本--》leader&follwer--》broker挂了重新选举leader即可。
- 保证数据的0丢失?参考kafka的0丢失的机制。