携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情
消息队列
1、为什么使用消息队列?
核心内容:异步、解耦、削峰
2、消息队列有什么优缺点?
系统可用性降低、系统复杂性提高、一致性问题
3、kafka、activemq、rabbitmq、rocketmq都有什么区别以及适合那些场景?
特性
ActiveMQ
RabbitMQ
RocketMQ
Kafka
单机吞吐量
万级,比 RocketMQ、Kafka 低一个数量级
同 ActiveMQ
10 万级,支撑高吞吐
10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景
topic 数量对吞吐量的影响
topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic
topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源
时效性
ms 级
微秒级,这是 RabbitMQ 的一大特点,延迟最低
ms 级
延迟在 ms 级以内
可用性
高,基于主从架构实现高可用
同 ActiveMQ
非常高,分布式架构
非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性
有较低的概率丢失数据
基本不丢
经过参数优化配置,可以做到 0 丢失
同 RocketMQ
功能支持
MQ 领域的功能极其完备
基于 erlang 开发,并发能力很强,性能极好,延时很低
MQ 功能较为完善,还是分布式的,扩展性好
功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用
4、如何保证消息队列的高可用?
(1)RabbitMQ的高可用性
rabbitmq有三种模式:单机模式、普通模式、镜像集群模式
1)单机模式
生成没人会用单机模式
2)普通集群模式
3)镜像集群模式
(2)kafka的高可用
5、如何保证消息不被重复消费(如何保证消息消费时的幂等性)?
(1)比如消费者拿数据是要写库的,先根据主键查一下,有就不插入或者update,否则就插入
(2)比如消费者是要写redis,反正每次都是set,天然幂等性
(3)生产者发送每条数据时,加一个全局的唯一id,类似订单id,到消费者消费的时候,先拿id去比如redis里查一下,之前若没消费过,就处理id写redis,若消费过,就不处理了,保证别重复消费相同 的消息即可
(4)或者基于数据库的唯一键来保证重复数据不会重复插入多条,跟方法1类似
6、如何保证消息的可靠性传输(如何处理消息丢失的问题)?
(1)RabbitMQ
1)生产者弄丢了数据
可以选择用RabbitMQ提供的事务功能,就是生产者发送数据之前开启RabbitMQ事务(channel.txSelect),然后发送消息,如果消息没有被RabbitMq接收到,那么生产者会受到异常报错,此时可以回滚事务(channe.txRollback),然后重试发送消息,如果收到了消息,那么可以提交事务(channel.txCommit)。但是事务机制一搞,基本上吞吐量会下来,太耗性能。(事务机制是同步的,生产者发送消息会同步阻塞卡主等待成功还是失败)
也可以用confirm模式,先把channel设置成confirm模式,如果RabbitMQ接收到这条消息的话,就会回调生产者本地的一个接口,通知这条消息已经收到,如果接收失败,就会回调这个接口通知失败(这种模式是异步模式,吞吐量可以提高)
2)RabbitMQ弄丢了数据
就是RabbitMQ自己弄丢了数据,这是必须开启RabbitMQ的持久化,就是消息写入之后会持久化到磁盘,哪怕是RabbitMQ自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢,除非机器罕见的是,RabbitMQ还没持久化,就自己挂了,可能导致少量数据会丢失,但是这个概率较小
设置持久化有两个步骤,第一是穿件queue的时候将其设置为持久化,这样就可以保证RabbitMq持久化queue的元数据,但是不会持久化queue里的数据,第二个是发送消息的时候的delivery设置为2,就是将消息设置为持久化,此时RabbitMQ就会将消息持久化到磁盘上去,必须同时设置这两个持久化才行
而且持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了。
3)消费者弄丢了数据
一般是打开了消费者的autoAck的机制,消费到数据之后,消费者会自动通知RabbitMQ,说已经消费完这条记录,如果消费了一条消息,还在处理中,还没处理完就通知RabbitMQ说已经消费了,刚好,消费者宕机了,但是RabbitMQ已经认为这个消息已经处理掉了
(2)kafka
1)消费端弄丢了数据
唯一可能导致消费者弄丢数据的情况,就是说,消费到了这个消息,然后消费者那边自动提交了offset,让kafka以为你已经消费好了这个消息,其实你刚准备处理这个消息,你还没处理,自己就挂了,此时消息就丢了。
只需要关闭自动提交offset,在处理完成之后手动提交,就可以保证数据不会丢。
2)kafka弄丢了数据
相当于生产者发送数据到leader中,但是leader还没来得及同步给相应的follower,结果leader突然宕机,kafka就会从其余的follower中选举出一个leader,这样就导致了数据丢失
此时需要设置4个参数
给这个topic设置replicatio.factor参数:这个参数必须大于1,要求每个partition必须有至少两个副本
在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower
在producer端设置acks=all,这个是要求每条数据,必须是写入所有replice之后,才能认为是写成功了
在producer端设置retries=MAX,这个是要求一旦写入失败,就无限重试
3)生产者不会弄丢数据
7、如何保证消息的顺序性
消息错乱的场景
(1)RabbitMQ:一个queue,多个consumer
(2)Kafka:一个topic,一个partition,一个consumer,内部多线程
如何保证消息的顺序性
(1)RabbitMQ:拆分多个queue,每个queue一个consumer,就是多一些queue而已
8、如何解决消息队列的延时以及过期失效问题?
假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。
这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。
假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
9、消息队列满了以后改怎么处理?
临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后到了晚上再补数据吧。
10、有几百万消息持续积压几小时,怎么解决?
一个消费者一秒是 1000 条,一秒 3 个消费者是 3000 条,一分钟就是 18 万条。所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概 1 小时的时间才能恢复过来。
一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:
-
先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
-
新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
-
然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
-
接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
-
等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。
11、如果让你写一个消息队列,该如何进行架构设计?说一下思路?
以下几个角度来考虑一下:
首先这个mq得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下kafka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘,才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是kafka的思路。
其次你考虑一下你的mq的可用性啊?这个事儿,具体参考我们之前可用性那个环节讲解的kafka的高可用保障机制。多副本 -> leader & follower -> broker挂了重新选举leader即可对外服务。
能不能支持数据0丢失啊?可以的,参考我们之前说的那个kafka数据零丢失方案