105消息队列kafka
核心概念
存储消息、收发消息的服务器,我们称为消息代理;
Kafka 中的数据单元被称为消息,也被称为记录,可以把它看作数据库表中某一行的记录。
为了提高效率, 消息会分批次写入 Kafka,批次就代指的是一组消息。
消息的种类称为 主题(Topic),可以说一个主题代表了一类消息。相当于是对消息进行分类。主题就像是数据库中的表。
一个主题可以被分为若干个分区(partition),同一个主题中的分区可以不在一个机器上,有可能会部署在多个机器上,由此来实现 kafka 的伸缩性,单一主题中的分区有序,但是无法保证主题中所有的分区有序

一直追加的顺序写操作;
副本:每个分区可以有多个副本,也就是数据的备份。这样即使分区数据错误,副本也可以恢复过来
**同步:**所有的消息必须发给主分区所在机器,副本分区同步主分区的数据

注意:消费者可以分组,一般一种微服务的所有消费者就是一组。
- 同组的消费者属于竞争关系,消息只能被一个消费者拿到;
- 不同组的消费者属于发布订阅关系。同一个主题的消息,广播给不同组的消费者。
- 消费者可以订阅多种主题的消息;

偏移量:偏移量(Consumer Offset)是一种元数据,它是一个不断递增的整数值,用来记录消费者发生重平衡时的位置,以便用来恢复数据。
创建主题
- 副本数量必须小于Broker数量。
- 分区数量无所谓,根据你自己对数据的预估,分成N个区,如果Broker只有一个,所有分区的数据都在这一个机器,如果Broker有多个,分区可以被分散到其他机器。从而形成一个压力分担的效果;
计算方式: N Broker 最多允许 N 副本;
Kafka 高可用机制详解
Kafka 的高可用性通过多种设计实现,确保在节点故障或网络分区时仍能持续提供服务,同时保证数据的一致性和可靠性。
Kafka 的 副本机制 是高可用的核心。每个分区(Partition)都有多个副本(Replica),其中一个是 Leader 副本,负责处理所有读写请求,其余为 Follower 副本,负责从 Leader 同步数据。当 Leader 副本发生故障时,会从同步副本(ISR)中选举新的 Leader,确保服务不中断。
Kafka 的 分区机制 提高了系统的扩展性和容错能力。一个主题(Topic)可以分为多个分区,分布在不同的 Broker 上,即使某个 Broker 宕机,也不会影响其他分区的正常运行。
Kafka 使用 ISR(In-Sync Replicas)机制 来保证数据一致性。ISR 是与 Leader 保持同步的副本集合,只有在 ISR 中的副本才有资格被选为新的 Leader。未同步的副本会被移出 ISR,避免数据不一致。
Leader 选举 是 Kafka 高可用的关键。当 Leader 副本失效时,Kafka 会从 ISR 中选举新的 Leader。选举过程由 Controller 节点负责,Controller 是通过 ZooKeeper 或 KRaft 选举出来的,确保整个集群的协调和管理。
Kafka 的 消费者组(Consumer Group) 提供了高可用的消费机制。消费者组中的消费者共同消费一个主题的分区数据,当某个消费者失效时,其分区会被重新分配给其他消费者,保证消费不中断。
Kafka 的 重平衡机制 在消费者组发生变更时重新分配分区,确保高可用性和伸缩性。然而,重平衡期间消费者无法读取消息,可能导致短暂的不可用。
通过以上机制,Kafka 实现了高可用性和高可靠性,能够在节点故障、网络分区等情况下保持服务稳定,同时确保数据的一致性和安全性。
消费者组
5个消费者,4个分区
每个分区所产生的消息能够被每个消费者群组中的消费者消费,如果向消费者群组中增加更多的消费者,那么多余的消费者将会闲置,

