0%

消息队列设计

[toc]


Q: 消息队列的作用是什么?#

A:

  • 解耦:
    通过一个MQ,发布和订阅模型(Pub/Sub模型),系统A就和其它系统彻底解耦。
    需要考虑一下负责的系统中,是否有类似的场景,就是一个系统或者一个模块,调用了多个系统,互相之间的调用很复杂,维护起来很麻烦。(新增、删除接口都是要两边互相适配)
    但是其实这个调用是不需要同步调用接口的(不需要等待返回),如果用MQ给他异步化解耦,也是可以的,这个时候可以考虑在自己的项目中,是不是可以运用这个MQ来进行系统的解耦。

  • 异步:
    加快接口的返回。

  • 削峰
    就是大量的请求过来,然后MQ将其消化掉了,然后通过其它系统从MQ中取消息,在逐步进行消费,保证系统的有序运行。一般高峰期不会持续太长,在一段时间后,就会被下游系统消化掉。


Q: 消息队列都有什么缺点?#

A:

  • 系统可用性降低: MQ挂掉的话很危险
  • 系统复杂性提高:要考虑消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性
  • 一致性问题:存在关联的消息,被不同消费者消费,如果另一个消费者执行失败,如何感知和回退?

Q: Kafka、activeMQ、RibbitMQ、RocketMQ都有什么优缺点?#

A:
列出一个表格
63e89c47a42f0a17c97a8a2a303e4aeabd0a6de1

简单记忆rabitMq和kafka的区别

  1. kafka高吞吐,适合大数据量的实时计算、日志采集。 但rabitMq的时延更小。
  2. rabitMq基于主从, kafka则支持分布式(多副本)
  3. rabitQq基于erlang开发, kafka用scala开发。

Q: 如何保证消息队列的高可用?不会因为1台消息队列服务挂掉导致服务失灵?#

A:
只讲一下kafka的

每个partition属于多台机器。
有一个是leader节点
leader会把数据同步到另外2台机器。
如果leader挂了,则消费者选择读取 这个partition的另外2台机器

假设其中的一个leader宕机了,但是因为每个leader下还有多个follower,并且每个follower都进行了数据的备份,因此kafka会自动感知leader已经宕机,同时将其它的follower给选举出来,作为新的leader,并向外提供服务支持。
45e7b2c323914c2bcc4ed65a2d3d872471f920ed


Q: 怎么知道leader掉线?#

A:
对于Kafka而言,定义一个Broker是否“活着”包含两个条件:

一是它必须维护与ZooKeeper的session(这个通过ZooKeeper的Heartbeat机制来实现)。
二是Follower必须能够及时将Leader的消息复制过来,不能“落后太多”。
Leader会跟踪与其保持同步的Replica列表,该列表称为ISR(即in-sync Replica)。如果一个Follower宕机,或者落后太多,Leader将把它从ISR中移除

更详细的解释,包括如何感知掉线(ack、zk-session)、如何选举
Kafka学习之路 (三)Kafka的高可用


Q: 如何保证消息不会被重复消费?#

A: 需要消息消费者保证幂等性, 同样的消息,消费2次,结果是一样的。

幂等性是什么?通俗点说:幂等性就是一个数据,或者一个请求,以相同的内容和方式给你执行多次,得保证对应的数据不会改变,并且不能出错,这就是幂等性。(这样才能做到发送者搞重试或者多发问题)


Q: 是如何保证消息消费时一定是幂等的?#

A:
需要应用服务器消费消息时是幂等的, 注意消息队列不保证幂等
消费中如果是insert相关,且只会insert1次的,通过主键判断,避免重插(消费端)
消费端业可以加一个redis, 以缓存消费过的记录, 重复消费可以通过redis识别,并且redis是临时缓存,不会占用太多资源。


Q: 消息队列 mq 怎么保证顺序消费?#

A:
abbitmq 中, 每个消费者对应一个队列
kafka中, 每个消费者对应一个 partition。 partion中是有序的。

即kafka能保证塞入partion时是有序的
因此你要求有序的那堆请求,要有相同的key映射到同一个partion

同时消费者处理的时候,也要按照核心key在内存中分配给不同的线程(内存线程使用加锁队列去获取消息), 避免多线程处理的时候出现混乱
e536b0c4b51e6b9230a6f586b3cb7c65fc9f23d9


Q: 如何保证消息的可靠性传输,不会丢失?#

A:
生产者发送到 MQ的时候丢了: 生产者使用ack机制,如果超时没收到,就回调nack接口做重发

MQ没发给消费者: 消息持久化,如果MQ挂了,还可以从磁盘中恢复重发。(ack应该在存盘后再发给生产者)

消费端没收到数据或者消费者挂了:
关闭MQ的自动ack, 在消费者的代码逻辑里自己实现ack机制,保证是自己处理完成后才发ack,而不是收到了就发ack。
对于kafka来说, 消费者的ack其实就是offset。 offset不能自动发,要自己实现。


Q:消息队列满了, 发生阻塞积压怎么办?例如突然流量峰值, 几百万消息持续积压?#

A:
运维根据告警信息, 对queue资源和consumer资源都临时进行紧急进行人工扩容。


Q: 如果让你写一个消息队列,该如何进行架构设计,说一下你的思路?#

A:

  1. 首先MQ得支持可伸缩性
    那就需要快速扩容,就可以增加吞吐量和容量,可以设计一个分布式的系统,参考kafka的设计理念,broker - > topic -> partition,每个partition放一台机器,那就存一部分数据,如果现在资源不够了,可以给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多的数据,提高更高的吞吐量

  2. 其次得考虑一下这个MQ的数据要不要落地磁盘?也就是需不需要保证消息持久化,因为这样可以保证数据的不丢失,那落地盘的时候怎么落?顺序写,这样没有磁盘随机读写的寻址开销,磁盘顺序读的性能是很高的,这就是kafka的思路。

  3. 其次需要考虑MQ的可用性?这个可以具体到我们上面提到的消息队列保证高可用,提出了多副本 ,leader 和follower模式,当一个leader宕机的时候,马上选取一个follower作为新的leader对外提供服务。

  4. 需不需要支持数据0丢失?可以参考kafka零丢失方案