吊炸天的mq面试总结

消息中间件总体优势:解耦,削峰,异步,但是由于引入了消息中间件,会带来 以下难点:  业务上复杂了,多引入了一个模块。消息的不稳定,消息多重消费,消息丢失。一致性,如果发送abcd,4个消息,但是abc收到了,但是d 收到了,但是消费失败,然后呢?

消息中间件模块宕机了如何?如何补偿。这是引入了消息中间件带来的一系列问题。

 

市场上比较活跃的 就是  activemq, rabbitmq, rocketmq,kafka

 

activemq: 出现较早,典型的pub/sub  订阅消费,但是不适合用于互联网模式下。因为消息的确认,重复消费,可靠性(宕机是如何处理),并发等一系列问题 都是无法很好的支撑解决

 

rabbitmq:  其实它的弊端就是 底层开源的代码不是java,但是他的并发,发送时间的间隔基本很快,在消息确认,可靠性,主从架构方面都做到了处理,所以不会有问题,而且开源社区比较活跃,适合中小型公司使用

 

rocketmq: 阿里出品嘛,必属精品。适用于分布式下,多消息丢失什么都做到了0丢失。高并发下弱于rabbitmq, 整体性能也是不错的。底层代码也是java的。但是就怕阿里社区黄点。除非公司有大牛,否则一般目前不建议考虑。

kafka:用于大数据场景下, 数据量大的情况,日志收集什么的。

单机吞吐量10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景

 

 

 

 

吊炸天的mq面试总结

 

 

吊炸天的mq面试总结

 

 

 

 

RabbitMQ的高可用性

 

RabbitMQ是比较有代表性的,因为是基于主从做高可用性的,我们就以他为例子讲解第一种MQ的高可用性怎么实现。

 

rabbitmq有三种模式:单机模式,普通集群模式,镜像集群模式

 

1)单机模式

 

就是demo级别的,一般就是你本地启动了玩玩儿的,没人生产用单机模式

 

2)普通集群模式

 

意思就是在多台机器上启动多个rabbitmq实例,每个机器启动一个。但是你创建的queue,只会放在一个rabbtimq实例上,但是每个实例都同步queue的元数据。完了你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。

 

这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个queue所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。

 

而且如果那个放queue的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让rabbitmq落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个queue拉取数据。

 

所以这个事儿就比较尴尬了,这就没有什么所谓的高可用性可言了,这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。

 

3)镜像集群模式

 

这种模式,才是所谓的rabbitmq的高可用模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。

 

这样的话,好处在于,你任何一个机器宕机了,没事儿,别的机器都可以用。坏处在于,第一,这个性能开销也太大了吧,消息同步所有机器,导致网络带宽压力和消耗很重!第二,这么玩儿,就没有扩展性可言了,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue

 

那么怎么开启这个镜像集群模式呢?我这里简单说一下,避免面试人家问你你不知道,其实很简单rabbitmq有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

普通集群模式

以下的模式提高的吞吐量,可以多个消费者链接到不同实例上获取数据。

 

 

吊炸天的mq面试总结

 

镜像集群模式

 

吊炸天的mq面试总结

 

kafka出现的重复消费问题

 

吊炸天的mq面试总结

(1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧

 

(2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性

 

(3)比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个idredis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

 

还有比如基于数据库的唯一键来保证重复数据不会重复插入多条,我们之前线上系统就有这个问题,就是拿到数据的时候,每次重启可能会有重复,因为kafka消费者还没来得及提交offset,重复数据拿到了以后我们插入的时候,因为有唯一键约束了,所以重复数据只会插入报错,不会导致数据库中出现脏数据

 

如何保证MQ的消费是幂等性的,需要结合具体的业务来看

吊炸天的mq面试总结

rabbit mq 如何保证顺序

吊炸天的mq面试总结

关于这个事儿,我们一个一个来梳理吧,先假设一个场景,我们现在消费端出故障了,然后大量消息在mq里积压,现在事故了,慌了

 

(1)大量消息在mq里积压了几个小时了还没解决

 

几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上很晚,10点多,11点多

 

这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复consumer的问题,让他恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。

 

一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟是18万条,1000多万条

 

所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来

 

一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下:

 

1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉

2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量

3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue

4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据

5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据

6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息

 

吊炸天的mq面试总结

 

其实回答这类问题,说白了,起码不求你看过那技术的源码,起码你大概知道那个技术的基本原理,核心组成部分,基本架构构成,然后参照一些开源的技术把一个系统设计出来的思路说一下就好

 

比如说这个消息队列系统,我们来从以下几个角度来考虑一下

 

(1)首先这个mq得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下kafka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?

 

(2)其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘,才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是kafka的思路。

 

(3)其次你考虑一下你的mq的可用性啊?这个事儿,具体参考我们之前可用性那个环节讲解的kafka的高可用保障机制。多副本 -> leader & follower -> broker挂了重新选举leader即可对外服务。

 

(4)能不能支持数据0丢失啊?可以的,参考我们之前说的那个kafka数据零丢失方案

 

其实一个mq肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。