向群组中增加消费者是横向伸缩消费能力的主要方式。总而言之,我们可以通过增加消费组的消费者来进行水平扩展提升消费能力。这也是为什么建议创建主题时使用比较多的分区数,这样可以在消费负载高的情况下增加消费者来提升性能。另外,消费者的数量不应该比分区数多,因为多出来的消费者是空闲的,没有任何帮助。
Kafka 一个很重要的特性就是,只需写入一次消息,可以支持任意多的应用读取这个消息。换句话说,**每个应用都可以读到全量的消息。**为了使得每个应用都能读到全量消息,应用需要有不同的消费组。对于上面的例子,假如我们新增了一个新的消费组 G2,而这个消费组有两个消费者,那么就演变为下图这样

在这个场景中,消费组 G1 和消费组 G2 都能收到 T1 主题的全量消息,在逻辑意义上来说它们属于不同的应用。
总结起来就是如果应用需要读取全量消息,那么请为该应用设置一个消费组;如果该应用消费能力不足,那么可以考虑在这个消费组里增加消费者。
从上面的消费者演变图中可以知道这么一个过程:最初是一个消费者订阅一个主题并消费其全部分区的消息,后来有一个消费者加入群组,随后又有更多的消费者加入群组,而新加入的消费者实例分摊了最初消费者的部分消息,这种把分区的所有权通过一个消费者转到其他消费者的行为称为重平衡,英文名也叫做 Rebalance 。如下图所示

重平衡是Kafka消费者组在成员发生变化时(消费者加入、离开或崩溃),重新分配分区给消费者的过程。
在重平衡期间,消费者组中的消费者实例都会停止消费,等待重平衡的完成。而且重平衡这个过程很慢。
触发重平衡的条件
消费者加入:新消费者加入消费者组
消费者离开:消费者主动离开或崩溃
订阅主题变化:订阅的主题分区数发生变化
心跳超时:消费者未能按时发送心跳(默认五分钟)
消费者数量变化、主题变化
当Kafka 集群要触发重平衡机制时,大致的步骤如下: 1.暂停消费: 在重平衡开始之前,Kafka 会暂停所有消费者的拉取操作,以确保不会出现重平衡期间的消息丢失或重复消费。 2.计算分区分配方案: Kafka 集群会根据当前消费者组的消费者数量和主题分区数量,计算出每个消费者应该分配的分区列表,以实现分区的负载均衡。 3.通知消费者: 一旦分区分配方案确定,Kafka 集群会将分配方案发送给每个消费者,告诉它们需要消费的分区列表,并请求它们重新加入消费者组。 4.重新分配分区: 在消费者重新加入消费者组后,Kafka 集群会将分区分配方案应用到实际的分区分配中,重新分配主题分区给各个消费者。 5.恢复消费: 最后,Kafka 会恢复所有消费者的拉取操作,允许它们消费分配给自己的分区。
消费者连接上以后,从这个分区之前偏移量以后的消息继续消费
1 | |
- 使用静态组成员(Kafka 2.3+)
1 | |
- 增量重平衡(Kafka 2.4+)
- 只重新分配受影响的分区
- 减少整个组的停顿时间
- 避免频繁重启:使用优雅关闭
Kafka为何如此快
Kafka 实现了**零拷贝**原理来快速移动数据,避免了内核之间的切换。Kafka 可以将数据记录分批发送,从生产者到文件系统(Kafka 主题日志)到消费者,可以端到端的查看这些批次的数据。
批处理能够进行更有效的数据压缩并减少 I/O 延迟,Kafka 采取顺序写入磁盘的方式,避免了随机磁盘寻址的浪费;
总结一下其实就是四个要点
- 顺序写
- 零拷贝
- 消息压缩
- 批处理

append only 日志****末尾添加的机制,也就是每次有数据写入kafka,它是将数据message 添加到消息文件的末尾,从而实现磁盘顺序写的机制,

DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载 。通俗来讲,就是DMA 传输将数据从一个地址空间复制到另外一个地址空间,当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成,也就是两个硬件之间完成的,而没有CPU的参与,那么CPU就可以释放出来做别的事情,这样极大地提高了效率。

