Java面试冲刺--一起来搞懂【消息队列】

532 阅读10分钟

1 消息队列的常见使用场景(为什么使用消息队列)

**解耦,异步,削峰**

1.1解耦

不用MQ的耦合场景:假设有系统A发送数据到B,C,D三个系统,此时增加系统E,则系统A需要修改对应代码。若是B系统弃用。则也需要修改A系统代码。

使用MQ的解耦场景:若是加入MQ消息队列系统,系统A就可以将需要发送的数据发送到消息队列,B、C、D系统均可以自行消费消息队列里边的数据。此时若是加入E系统,则自行消费消息队列里边的数据就行。若是弃用B、C、D、E,取消对消息队列数据的消费即可。此时,系统A和其他系统彻底解耦

1.2异步

不使用MQ的同步高延时场景:用户对系统A发起请求,并且系统A调用系统B、C、D三个系统,总耗时将近1秒。并且B、C、D三个系统的处理结果并不及时同步返回给用户。这样用户使用系统A发送请求需要近一秒才有返回,体验极差。

使用MQ的异步接口性能优化:若是在系统A和B、C、D之间加入消息队列,则用户请求系统A之后,系统A只需将需要调用B、C、D系统的三条消息发送到消息队列中,B、C、D系统只需监听各自需要的数据类型,若是有消息进行消费即可。此时对于用户来说调用系统A只是花费了20ms,几乎是无感知的。用户体验极好。

1.3削峰

不使用MQ时高峰期系统崩溃的场景:每天的12点左右,用户对系统A请求为每秒5000条,但是MySQL数据库每秒仅能处理2000条数据,此时系统崩溃。

使用MQ进行削峰的场景:将用户的大量请求发送到消息队列中,每秒只是向A系统发送2000条,则确保了A系统的稳定性,不会崩溃。虽然有消息的积压,但是等高峰期一过,则将会被快速的消费掉。

2 消息队列的优点和缺点

2.1优点:

上边说过的解耦,异步,削峰

2.2缺点:

  1. 系统可用性降低:一旦MQ出现故障,则整个系统崩溃,A无法发送消息,BCD无法消费消息,无法正常运转。
  2. 系统的复杂性变高:多加了一个系统,便有更多的问题,比如如何保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?
  3. 一致性问题:当用户请求系统A时,消息发送到MQ成功即给用户返回成功。但是系统B、C消费消息成功,但是系统D处理失败。此时结果应为失败,但是已给用户返回成功。

3 Kafka、activemq、rabbitmq、rocketmq都有什么优缺点啊

4 如何保证消息队列的高可用性

4.1RabbitMQ的高可用性

基于主从做高可用

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

1.单机模式:用作demo,或者本地测试用。

2.普通集群模式

假如rabbitmq集群有三台机器,此时向集群中的MQ-2机器写入一条消息,则MQ-1和MQ-3只会同步这个queue的元数据,即这条消息存在哪个机器上等一些配置信息,并不会同步实际数据。当消费者在MQ-3上请求消费这条消息时,MQ-3会根据元数据的信息向MQ-2请求这条queue的实际数据并将它返回给消费者。

3.镜像集群模式:

假如rabbitmq集群有三台机器,此时向集群中的任意一个节点发送消息,那么这条消息会被同步到其余的节点。当消费者消费消息的时候,可以从任意一个节点消费消息。

任何一个节点宕机,其他节点上还包含了完整的数据,消费者可以读取并消费数据。

4.2Kafka的高可用性

**Kafka是一个纯分布式的MQ架构。**一个kafka集群是由多个broker组成,每个broker是一个节点;每创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition存一部分数据。

就是说每个topic的数据是分开存放在多个机器上的,每个机器存放一部分数据。

高可用性:每个partition都有多个副本,多个副本的数据相同,并在多个副本中选举一个成为leader,其余的则为follower。生产者和消费者只与leader进行交互,即只有leader对外有读写的服务。写入数据到leader的时候,leader会将数据同步到它的follower上去,即follower只是被动同步leader的数据。此时高可用架构成型,若是某一台leader机器宕机,kafka会感知到并且从这个leader的follower机器中重新选举出一个leader。

5 如何保证消息不被重复消费

5.1为什么会有重复数据

kafka消费端可能出现的重复消费的问题:

offset:kafka会给每条消息分配表示消息顺序的唯一标识,这个标识就叫offset。

消费者消费数据是按照offset的顺序去消费的。消费者会按照一定的时间周期提交offset到zookeeper,表示自己消费到了哪条数据。

若是在消息2消费完之后,但是并没有到提交offset的时间,此时发生故障,断开连接亦或消费端重启。恢复正常之后,消费者向kafka请求继续消费数据,此时kafka查看zookeeper为消息1,2,3消费之前。此时kafka将会把消息1,2,3再次发送给消费者。导致消息1,2被重复消费。

