带你整理面试过程中关于消息队列(RabbitMQ/RocketMQ/Kafka)的相关知识点
Posted 南淮北安
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你整理面试过程中关于消息队列(RabbitMQ/RocketMQ/Kafka)的相关知识点相关的知识,希望对你有一定的参考价值。
Kafka:不支持事务,对于消息重复、丢失、错误没有严格要,适用大量数据的互联网服务
RabbitMQ:对数据的一致性、稳定性、可靠性要求很高的场景,对性能和吞吐量要求其次
RocketMQ:具有高吞吐量,高可用性,主要用于阿里内部
文章目录
一、RabbitMQ 如何保证高可用?
RabbitMQ 有三种部署方式:
- 单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
- 普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
- 镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案
镜像模式集群,这种方式的好处就在于, 任何一个服务宕机了,都不会影响整个集群数据的完整性, 因为其他服务中都有queue的完整数据, 当进行消息消费的时候,连接其他的服务器节点一样也能获取到数据.
缺点:
- 性能开销大,因为需要进行整个集群内部所有实例的数据同步
- 无法线性扩容: 因为每一个服务器中都包含整个集群服务节点中的所有数据, 这样如果一旦单个服务器节点的容量无法容纳了怎么办?
二、如何保证消息队列的高性能?
消息队列的高性能主要是通过它的幂等性、可靠性、顺序性体现的
1. 幂等性
所谓的幂等性其实就是保证同一条消息不会重复或者重复消费了也不会对系统数据造成异常。
比如:
拿RabbitMQ来说的话,消费者在消费完成一条消息之后会向MQ回复一个ACK(可以配置自动ACK或者手动ACK) 来告诉MQ这条消息已经消费了。假如当消费者消费完数据后,准备回执ACK时,系统挂掉了,MQ是不知道该条消息已经被消费了。所以重启之后MQ会再次发送该条消息,导致消息被重复消费,如果此时没有做幂等性处理,可能就会导致数据错误等问题。
保证幂等性的措施:
- 消费数据为了单纯的写入数据库,可以先根据主键查询数据是否已经存在,如果已经存在了就没必要插入了。或者直接插入也没问题,因为可以利用主键的唯一性来保证数据不会重复插入,重复插入只会报错,但不会出现脏数据。
- 消费数据只是为了缓存到redis当中,这种情况就是直接往redis中set value了,天然的幂等性。
- 针对复杂的业务情况,可以在生产消息的时候给每个消息加一个全局唯一ID,消费者消费消息时根据这个ID去redis当中查询之前是否消费过。如果没有消费过,就进行消费并将这个消息的ID写入到redis当中。如果已经消费过了,就无需再次消费了。
2. 可靠性
可靠性就是保证了对于消息投递失败和消息丢失问题的处理
出现消息丢失的三个情况:
(1)生产者弄丢了消息
生产者在将数据发送到MQ的时候,可能由于网络等原因造成消息投递失败
解决办法:
- 发送数据前开启事务,缺点就是因为事务机制,导致吞吐量下降,消耗性能
- 开启confirm 模式
事务机制和 confirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是 confirm机制是异步的,你发送个消息之后就可以发送下一个消息,RabbitMQ 接收了之后会异步回调confirm接口通知你这个消息接收到了。一般在生产者这块避免数据丢失,建议使用 confirm 机制。
(2)MQ自身弄丢了消息
未开启RabbitMQ的持久化,数据存储于内存,服务挂掉后队列数据丢失;
开启了RabbitMQ持久化,消息写入后会持久化到磁盘,但是在落盘的时候挂掉了,不过这种概率很小
解决办法:开启持久化机制,将消息落盘
- 创建queue时,设置为持久化队列
- 发送消息时将消息的deliveryMode设置为持久化
(3)消费者丢失了消息
消费者刚接收到消息还没处理完成,结果消费者挂掉了…
解决办法:关闭自动ACK,使用手动ACK
每次处理完消息之后,再手动ack一下。不过这样可能会出现刚处理完还没手动ack确认,消费者挂了,导致消息重复消费,不过我们只需要保证幂等性就好了,重复消费也不会造成问题。
3. 顺序性
消息在投入到queue的时候是有顺序,如果只是单个消费者来处理对应的单个queue,是不会出现消息错乱的问题。但是在消费的时候有可能多个消费者消费同一个queue,由于各个消费者处理消息的时间不同,导致消息未能按照预期的顺序处理。其实根本的问题就是如何保证消息按照预期的顺序处理完成。
出现顺序错乱的情况:
- 为了提高处理效率,一个queue存在多个consumer
- 一个queue只存在一个consumer,但是为了提高处理效率,consumer中使用了多线程进行处理
保证消息顺序性的方法: - 将原来的一个queue拆分成多个queue,每个queue都有一个自己的consumer。该种方案的核心是生产者在投递消息的时候根据业务数据关键值(例如订单ID哈希值对订单队列数取模)来将需要保证先后顺序的同一类数据(同一个订单的数据) 发送到同一个queue当中。
- 一个queue就一个consumer,在consumer中维护多个内存队列,根据业务数据关键值(例如订单ID哈希值对内存队列数取模)将消息加入到不同的内存队列中,然后多个真正负责处理消息的线程去各自对应的内存队列当中获取消息进行消费。
三、RocketMQ
1. 高性能
保证顺序性:一个生产者一个消费者
根据业务,把不同业务的消息发送到不同的队列(订单号)
保证幂等性:利用幂等性控制不用重复发送,或者重复的消息不处理
消费端处理:不重复发送
利用记录日志:重复的消息不处理
保证可靠性:
发送端:有重试策略,比如有异常就重复发送
存储端:将消息落地到磁盘
消费端:如果消费异常,会重新消费,消费不成功会放到死信队列(可以知道哪些没有发送成功),支持消息回溯(可以回退消息进度)
2. 高可用
采用多主多从的模式,同步双写,数据和服务都没有单点,宕机时,其他节点依然可以提供服务
四、Kafka
参考
【1】https://blog.csdn.net/zw791029369/article/details/109561457
【2】https://www.cnblogs.com/flyrock/p/8859203.html
【3】https://www.cnblogs.com/linjiqin/p/12683076.html
【4】https://www.cnblogs.com/dc-earl/articles/11176603.html
【5】https://blog.csdn.net/yueloveme/article/details/98493896
【6】https://blog.csdn.net/javahongxi/article/details/72956619
【7】https://blog.csdn.net/yunfeng482/article/details/72856762
以上是关于带你整理面试过程中关于消息队列(RabbitMQ/RocketMQ/Kafka)的相关知识点的主要内容,如果未能解决你的问题,请参考以下文章
带你整理面试过程中关于 Java中对于栈和队列的复习的相关知识点