传统拷贝:
- 我们可以清楚的看到共产生了4次copy,从磁盘文件到Kernal的相互读写是支持DMA copy的,但即使是这样,从Kernal到User没有硬件的支持所以不支持DMA,还有两次CPU copy。
- Kafka只是把文件存放到磁盘之后通过网络发出去,中间并不需要修改什么数据,那read和write的两次CPU copy的操作完全是多余的。
零拷贝:
Linux2.1内核开始引入了
sendfile函数 (mmap),用于将文件通过socket传送。Linux2.4内核开始引入了 sendfile 函数; 零拷贝

操作系统 ==> JVM 提供零拷贝函数 ==> Kafka调用函数;
JVM 提供零拷贝函数 : NIO(New IO)(Buffer、Selector、Channel)
file.transferTo(file2):底层直接零拷贝
IO:Input Output;
压缩消息可以节省网络带宽。从而提高吞吐量

导致丢消息


消息达到最大数量、或者最大内存、或者最长时间,都会被全部发出去
kafka如何保证消息不丢失
要保证消息不丢失,第一点要做的就是要保证消息从producer端发送到了kafka的broker中,并且broker把消息保存了下来。 由于在发送消息的过程中有可能会发生网络故障,broker故障等原因导致消息发送失败,因此在producer端有两种方式来避免消息丢失。
我们在使用kafka发送消息的时候,通常是使用producer.send(msg)方法,但是这个方法其实是一种异步发送,调用此方法发送消息的时候,虽然会立即返回,但是并不代表消息真的发送成功了。 1、*所以可以使用同步发送消息,**producer.send(msg).get()此方法会执行同步发生消息,并等待结果返回。 2、也可以使用带回调函数的异步方法,producer.send(msg,callback)**,用回调函数来监听消息的发送结果,如果发送失败了,可以在回调函数里面进行重试。*
producer也提供了一些配置参数来避免消息丢失。
1 | |
当消息发送到broker后,broker需要保证此消息不会丢失,我们都知道,kafka是会将消息持久化到磁盘中的。 但是kafka为了保持性能采用了,页缓存+异步刷盘 的形式将消息持久化到磁盘的。也就是批量定时将消息持久化到磁盘。 但是页缓存如果还没来的及将消息刷到磁盘,broker就挂了,还是会有消息丢失的风险,因此kafka又提供了partition的ISR(同步副本机制),即每一个patrtition都会有一个唯一的Leader和一到多个Follower,Leader专门处理一些事务类型的请求,Follower负责同步Leader的数据。当leader挂了后,会重新从Follower中选举出新的Leader,保证消息能够最终持久化。
另外,在producer中的配置参数acks,配置不同的值,broker也是会做不同的处理的。
acks=0:表示Producer请求立即返回,不需要等待Leader的任何确认。这种方案有最高的吞吐率,但是不保证消息是否真的发送成功。acks =-1: 表示分区Leader必须等待消息被成功写入到所有的ISR副本(同步副本)中才认为Producer请求成功。这种方案提供最高的消息持久性保证,但是理论上吞吐率也是最差的。acks=1: 表示Leader副本必须应答此Producer请求并写入消息到本地日志,之后Producer请求被认为成功。如果此时Leader副本应答请求之后挂掉了,消息会丢失。这个方案,提供了不错的持久性保证和吞吐。
Consumer端,只要保证消息接收到不胡乱的提交offset就行,kafka本身也是会记录每个pratition的偏移量,但是为了业务的可靠性,最好在确认消费消息后,提交偏移量;默认是拿到消息,就自动提交偏移量
为了避免消息丢失,建议使用**手动提交偏移量的方式,**防止消息的业务逻辑未处理完,提交偏移量后消费者挂了的问题。
1 | |
kafka如何保证消息顺序消费
顺序发送 + 顺序存储 + 顺序消费
具体实现顺序发送消息有两种方式: 1、在使用kafka时,对需要保证顺序消费的topic,只创建一个partition,这样消息就都会顺序的存储到这*一个partition中,也就能保证顺序消费了。 2、当一个topic有多个partition时,对需要保证顺序的消息,都**发到指定的partition即可,这样也能保证顺序消费。
Kafka 会按照消息发送顺序进行顺序存储;但是一定是单发送者。
多发送者不能保证消息抵达Kafka的顺序
在消费端,要保证顺序消费,单线程处理消息即可
kafka如何保证消息不重复消费【幂等性】
什么情况下会导致消息被重复消费呢?
生产者,生产者可能重复推送了一条消息到kafka,例如:某接口未做幂等处理,接口中会发送kafka消息
**kafka:**在消费者消费完消息后,提交offset时,kafka突然挂了,导致kafka认为此消息还未消费,又重新推送了该条消息,导致了重复消费消息。
消费者,在消费者消费完消息后,提交offset时,Consumer突然宕机挂掉,这个时候,kafka未接收到已处理的offset值,当Consumer恢复后,会重新消费此部分消息。
======== 所有的都是消费端保证幂等性(消息执行的历史记录表)即可 ========
- 还有一种情况,Kafka 存在 Partition ReBalance 机制,会将多个 Partition 均衡分配给多个消费者。若 Consumer 在默认 5 分钟内未处理完一批消息,会触发 Rebalance 机制,导致 offset 自动提交失败,重新 Rebalance 后,消费者会从之前未提交的 offset 位置开始消费,从而造成消息重复消费。(消费者数量变化等也会导致重平衡)
重平衡(有可能是消费者慢(假掉线),导致重平衡,offset没来及提交,kafka会重复投递消息给别的消费者)
其实上面的1、2、3、4这些情况都可以用幂等机制来防止消息被重复消费。
为消息生成 一个唯一标识,并保存到 mysql 或 redis 中,处理消息前先到 mysql 或 redis 中判断该消息是否已被消费过。
但是第4种情况,前提是要先优化消费端处理性能,避免触发 Rebalance。额外的方案
例如:
采用异步方式处理消息缩短单个消息消费时长调整消息处理超时时间(5min、10min)减少一次性从 Broker 拉取的数据条数等。10000条
从0开始做;
服务降级熔断
服务雪崩
OK,我们从服务雪崩开始讲起!假设存在如下调用链
而此时,Service A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算Service A能扛得住请求,Service B和Service C未必能扛得住这突发的请求。
此时,如果Service C因为抗不住请求,变得不可用。那么Service B的请求也会阻塞,慢慢耗尽Service B的线程资源,Service B就会变得不可用。紧接着,Service A也会不可用,这一过程如下图所示
如上图所示,一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。
ps:谁发明的这个词,真是面试装13必备!
那么,服务熔断和服务降级就可以视为解决服务雪崩的手段之一。
1. 服务降级
1.1 定义
服务降级是指在系统部分功能出现异常或负载过高时,主动降低某些非核心功能的质量或直接停止这些功能,以保证核心功能的正常运行。其核心思想是“优先保障核心服务”。
1.2目的
- 保障核心功能:在系统资源有限的情况下,优先保障对用户和业务最重要的功能。
- 提高系统可用性:通过降低非核心功能的质量,释放资源给核心功能,防止系统全面崩溃。
- 改善用户体验:即使在系统压力较大的情况下,也能为用户提供基本可用的服务。
1.3 实现策略
- 功能降级:直接关闭一些不重要的功能或特性。例如,电商网站在大促期间可能会关闭一些复杂的推荐算法。
- 质量降级:降低服务的质量或精度。例如,降低图片的分辨率或减少数据的刷新频率。
- 异步处理:将一些实时性要求不高的功能改为异步处理,例如,将订单确认邮件的发送改为异步任务。
1.4 实施步骤
- 识别核心与非核心功能:明确系统中的核心功能和非核心功能。
- 设定降级策略:为不同的非核心功能设定相应的降级策略。
- 监控与触发:通过监控系统的运行状态,自动或手动触发降级策略。
- 恢复机制:在条件允许的情况下,自动或手动恢复被降级的服务。
2. 服务熔断
2.1 定义
服务熔断是一种保护机制,用于防止系统在某些服务出现问题时,影响到整个系统的稳定性。其灵感来源于电力系统中的熔断器,当电路出现过载时,熔断器会自动切断电路以保护整个系统。
2.2 目的
- 防止故障蔓延:当某个服务出现故障时,防止其影响扩大到其他服务。
- 快速失败和恢复:通过快速失败,减少资源消耗,并在服务恢复后快速恢复正常状态。
- 提高系统稳定性:通过隔离故障,保证其他服务的正常运行。
2.3 实现策略
- 熔断器模式:实现熔断器的三种状态:关闭、打开和半开。
- 关闭状态:正常请求通过。
- 打开状态:请求快速失败,直接返回错误。
- 半开状态:允许部分请求通过,以检测服务是否恢复。
- 熔断条件:设定触发熔断的条件,例如请求失败率超过一定阈值。
- 恢复策略:设定从打开状态到半开状态的条件,例如经过一段时间后自动进入半开状态。
2.4 实施步骤
- 设定熔断条件:根据业务需求设定触发熔断的条件。
- 实现熔断器逻辑:在服务调用链中实现熔断器的逻辑。
- 监控与调整:持续监控熔断器的状态,并根据实际情况调整熔断条件。
3. 服务限流
3.1 定义
服务限流是指通过限制系统处理请求的速率,来保护系统资源,防止系统过载。限流策略通常用于防止突发流量对系统的冲击。
3.2 目的
- 防止系统过载:通过限制请求速率,防止系统资源被耗尽。
- 保障服务稳定性:在高并发情况下,保证服务的稳定性和响应速度。
- 提高用户体验:通过平滑处理请求,避免因系统过载导致的服务不可用。
3.3 实现策略
- 令牌桶算法:通过令牌的生成和消费来控制请求的速率。
- 漏桶算法:通过固定速率的请求处理,平滑突发流量。
- 计数器算法:在固定时间窗口内限制请求数量。
3.4 实施步骤
- 确定限流目标:识别需要限流的服务或接口。
- 选择限流算法:根据业务需求选择合适的限流算法。
- 配置限流参数:设定限流的阈值和策略。
- 监控与优化:监控限流效果,并根据需要进行优化和调整。
4. 综合应用
在实际应用中,服务降级、服务熔断和服务限流常常结合使用,以应对复杂的系统问题和不确定性。通过合理的策略组合,可以有效提高系统的鲁棒性和可用性。
- 降级与限流结合:在流量高峰期,通过限流保护系统,并通过降级保证核心功能的可用性。
- 熔断与降级结合:当某个服务触发熔断后,通过降级策略保证其他服务的正常运行。
- 熔断与限流结合:在熔断器恢复过程中,通过限流控制请求速率,防止服务再次过载。
为什么有了服务降级还需要服务熔断?
这里我们总结了4个核心理由:
- 避免资源浪费:当一个服务出现故障时,如果没有熔断机制,系统可能会持续不断地尝试调用这个失败的服务,导致请求积压和资源耗尽。服务熔断通过快速失败,避免了不必要的调用,节省了宝贵的系统资源。
- 防止级联故障:在微服务架构中,服务之间通常相互依赖。如果一个服务出现问题,持续的失败调用可能会影响到依赖它的其他服务,导致级联故障。服务熔断器可以在问题初期及时切断受影响的服务调用,防止故障扩散到整个系统。
- 加速系统恢复:通过熔断机制,系统能够更快地检测到服务的故障状态,并在熔断器打开后,等待一段时间再尝试恢复调用。这有助于目标服务有足够的时间进行自我修复,从而加速整个系统的恢复过程。
- 提供更好的用户体验: 服务降级虽然能够保证核心功能的可用性,但在高负载或持续失败的情况下,用户可能会频繁遇到降级后的功能或默认响应,影响使用体验。服务熔断器通过控制调用频率和恢复策略,能够在保证必要降级的同时,减少对用户的负面影响。
end