因为之前的项目中有涉及到MQ的相关使用,当时架构已经设计好了,我只负责代码的编写,但是后来想想不应该是这么回事,所以把MQ的相关知识结合一些视频进行了整理。
目前市面上主流的有四种MQ,分别为ActiveMQ,RabbitMQ,RocketMQ,以及Kafka。而用的比较多的分别为RabbitMQ以及Kafka。
画重点的两款MQ是博主最近学习的,之后的相关讨论也将围绕这两者展开,毕竟两者在是否为分布式方面都有自己独特的优点,比较有代表性。
项目里使用消息队列,主要的三点好处为解耦、异步和削峰。
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中处理,再之后进行数据找回