5.2如何保证消息不被重复消费(保证消息的幂等性)

  1. 消费端直接判断消息是否重复,舍弃重复的消息。
  2. 在内存中用redis做一个记录,每次收到消息的时候进行比较。
  3. 基于数据库的唯一键来保证

6 如何避免消息的丢失

6.1 RabbitMQ

可能的情况:

  1. 生产者丢失数据:写消息到RabbitMQ的时候,消息在网络传输中丢失。并没有成功写入RabbitMQ。
  2. RabbitMQ丢失数据:RabbitMQ收到消息之后,暂存在在即的内存里。在消费者消费之前RabbitMQ故障重启,导致数据丢失。
  3. 消费者丢失数据:消费者取到数据,还未消费,消费者故障重启,导致数据丢失。

解决方案:

1.生产者丢失数据:

1)使用RabbitMQ提供的事务功能:在 生产者发送消息时创建事务(channel.txSelect),若是消息发送失败,则回滚事务(channel.txRollback),重新发送这条消息。消息发送成功,则提交事务(channel.txCommit)。

事务机制是同步的,生产者发送的各个消息会同步阻塞,等待返回结果。会影响生产发送消息的吞吐量。

2)confirm模式:可以把对应的channel设置成confirm模式,在RabbitMQ接收到之后,会回调生产者的回调接口,告知生产者消息是否发送成功。

生产者主要使用confirm机制保证消息不丢失,它是异步的模式,不会阻塞,不影响性能。

2.RabbitMQ丢失****数据

开启RabbitMQ的持久化,设置持久化又两个步骤:

1)在创建queue的时候设置为持久化的。这样不会持久化数据,只会持久化queue的元数据。

2)发送消息的时候将deliveryMode设置为2,即将消息设置为持久化的。

3.消费者丢失数据

消费者消费完成后回调rabbitMQ发送ack确认消息。

6.2 Kafka

1.消费者丢失数据

kafka会自动提交offset,只要关闭自动提交,处理完之后手动提交,就可以保证数据不会丢。

2.kafka丢失数据

当某个broker宕机,恰好这个broker是一个leader,数据还没有被follower同步,此时重新选举leader之后,就少了一些数据。

解决方案,设置以下四个参数:

topic设置replication.factor参数:这个值需要大于1,即为每个partition必须有两个副本。

kafka服务端设置min.insync.replicas参数:这个值需要大于1,要求一个leader至少感知到一个follower与自己保持联系。

producer端设置acks=all:要求每个数据,写入所有replica之后,才认为是写成功了。

producer端设置retries=MAX:一旦写入失败,无限重试。

3.生产者丢失数据

当设置acks=all时,生产者不会丢失数据。

7 如何保证消息的顺序性

7.1 Rabbit可能出现的消息顺序不正确的情况及解决方案

1.消息顺序不正确的情况:

当RabbitMQ收到消息发送给消费者的时候,若是有多个消费者,且把消息发给了不同的消费者。此时,消息的顺序性没法保证,任何一个都可能先执行。

2.解决方案:

一个queue对应一个消费者,在同一个queue内保证消息的顺序性即可。

7.2 kafka可能出现的消息顺序不正确的情况及解决方案

1.情况

在kafka中写入同一个partition的数据一定是有顺序的,生产者在写入消息的时候可以指定一个key,拥有这个key的消息会被发送到同一个partition中。

一个partition的消息只会发送给一个消费者,三个partition的消息只会分别发送给三个消费者,若是有第四个消费者,那么它将收不到消息。

问题出在消费者多线程消费消息,消费者接收到的消息是有序的,但是消费者消费消息的多线程无法确定消息的有序性。

2.解决方案

在消费消息之前,将同一个key的消息放到同一个内存队列,由同一个线程消费。

8 如何解决消息队列的延时以及过期失效问题

8.1 消息积压了几百万条在消息队列怎么办

  1. 修复故障的consumer,使其可以正常工作。
  2. 新建一个topic,partition是原来的10倍,临时建立原先设备的queue数量
  3. 写一个临时分发的consumer程序,消费积压的消息,并均匀存储到临时建立的新的queue
  4. 临时征用10倍的机器部署consumer,每一批consumer消费一个临时的queue的数据
  5. 这样就会以10倍的速度消费掉积压的数据

8.2 RabbitMQ,设置了过期时间,消息过期清除了怎么办

这个时候只能批量重新导入,写一个程序将丢失的数据查出来,重新写入MQ。

8.3 消息积压再MQ里,长时间没处理掉,快满了怎么办

这个是第二个问题没有及时处理,只能写临时的消费者,直接写到其他的临时的MQ里边或者直接舍弃消息,快速消费消息,等到峰值过去,再重新写。