消息队列-⑥巩固篇
消息队列-⑥巩固篇
学习核心
- 消息队列-基础篇
- 核心概念
- 应用场景
学习资料
消息队列应用核心题型
1.消息队列常见的应用场景?
应用解耦、异步提速、消息分发、削峰填谷
这些概念都是比较抽象的,结合案例进行说明
什么情况下需要解耦?
例如服务模块之间的调用,A模块调用B模块,一般场景下如果业务允许不需要等待B模块的响应,则可以考虑进行解耦,一来是提升A模块的响应处理效率,二来避免模块间的调用依赖导致相互影响。解耦后模块A、模块B是不会相互影响的
例如订单服务:生成订单信息 =》发送到MQ =》消费者:支付、订单、物流等模块会相应处理订单信息,相互直接不会受到影响
解耦和异步的作用其实比较类似,但解耦的本质在于避免模块间的相互影响,异步的本质则在于异步处理耗时任务,提升接口响应速度
什么情况下需要削峰?
削峰主要针对的是对瞬时流量的处理
例如一些秒杀场景中,瞬间大量流量打入,如果服务承载能力不足,就可能导致服务被搞崩,一般有两种场景:
- 主服务承载能力不足:主服务本身能力承载不足直接被搞崩
- 主服务可承载,下游服务承载能力不足:主服务的压力会传递给到下游服务,虽然主服务可以承载,但是下游服务无法承载就会被搞崩,进而导致链路异常
引入MQ是为了将请求放到队列中,然后由相应的服务慢慢去消费,将瞬时的请求压力平摊到一段时间内进行处理。但需要注意的是如果是一直持续的大流量请求,MQ并不能单纯解决这种场景问题,因为会有持续的请求存入MQ,而消费者消费能力不足就会导致MQ中的任务囤积(消息囤积)进而导致异常,这种情况只能通过横向扩容来增强消费能力
什么情况下需要分发消息?
消息分发的场景:A模块需要将一条通知告知给B、C、D,传统实现方式是依次通知,但如果新增了接收节点的话,A不可避免要相应进行迭代维护,这种方式扩展性较差。通过引入MQ,A只需要将消息传递给MQ,订阅了指定topic的接受节点会相应受到消息并做出响应,不需要A单独一个个去通知。
这种思路其实也是有”解耦“的思想在里面,可以理解为**”分发消息是群发场景下的一种解耦方案“**
2.项目开发中,会如何选择消息队列?
实际上还有很多消息队列可供选择。因此要结合实际业务场景选择合适的队列去适配。于个人而言,选型思路分析可以如下:为什么选择kafka?为什么不用xxx?
其实大多是场景中选用主流消息队列基本够用,因此在选择上考虑结合项目需求和业务场景去做考量
- 项目需求:对性能和可靠性有要求(例如要支持5000QPS),基于Kafka和RocketMQ分布式的高可用特性,可以作为参考
- 功能扩展需求:队列的其他功能暂时不作为重要的考量点
- 团队技术栈:加上团队此前已经有比较成熟的技术体系,选择选择xxx
3.Kafka和RocketMQ有什么区别?
这点主要结合个人使用来说,需要对比说明,如果不熟悉就老老实实挨打,专注于自己熟悉的技术栈
对比补充说明
- RocketMQ 和 Kafka 相比,在架构上做了减法,在功能上做了加法跟 Kafka 的架构相比,RocketMQ 简化了协调节点和分区以及备份模型
- 同时增强了消息过滤、消息回溯和事务能力,加入了延迟队列,死信队列等新特性
消息队列实践核心题型
todo
1.消息丢失
kafka什么情况下会出现消息丢失的问题,kafka如何保证消息不丢失
消息丢失可以从三个环节进行分析:生产环节、存储环节、消费环节
生产环节:kafka无法保证生产环节,这点由业务进行保证(通过重试机制)
存储环节:依托于kafka的确认机制和持久化存储,可以确保数据在存储环节可以正常存储(acks的选择,一般选择这种方案(设置acks为1),可靠性和性能都相对较高)
消费环节:消费环节则是基于提交偏移机制,有自动提交和手动提交。自动提交即每次客户端消费一条消息自动提交偏移,让kafka可以通过这个偏移继续消费数据(就算服务出现宕机,后续也能通过这个偏移跟踪定位)。可以通过”手动“提交的方式,只有当消费端确认消费一条消息后自行提交偏移成功后才认为是真正的消费成功,但是这个过程可能会存在客户端消费完消息后没有及时成功提交偏移,而导致消息被重复消费的问题
2.消息重复
kafka什么情况下会出现消息重复的问题,kafka如何保证消息不重复消费
针对消息重复的问题,也可以通过三个环节进行分析:
生产环节:可能是网络抖动或者重试导致消息重复发送,这点kafka无法控制,由业务端控制
存储环节:可以确保数据只存一条
消费环节:kafka可以确保消息只能被同组的同一个消费者消费一次
基于此,实际上kafka对消息不重复消费的保证还是要结合业务进行控制,可以通过业务的“幂等性处理机制”来进行处理,最常见的处理方式是通过Redis、MySQL进行幂等性处理。
Redis:原子操作
MySQL:唯一约束、insert ignore
3.消息有序性
什么时候需要“消息有序”
在一些业务场景中,如果消息间的顺序执行对服务依赖存在影响,则需考虑消息有序的场景。
例如最常见的金融服务的金额操作,假设A有1000元,A需要增值1000元然后统一给B转账2000元,正常顺序操作下这个操作是正确的。但是如果拆分两条消息处理记录顺序出现问题的话,就可能导致流程变为“A给B转账2000、A增值1000”,在A初始余额不足的情况下,这点肯定会导致异常。因此需要通过控制“消息有序”来确保流程的正确性
消息有序如何保证(假设有个业务进入kafka的消息都是有序的,要怎么做?)
基于“分区”概念,最简单粗暴的做法就是根据业务进行分区,每个业务一个分区。
但考虑到对并发度的支持和业务性质,需要从性能和业务需求两方面去兼顾,如果单个业务承载太大,则可以进一步对子业务进行分区(这点可以对标MySQL分库分表的概念)。
分区:
- 子业务分区:将业务按照一定维度拆分为多个不同的子业务
- 例如金融服务(风控子业务、支付子业务)、通知服务(短信子业务、微信子业务),每个子业务对应各自的分区
- 客户分区:基于客户间不存在相互依赖,客户内部需要消息有序的场景
- 例如某个客户希望进行体重管理,需要将自己的日常计划、体重等记录进行有序排列等场景,可以按照userId进行分区,最简单的形式就是Hash(userId)进行分区。
- 基于这个分区设计,当增删节点时会对现有Hash路由造成影响,可维护性比较差,可以考虑Redis的“Hash槽方案”、“一致性Hash方案”进行优化
- 基于这个分区设计,可能会存在分区数据倾斜的问题
- 为了进一步解决上述分区数据倾斜问题,可以考虑“大小客户分区的方式”,按照客户大小进行分区,大客户一个分区,相当于开通vip通道,不和其他小客户挤在一起
- 大客户的鉴定:提前预知(现实业务场景中会和大客户接洽签单的)、过程感知(在业务进行过程中去发现大客户,然后采取方案)
- 例如某个客户希望进行体重管理,需要将自己的日常计划、体重等记录进行有序排列等场景,可以按照userId进行分区,最简单的形式就是Hash(userId)进行分区。
4.MQ消息积压了怎么办
MQ消息积压是MQ常见的问题,一般的解决方案可以结合其产生因素进行分析(消息监控、分析问题、弃车保帅)
- 监控(发现问题):通过脚本监控查看“偏移量”,确认是否出现积压的情况(积压与否是取决于业务实际场景的)
- 异常:这种就是因为服务异常导致,需要介入排查异常原因,然后进行处理
- 无关的依赖服务太多:为了适配一些特殊活动场景,可以将一些和现有活动业务无关的依赖服务暂停,提升整体的消费速率
- 消费能力不足:压力过载(压力超过可承载范围)
- 扩容:这是最常见的一种解决思路,用钱解决问题。但需要注意看场景是否支持水平扩容,否则加机器也没有用
- 增加中间消费者:在kafka和真正的消费者之间放一层“缓冲带”暂时将任务缓冲起来,主要是为了解决MQ消息积压问题,但实际上消息真正被消费处理的能力并没有提升,本质还是要解决消费效率提升的问题
- 保新:在一些特殊业务场景中,如果业务许可的话,可以暂时将积压的任务放一边,将新产生的任务放到新队列,消费新队列即可,待后续消费端缓过来再去处理老消息
消息队列架构核心题型
1.有了Topic为什么还要Partition?
不同消息可以存放在不同的Topic下用于业务隔离,避免消息混乱。但考虑到业务的量级是存在差异的,因此kafka引入Partition,一个Topic下可以有多个Partition(类似数据库分库分表的概念),以支撑业务处理的并发度
2.Partition是逻辑概念还是物理概念?
Topic 是逻辑概念,Partition 是物理概念,消息通过指定key发送,实际存储在某个Topic下的某个Partition分区
3.介绍消息生产的流程
消息生产的流程有3个环节:创建消息=》序列化=》发送消息
创建消息:即封装数据为指定的kdfka格式(消息格式)
序列化:将创建好的消息数据序列化为二进制数据,用于网络传输
发送消息:调用方法,设定指定存储key(对应topic的指定分区),发送消息
4.有消费者为什么还要有消费者组?
消费者组的概念是指将做同一个业务的消费者聚合起来共同协作,它可以理解为统一管理、协同的作用,提升业务处理并发度。当消费者组成员发生变化时会触发相应的再平衡机制,用于协调相应的消费者组重新的分区分配
5.介绍下再平衡机制
再平衡机制的引入是用于协调消费者中的分配分区。当消费者组成员发生变化或者分区数量发生变化的时候,就会相应触发再平衡机制,对消费者组成员重新进行分区分配。
再平衡过程
- 暂停消费者
- 触发再平衡
- 重新分配分区
- 通知消费者
- 恢复消费
分区分配策略
分配策略可以分为两大类:一类是“急切的再平衡”、一类是“增量的再平衡”
“急切的再平衡”:基于STW概念,它会暂时暂停所有消费者的工作,直到再平衡工作完成才会通知消费中继续工作。基于这种机制可能存在两个问题:
- 消费者闲置:再平衡过程中,相关的消费者被闲置
- 再平衡影响原有分配格局:经过再平衡后,消费者会重新加入消费者组获取新的分区分配,不一定能够取回原有的分区,整个格局变了
“增量的再平衡”:它是一种优化演进方案,一些没有收到影响的消费者可以正常进行工作,它是一种渐进式的调整,可能会经过多次调整达到一个最终适配的平衡状态,这种方式虽然稳但是相对来说耗费的时间也是比较多的
消息队列高可用核心题型
1.Replica、Leader、Follower 三者概念
Kafka 高可用的一个重要核心是“多副本”机制,而Replica、Leader、Follower可以理解为相应的概念核心
此处的副本概念强调的是“分区副本”(理解概念,关注核心)
Replica:Kafka集群中的一个分区副本(每个副本保存了分区的完整数据)
Leader:Leader 分区副本(Leader 负责处理分区的所有读写请求)
Follower:Follower 分区副本(Follower 只能同步 Leader 数据)
可以简单理解为 Replica = Leader + Follower
2.Kafka 中 AR、ISR、OSR 三者概念
AR、ISR、OSR 概念的引入是“副本写入机制”的扩展概念。当副本写入策略(即如何确认写入成功的参考标准)为all,此时只有Leader接收到“所有副本”的确认反馈才认为写入成功(此处的“所有副本”是有条件的,如果“所有”为“全部副本”概念,那么就会出现一台Follower宕机就会造成整个集群的不可用,这点肯定是不行的,因此引入AR、ISR、OSR概念,来约束这个“所有”的概念 =》即“所有副本” = ISR集合中的副本)
- AR:分区的所有副本(包括Leader、Follower),是一个整体的集合
- ISR:指的是和Leader副本保持同步(或数据同步的差异在一定阈值范围内)的副本集合(这个集合是动态变化的)
- OSR:指和Leader副本不同步(或数据同步的差异超出一定阈值)的副本集合
3.分区副本什么情况下会从ISR中剔除?
ISR 是动态变化的机制,回归ISR的概念,既然它和Leader副本保持“高度”同步,那么当不满足这个条件时就会被动态剔除。例如副本所在的broker宕机或者网络抖动导致没跟上节奏
每个Partition都会由Leader 动态维护一个与自己基本保持同步的ISR列表。所谓动态维护,就是说如果一个Follower比一个Leader落后超过了给定阈值(默认是10s),则Leader将其从ISR中移除。如果OSR列表内的Follower副本与Leader副本保持了同步,那么就将其添加到ISR列表当中
4.分区副本中的Leader如果宕机,但是ISR却为空该如何处理?
正常情况下如果Leader宕机,则会通过Leader选举机制重新从ISR中选举一个。如果说ISR为空,那么可以考虑“OSR”,通过配置参数 unclean.leader.election
来决定是否从OSR中选举出leader,但需注意 OSR 的消息较为滞后,可能会出现消息丢失的问题。
如果没有配置unclean参数,且ISR也为空,那么OSR也没办法参与竞选,就会最终导致这个分区不可用(因为没有新Leader来支持读写了,只能被动等待原Leader恢复或者看看是否有新副本节点动态ISR)
5.分区副本之间同步,是推还是拉?
是“拉机制”,Follower副本主动拉取Leader的数据进行同步:涉及“HW(高水位线)”、“LEO(下一条要写入的位置)”概念
- Follower 拉取,发现状态同步到最新,则不做任何操作
- 写入新数据到Leader(Leader的LEO改变),Follower拉取发现LEO不同(Follower的LEO滞后于Leader的LEO)因此拉取消息进行同步,并相应更新自身的LEO值
- Follower 再次拉取,Leader发现LEO变高了(对比Leader目前的HW),引发Leader的HW更新并反馈给Follower,Follower收到反馈后就会相应更新HW,继续和Leader保持同步,以此类推
拉模式的优势在于副本机器可以根据自身的负载情况来拉取
消息队列高性能核心题型
1.Kafka为什么这么快?
Kafka 的高性能得益于很多方面,可以结合“底层机制”+“应用层”进行性能优化说明
从“存储设计”(多层次)、“读写”(顺序写、Page Cache)、“传输”(零拷贝)、“应用层”(批量操作、数据压缩)等方面提供了性能优化支持
2.聊聊Kafka顺序写机制
“顺序写”指的是顺序将数据写入磁盘(对照kafka的写入机制即将数据追加到磁盘末尾),这种写入方式的性能是非常高的(其性能既接近普通内存写,又降低了数据丢失风险),顺序写入减少了磁盘寻道时间,使得磁盘IO操作更加高效,能够持续高速写入数据,从而实现高吞吐量
3.聊聊Kafka页缓存机制
“顺序写入磁盘”既然可以那么快,那么“顺序写入内存”则性能更快
页缓存时Kafka充分利用操作的系统page cache进行读写优化,提升效率
- 写入消息:将消息写入page cache,然后由操作系统异步刷入磁盘
- 读取消息:读取消息先从page cache读取,如果命中则返回,如果未命中则从磁盘中读取并回写到page cache后返回
4.聊聊Kafka零拷贝机制
Kafka的零拷贝机制是针对传输环节的优化
- 零拷贝技术可以优化数据发送效率,Kafka则充分利用了Linux的这个特性来提升性能
- Kafka底层传输是通过调用sendfile()系统调用函数,进而减少【上下文切换】、【数据拷贝】带来的损耗
- 【上下文切换】:sendfile() 替换了原有的read()、wirte()(传统:2次=》零拷贝:1次)
- 【数据拷贝】:数据直接在核心态传输,只需要拷贝2次(传统:4次=》零拷贝:2次)
5.聊聊Kafka分层设计机制
Kafka分层设计采用“分治”+“查询优化”的核心实现分层设计:
- 分治(Topic分区):将Topic划分为多个Partition,可以理解为将一个大数组拆分为多个小数组分布在broker上,可以提升操作并发度
- 查询优化(Partition的进一步拆分):Kafka进一步将Partition拆分为多个文件进行存储(.log、.timeindex、.index),可以通过索引文件快速定位数据
6.聊聊Kafka哪些环节用了批量操作
批量操作是后端优化的一个通用设计,Kafka提供了批量发送和批量消费概念:
- 批量发送:将消息放在缓冲地带,待达到一定量后统一发送(减少IO交互和网络带宽的性能损耗)
- 批量消费:拉取一批数据后再统一消费(减少IO交互和网络带宽的性能损耗)
7.聊聊Kafka数据压缩
Kafka的数据压缩是对消息进行压缩,便于传输。Kafka对数据压缩的环节体现在producer端和broker端
在prodcuer端进行数据压缩:对消息按照指定压缩算法进行压缩,可以和批量操作搭配使用,提升压缩效益,达到更好的性能提升效果
在broker端进行数据压缩:在broker端压缩存在一定的规则
- 如果broker端指定了压缩,而prodcuer端没有指定压缩,则broker正常压缩即可
- 如果broker端和prodcuer端都指定了压缩,则看两者的配置是否相同
- 压缩配置相同,则broker不会重复压缩
- 压缩配置不同,则broker会对消息进行解压缩,然后再按照broker端配置的压缩算法进行重新压缩