1,浅析消息队列面经
这里我们通过讲解一些常见的面试题和其变种的问法来熟悉消息队列中的一些核心的概念,然后深挖其中知识点,以此来进行拓展,在这个文档中最好只是做简单但确的总结性质的语言来讲述,其他完整的回答,根据相应的模块整理起来即可:
1.1,消息队列应用场景?
- 为什么使用消息队列?
- 使用消息队列有哪些好处和坏处?
- 消息队列的优缺点?
1.2,常见的消息队列有哪些,以及他们的对比?
- MQ的技术选型?
- 1.3,消息模型有哪些?
- 1.4,MQ框架是如何实现高吞吐量的?
- 2.1,如何解决MQ的消息丢失?
2.2,如何解决消息的重复消费?
- ExactlyOnce语义?
- 消息的幂等性?
- 重复出现的原因是什么?
- 2.3,如何保证MQ的消息是有序的?
- 3,如果出现消息积压,应该怎么办?
- 4,如何保证数据的一致性问题?
- 5,事务消息是如何实现的?
- 6,MQ框架如何做到高可用?
(1),消息队列应用场景?
消息队列中的一些其他的特点等信息需要查看另外的文档,做了详细的整理。
- 异步处理:将一个请求链路中的非核心流程,拆分出来,异步处理,减少主流程链路的处理逻辑,提升吞吐量。
- 限流消峰:可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。广泛应用于秒杀或抢购活动中,避免某一刻流量过导致应用系统挂掉的情况;
- 应用解耦:多应用通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
- 消息通讯:消息队列中内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。
- 日志处理:解决大量的日志传输
(2),常见的几种消息队列,以及技术选型?
常见的消息队列:
- RabbitMQ
- Kafka
- RocketMQ
- ... ...
技术选型,主要可以通过以下几个维度:
- 当前系统中主要技术栈,以及团队中对哪个框架更加熟悉;
- 数据的吞吐量(Kafka)
- 对事务支持性(RabbitMQ)
- 社区的活跃程度
- ... ...
(3),如何解决消息的重复消费?
1,首先分析消息队列本身都是保证的"至少一次(At least Once)"的语义,所以如果要通过MQServer配置和业务代码配合来解决。
另外其他几种常见的语义,我们需要保证
Exactly Once
语义。- 最多一次(At most once)
- 至少一次(At least once)
- 仅一次( Exactly once)
2,分析出现重复消费原因
(1),生产者发送了重复的消息:
一般为了保证消息的可靠性,生产者在发送了信息后需要等待Broker的响应,此时如果出现网络波动等情况,响应超出了时间之后,会导致数据的重复发送,
(2),消费者在消费消息的重复的:
当消费者在消费消息的时候,虽然基本的业务逻辑已经走完了,但是在提交Offset的时候,消费者服务挂了,那么这条被消费但是没有被提交的消息会发送到其他消费者中,导致这条消息被重复消费。
- (3),补充“幂等性”的概念:通俗的说,同样的接口或者数据去调用同一个接口的时候,无论重复调用多少次,总能保证数据的正确性,不能出错,这里特指的是接口的幂等性。
3,解决重复的消费的问题
(1),基于业务侧的调整:
- 生产者:可以在发送消息的时候,添加一个唯一的字段,在插入数据的时候在数据库中进行唯一校验即可;但是这样在一定的程度上会影响数据库的性能,一般是需要强检验的场景中会使用。
- 消费者:首先是开始手动提交Offset,单独创建一个消费记录表,将提交Offset、执行插入动作的sql和插入消费记录表唯一key的操作都放在同一个事务中,那么当插入之前先判断这个唯一的key是否已经在消费记录表中,只有不存在的才继续消费。
(4),如果出现消息积压,应该怎么办?
1,分析:出现消息积压一般是是消息的消费速度比不上生产的速度,从而导致了消息堆积。
(1),导致出现消息积压的情况可能有以下集中情况:
- 磁盘写满了,导致数据写入磁盘的时候无法写入,进而导致的消息的积压。
- 数据写入MySQL,但是此时MySQL的服务出现了异常,也可能直接服务down掉等等,导致的消息积压。
- 有可能是因为程序中某步操作时时候,线程耗时过长,导致的消息堆积
2,解决方法:
消费者:
(1),可以适当增加消费者组的机器数量,以提升整体的消费能力;如果是线上的紧急任务,我们可以通过创建一个Topic,Partition是原来的十倍,然后临时写一个Consumer程序,并启动多个线程去进行消费,消费的数据只是临时存储,等待处理消费完积压的数据之后,恢复原先部署的架构,重新用原先Consumer机器来消费消息。
如果可以的话,尽量保留部分现场环境,便于排查消费能力下降的原因。
- (2),排查出问题之后,我们不仅要解决问题,也要对集群的消费能力再进行一次评估,避免是因为消费能力不够引起的消息堆积,尤其是针对一些顺发的流量,比如大促活动之类的。
- (3),优化每条消息的消费过程,从业务的角度考虑优化。
- 生产者:生产者要能及时感知到消费者的能力不足,出现消息积压的时候,可以适当放缓消息放入的速度,可以直接给前端页面提示排队等等。
3,补充:数据积压的时间太长了,导致是消息队列中设置了过期时间的的数据丢失问题
答:如果出现消息的丢失问题,想办法找到丢失的部分数据,重新发送到MQ集群里。
(5),如何保证数据的一致性问题?
关于数据的一致性问题,这里通过“解耦”场景举例:比如电商场景中,下单成功之后,再通知库房扣减库存。
那么这个时候,我们在同一个事务空间中,先处理下单的数据库操作,然后发送MQ消息;剩下的扣减库存的操作交给消费者进行。
另外在消费的环节,也可能会出现数据不一致的情况,那么我们可以采用最终一致性原则,增加重试的机制。
(6),MQ框架是如何实现高吞吐量的?
- 消息可以批量处理
- 对消息体进行压缩,从而节省传输的带宽和存储空间
- 顺序写入磁盘(Kafka):每个分区内是有序的。
- 零拷贝(Kafka):直接在内核层将消息的内容传递给网络Socket,从而避免了应用层之间的拷贝。
- 采用页缓存(Page Cache),使用操作的系统的内存,而不是使用JVM的内存,能够避免占用堆内存和GC问题。
- 采用分区的设计,在每个分区中保持是有序的,另外每个分区可以针对不同的机器消费信息,可以用于并发处理。
(7),MQ事务消息是如何实现的?
这里的实现方式类似于“两阶段提交”,在MySQL的事务中也是处理的。
2,浅析Kafka面试题
(1),Kafka为什么不支持读写分离?
如果使用读写分离的策略,必然会有主和副本之间数据同步,要保证其一致性,另外副本在同步的时候如何保证实时性。
- 数据一致性:如果采用一主多从的方式,Leader副本的数据在同步到Follower副本的时候会存在一定的延迟,那么Follower副本的消息位移也不一样,但是消费者需要通过消费位移来控制消息拉取的进度,多个副本之间要维护统一消费位移的一致性。那么如果要解决这个问题,就需要引入分布式锁,保证锁的安全,非常耗费性能。
- 实时性:如果网络延迟比较大,在同步的过程中难免会影响效率,从而可能无法满足实时性业务的需求。
(2),MQ如何实现高可用?
这里直接以Kafka举例的,其他基本是类似的。
简单来说,就是几个节点之间,选举出主节点(Leader),那么这个时候如果主节点宕机了,可以从其他的节点中进行重新选举。另外每个节点在保存的数据的时候,会在从节点(Follower)中保存相应的副本,通过多副本机制,又是另一个高可用的体现。
(3),如何保证MQ的消息是有序的?
这里的讲解主要是以Kafka为例进行讲解的。
- 方式1,可以强制只有一个分区,那么在一个分区中就是有序的,那么整体就是有序的。但是只有一个分区kafka的吞吐量就不高了。
方式2,从业务的角度考虑,可以通过自定义分区的策略(
org.apache.kafka.clients.Partitioner
)将满足指定规则的数据存储在同一个分区中,从而实现有序:- (1),比如,同一个订单的不同状态的消息存储在同一个分区中
- (2),或者,同一个登录的用户的各类操作存储在同一个分区中
(4),如何解决MQ的消息丢失?
这里是针对Kafka进行分析的,其他的框架也会有类似的:
1,分析可能出现消息丢失的几种情况:
- (1),消息队列本身:在写数据的过程中,我们如果只保证写入Leader节点,而不管副本是否同步成功就算写入成功的话,这种情况下是存在单点故障的,即如果Leader节点挂了那么就会出现丢失数据的情况;
- (2),生产者:由于网络的延迟,导致数据出现发送失败情况,也可以理解为数据丢失的一种情况;
- (3),消费者:使用自动提交Offset的方式,会出现数据在处理完成之前就把Offset提交了,这样也会出现数据丢失的情况;
2,针对以上几种情况提出具体的解决方案:
生产者:
- (1),配置
acks=1 或者 acks=all
参数,在Leader节点写入成功之后,将消息同步到副本列表中 - (2),数据发送失败了之后,可以指定重试(
retries
)的次数,在一些强校验的场景下可以设置为Integer.MAX_VALUE
- (1),配置
可以从Broker的角度考虑:
保证消费者消费到数据之后,再删除Broker中的暂存的信息。
如果是kafka的话,在Broker层面,使用到了
ISR列表 + HW高水位 + Leader Epoch
来防止数据丢失。消费者端:
关闭自动提交,根据回调函数合理处理消息,并手动提交Offset。
(5),补充一些常见的面试题
这里主要是题目描述一下,然后简答写一些回答的关键字。
墙裂推荐大家仔细阅读,并可以关注其公众号。:从面试角度一文学完 Kafka
1),什么是分布式消息中间件?
简单说是一个消息流的管道。
2),消息中间件的作用是什么?
解耦、异步、削峰。
3),消息中间件的使用场景是什么?
除了上述笼统的说法,从实际的业务中描述的话,可以用在一些秒杀的场景中、两个系统的耦合性太高的话,可以去做拆分。
4),消息中间件选型?
主要看业务场景是追求数据的可靠性(事务),还是追求吞吐量。
5),简单讲下 Kafka 的架构?
生产者 --> Broker集群(Topic、Partition(Leader、Follower)、Zookeeper集群) --> 消费者
6),Kafka 中 Zookeeper 的作用?
起到一个注册中心的作用,其本身也是一个分布式的集群管理工具,主要的作用便是管理集群、负责Leader节点的选举;
Zookeeper是怎么作用Leader选举的?
类似与FIFO,也就是先到先得;另外如果主节点挂掉了之后,会从Follower节点中重新进行一轮筛选。了不了解Zab协议?
原子·崩溃协议:其中的原子,是指原子广播协议,用来保证Server之间的同步,从而保证数据的一致性;崩溃,是指支持崩溃恢复,即如果出现服务器宕机重启之后,重新选举主节点,同时Follower节点与Leader节点的同步,来完成崩溃恢复。Zookeeper存储方式?
基于ACL的策略控制方式实现树状的存储,另外他是存储在内存中,并以此来保证高吞吐和低延迟。
7),Kafka 是推模式还是拉模式,推拉的区别是什么?
生产者是推模式,消费者是拉模式;
区别的话,主要就是一个主动拉取数据,另一个是被动接收数据把,通过主动拉取数据的方式可以自己管理消费的Offset,从而提高可控性和读取的性能。
8),Kafka的Offset是什么?
这里的Offset即消息的位移,或者说是偏移量,其本质上是有两种含义:
一是,Broker当中最新消息的Offset的值;
二是,消费者消费到了哪一条的数的Offset的值。
9),Kafka 如何广播消息?
广播是指发送消息给所有的消费者,也就是消费者组的概念。
同一条消息只能被同一个消费者组中一个消费者消费,但是可以被多个消费者组同时消费。
10),Kafka 的消息是否是有序的?
Topic是无序的,但是每个分区内是有序的。
11),Kafka 是否支持读写分离?
不支持,所有的读写操作都在Leader节点上;
Follower节点做镜像节点,负责同步和备份数据,当主节点Down掉后,会从Follower节点中重新选举主节点,从而实现HA;
12),Kafka 如何保证高可用?
高可用体现在集群的高可用和数据的高可用两个方面:
- 集群的高可用体现Leader节点在Down之后可以重新从Follower节点中重新选择;
数据的高可用体现在“多副本机制”、“ISR列表”、“HW”、“Leader Epoch”
- 通过配置acks=all实现多个副本都复制成功的时候才算数据接收成功;
- ISR列表指的是达到数据同步标准的Follower节点;
- HW是High Water Mark 高水位,防止消费者读到未同步的数据;
Leader Epoch 是解决的HW错位导致的数据不一致的问题:
- 1,Epoch:是一个单调递增的版本号;
- 2,Start Offset:是我们下一次需要从哪开始访问的位移;
13),是否支持事务?
在0.11之后是支持事务的。
14),分区数是否可以减少?
不能啊,会导致数据丢失。
15),Kafka 有哪些命令行工具?你用过哪些?
/bin 目录下的脚本文件;
管理 kafka 集群、管理 topic、模拟生产者和消费者等等
16),Kafka Producer 的执行过程?
1,Producer生产消息 --> 2,从Zookeeper找到Partition的Leader --> 3,推送消息 --> 4,通过ISR列表通知给Follower --> 5, Follower从Leader拉取消息,并发送ack --> 6,Leader收到所有副本的ack,更新Offset,并向Producer发送ack,表示消息写入成功。
17),Kafka Producer 有哪些常见配置?
acks的配置(acks=all
)、异常重试的配置(retries
)、提升消息吞吐量(设置缓冲区的大小、开启消息压缩(compression.typ
))的配置
18),如何让 Kafka 的消息有序?
Kafka 在 Topic 级别本身是无序的,只有 partition 上才有序,所以为了保证处理顺序,可以自定义分区器,将需顺序处理的数据发送到同
19),一个 Partition Producer 如何保证数据发送不丢失?
ack机制(acks=all
),重试机制
20),如何提升 Producer 的性能?
批量,异步,压缩
21),如果同一 group 下 consumer 的数量大于 part 的数量,kafka 如何处理?
多余的 consumer 将处于无用状态,不消费数据。
22),Kafka Consumer 是否是线程安全的?
不安全;所以在Consumer端采用的是 单线程消费,多线程处理
23),讲一下你使用 Kafka Consumer 消费消息时的线程模型,为何如此设计?
Thread-Per-Consumer Model,这种多线程模型是利用Kafka的topic分多个partition的机制来实现并行:每个线程都有自己的consumer实例,负责消费若干个partition。各个线程之间是完全独立的,不涉及任何线程同步和通信,所以实现起来非常简单。
其他的详细信息可以看上述连接;
24),Kafka Consumer 的常见配置?
心跳时间的配置、一次Poll返回数据的最大条数、是否自动提交位移 等等
25),Consumer 什么时候会被踢出集群?
崩溃,网络异常,处理时间过长提交位移超时
26),当有 Consumer 加入或退出时,Kafka 会作何反应?
进行 Rebalance
27),什么是 Rebalance,何时会发生 Rebalance?
组成员发生变化;
订阅的主题发生变更;
定于主题的分区数发生变更;
28),Kafka 的交付语义?
- at most once:最多一次,即不会产生重复数据,但可能会丢数据
- at least once:至少一次,即可能会产生重复数据,但不会丢数据
- exactly once:准确的一次,不多也不少
29),Replic 的作用?
通过Replic(副本)来保证数据的高可用性
30),为什么 Follower 副本不提供读服务?
这个问题,本质上来说是对性能和一致性的取舍。假设follower也提供读写服务,固然会提高性能,但是同时也会出现类似于数据库中幻读、脏读等问题。出现这一情况,主要是因为他们之间的同步的不一定是完全一致的。
31),Leader 和 Follower 是什么?
Partition分区中,分为两种节点:Leader、Follower,这两者之间是主备关系,当Leader节点挂了的时候,会通过选择在Follower节点中生成新的Leader节点。
- Leader:所有的读写操作都发生在Leader分区上。
- Follower:所有的Follower节点都需要从Leader节点上同步消息,并做为Leader的备份节点。
32),什么是 AR,ISR?
AR:All Replication,所有分配的副本列表
ISR:In-Sync Replication管理的Follower副本同步的列表,我们会配置一个可容忍的延迟数量,只有大于等于这个数量的Follower才会进入到ISR列表中,同时如果Leader节点挂掉之后,会从ISR列表中重新选举新的Leader。
33),Kafka 中的 LSO、LEO、LW、HW 等分别代表什么?
LSO:是
log Start Offset
,表示第一消息的offsetLEO:是
Log End Offset
日志末端位移的概念,指的是每个副本最后一个 offset + 1LW:是
Low Watermark
的缩写,俗称“低水位”,代表AR集合中最小的logStartOffset值HW:是
High Watermark
的缩写,俗称“高水位”,指的是消费者能见到的最大的 offset,ISR 列表中最小的 LEO。
35),Kafka 为保证优越的性能做了哪些处理
- Partition并发
- 顺序读写磁盘:每个分区文件在本地实行分段(Segment)存储,每个段都采用append追加的方式存储。
- 零拷贝:是指减少了一次数据从内核区到用户缓冲区的拷贝。
- PageCache:页缓存
- 批量读写
- 消息压缩
3,餐饮系统消息队列应用专题分析
本部分的整理内容是来自《苏三说技术-我用kafka两年踩过的非比寻常的坑》,墙裂推荐大家仔细阅读,并可以关注其公众号。
1,保证消息的顺序性,这里一个订单的不同状态举例:
说明:比如“下单” -> “支付” -> “完成” -> “撤销”等,需要保证不能出现,“下单”的消息还没有读到,就先读到“支付”或者“撤销”的消息吧,所以是有必要保证消息的有序性的。
实际处理:通过每一个
订单编号
路由到同一个Partition
分区中,然后部署相同分区数的消费者节点,从而一个分区对应一个消费者节点,从而保证同一个订单的不同状态的消息是有序的。2,虽然写入同一个分区是可以保证有序的,但是如果出现网络超时的情况,导致“下单”的消息一直没有发送成功,但是收到了“支付”的订单消息,那么这个时候,一来消息是错乱的,二是没有下单信息,页面中无法显示完整的消息。
实际处理:基础的想法是采用重试的机制
- (1),同步重试:在消费信息的时候,出现网络失败等情况,立马重试3~5次;但是这样会严重的影响消费者的消费速度,降低他的吞吐量。
(2),异步重试:将失败的消息保存在重试表中,然后有个定时任务不断的拉取重试:
Todo:
1,这里是需要补充重试表得设计规则,以及相关数据是如何保存的,是否需要放置在同一个事务中处理。
2,关于一些常见重试的实际编程,比如
spring-retry
的使用3,关于
elastic-job
的使用消费者在处理消息的时候,首先判断当前的订单号是否在重试表中,如果存在,则执行将当前消息保存在重试表中;如果没有,再正常进行业务处理,当出现了异常的时候,才把消息保存在重试表中。
补充:另外博文中提到:后来使用用
elastic-job
建立了 失败重试机制 ,如果重试了 7 次后还是失败,则将该消息的
状态标记为 失败 ,发邮件通知开发人员。
3,处理消息积压的问题:
在业务中将消息积压的问题体现很明显,后台厨房可能很长一段时间看不到客户下的单据,就会导致餐品上的不及时。
(1),从磁盘网络传输的角度出发:消息在传输的过程中如果消息体的内容过多,导致消息的多,无论是在网络的传输还是数据落地到磁盘,再从磁盘中获取这两个方面考虑,效率都是低下的。
实际处理:
- 1),订单系统发送的消息中只包含:订单id、状态等关键的信息。
- 2),后厨显示系统在消费到信息之后,通过id去查询订单系统对应的相信数据(存在两个服务之间的远程调用)。
- 3),后厨系统中判断数据库是否有该订单的数据,如果有则更新,没有则直接入库(同时可以避免出现消息重复消费的情况)。
- (2),注意检查路由规则设计是否合理
- (3),对于高并发的设计要合理的使用线程池
- (4),对于单表的数据量如果过大,可以根据实际的情况进行分表处理即可。
4,数据库的主从复制
最常见的方式就是在发现处从复制的有一定的延迟的时候,通知DBA进行处理,同时业务数据在查询不到结果的时候,将对应的查询id落地到重试表中,进行相关的重试操作。
5,数据的重复消费问题:
在当前场景下是在插入保存的时候,需要保证是幂等的。于是可以在保存数据的时候使用
insert into ... on duplicate key update
控制存在的时候更新,不存在的时候插入。
参考连接
1,MQ那点破事!消息丢失、重复消费、消费顺序、堆积、事务、高可用....
首先很感谢一些博主的分享,也代表着本文部分参考其他博主的一些文档,如果有侵权,还请及时提醒,我会积极配合!!谢谢您~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。