zhuanlan.zhihu.com/p/612484181
zhuanlan.zhihu.com/p/388206784
概览
作用
- 削峰填谷
- 消息订阅
- 流式计算
概念
- 消息:Record。Kafka 是消息引擎嘛,这里的消息就是指 Kafka 处理的主要对象。
- 主题:Topic。主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。
- 分区:Partition。一个有序不变的消息序列。每个主题下可以有多个分区。
- 消息位移:Offset。表示分区中每条消息的位置信息,是一个单调递增且不变的值。
- 副本:Replica。Kafka 中同一条消息能够被拷贝到多个地方以提供数据冗余,这些地方就是所谓的副本。副本还分为领导者副本和追随者副本,各自有不同的角色划分。副本是在分区层级下的,即每个分区可配置多个副本实现高可用。
- 生产者:Producer。向主题发布新消息的应用程序。
- 消费者:Consumer。从主题订阅新消息的应用程序。
- 消费者位移:Consumer Offset。表征消费者消费进度,每个消费者都有自己的消费者位移。
- 消费者组:Consumer Group。多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。
- 重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。
线上环境考量
-
Linux
- I/O 模型的使用: Kafka 客户端底层使用了 Java 的 selector,selector 在 Linux 上的实现机制是 epoll,而在 Windows 平台上的实现机制是 select。
- 数据网络传输效率: 零拷贝(Zero Copy)技术,就是当数据在磁盘和网络进行传输时避免昂贵的内核态数据拷贝从而实现快速的数据传输
- 社区支持度
-
磁盘
- RAID:追求性价比的公司可以不搭建 RAID,使用普通磁盘组成存储空间即可。使用机械磁盘完全能够胜任 Kafka 线上环境。
- 日志顺序写
-
磁盘容量
-
带宽
zookeeper
它是一个分布式协调框架,负责协调管理并保存 Kafka 集群的所有元数据信息。
比如集群都有哪些 Broker 在运行、创建了哪些 Topic,每个 Topic 都有多少分区以及这些分区的 Leader 副本都在哪些机器上等信息。
主题与分区
分区
- 提供负载均衡的能力,高scalability;
- 实现业务级别的消息顺序的问题;
-
分区策略
- 轮询策略,Round-robin
- 随机策略
- 按消息键保序策略:一旦消息被定义了 Key,那么你就可以保证同一个 Key 的所有消息都进入到相同的分区里面
- 基于地理位置分区
位移主题 __consumer_offsets
-
Key 中应该保存 3 部分内容:<Group ID,主题名,分区号 >,Value保存了位移值
-
如果位移主题是 Kafka 自动创建的,那么该主题的分区数是 50,副本数是 3
-
提交位移:
-
自动提交位移:只要 Consumer 一直启动着,它就会无限期地向位移主题写入消息。Kafka 使用 Compact 策略来删除位移主题中的过期消息,避免该主题无限期膨胀。
-
手动提交位移:
- 同步提交:在调用commitSync()时,Consumer程序会处于阻塞状态,直到远端Broker返回提交结果,这个状态才会结束。对TPS影响显著
- 异步提交:在调用commitAsync()时,会立即给响应,但是出问题了它不会自动重试。提供了回调函数(callback),供你实现提交之后的逻辑,比如记录日志或处理异常等。
- 最好是同步和异步结合使用,正常用异步提交,如果异步提交失败,用同步提交方式补偿提交。
-
批次提交
- 对于一次要处理很多消费的Consumer而言,将一个大事务分割成若干个小事务分别提交。这可以有效减少错误恢复的时间,避免大批量的消息重新消费。使用commitSync(Map<TopicPartition,Offset>)和commitAsync(Map<TopicPartition,OffsetAndMetadata>)。
-
-
CommitFailedException 异常
- 缩短单条消息处理的时间
- 增加 Consumer 端允许下游系统消费一批消息的最大时长
- 减少下游系统一次性消费的消息总数
- 下游系统使用多线程来加速消费
生产者
压缩算法
-
消息集合,消息:Kafka 通常不会直接操作具体的一条条消息,它总是在消息集合这个层面上进行写入操作
-
v1,v2版本不同:
- v2把消息的公共部分抽取出来放到外层消息集合里面,这样就不用每条消息都保存这些信息了。
- V1 版本中,每条消息都需要执行 CRC 校验; v2消息的 CRC 校验工作就被移到了消息集合这一层。
- V1 版本中保存压缩消息的方法是把多条消息进行压缩然后保存到外层消息的消息体字段中; V2 版本的做法是对整个消息集合进行压缩;
-
何时压缩:生产者端和 Broker 端(情况一:Broker 端指定了和 Producer 端不同的压缩算法。情况二:Broker 端发生了消息格式转换。)
Java tcp 连接
-
KafkaProducer 实例创建时启动 Sender 线程,从而创建与 bootstrap.servers 中所有 Broker 的 TCP 连接。
-
KafkaProducer 实例首次更新元数据信息之后,还会再次创建与集群中所有 Broker 的 TCP 连接。
-
如果 Producer 端发送消息到某台 Broker 时发现没有与该 Broker 的 TCP 连接,那么也会立即创建连接。
-
如果设置 Producer 端 connections.max.idle.ms 参数大于 0,则步骤 1 中创建的 TCP 连接会被自动关闭;如果设置该参数 =-1,那么步骤 1 中创建的 TCP 连接将无法被关闭,从而成为“僵尸”连接。
消费者
Rebalance
触发条件
- 组成员数发生变更
- 订阅主题数发生变更
- 订阅主题的分区数发生变更
在 Rebalance 过程中,所有 Consumer 实例都会停止消费,等待 Rebalance 完成。
如何确定 Coordinator
Coordinator:负责执行消费者组的注册、成员管理记录等元数据管理操作。
- 第 1 步:确定由位移主题的哪个partition来保存该 Group 数据:partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)。
- 第 2 步:找出该分区 Leader 副本所在的 Broker,即为对应的 Coordinator。
非必要rebalance
-
第一类非必要 Rebalance 是因为未能及时发送心跳,导致 Consumer 被“踢出”Group 而引发的。
- 当 Consumer Group 完成 Rebalance 之后,每个 Consumer 实例都会定期地向 Coordinator 发送心跳请求,表明它还存活着。
- 可通过设置 session.timeout.ms 和 heartbeat.interval.ms 避免。
-
第二类非必要 Rebalance 是 Consumer 消费时间过长导致的
- max.poll.interval.ms
- GC 参数
通知机制
重平衡的通知机制正是通过心跳线程来完成的。当协调者决定开启新一轮重平衡后,它会将“REBALANCE_IN_PROGRESS”封装进心跳请求的响应中,发还给消费者实例。当消费者实例发现心跳响应中包含了“REBALANCE_IN_PROGRESS”,就能立马知道重平衡又开始了
状态流转
消费者端重平衡流程
- JoinGroup 请求
当组内成员加入组时,它会向协调者发送 JoinGroup 请求。在该请求中,每个成员都要将自己订阅的主题上报,这样协调者就能收集到所有成员的订阅信息。一旦收集了全部成员的 JoinGroup 请求后,协调者会从这些成员中选择一个担任这个消费者组的领导者。
- SyncGroup 请求
消费者组领导者向协调者发送 SyncGroup 请求,将刚刚做出的分配方案发给协调者。值得注意的是,其他成员也会向协调者发送 SyncGroup 请求,只不过请求体中并没有实际的内容。这一步的主要目的是让协调者接收分配方案,然后统一以 SyncGroup 响应的方式分发给所有成员,这样组内所有成员就都知道自己该消费哪些分区了。
Consumer 单线程设计,多线程消费方案
- 从 Kafka 0.10.1.0 版本开始,KafkaConsumer 就变为了双线程的设计,即用户主线程和心跳线程。
- 所谓用户主线程,就是你启动 Consumer 应用程序 main 方法的那个线程,而新引入的心跳线程(Heartbeat Thread)只负责定期给对应的 Broker 机器发送心跳请求,以标识消费者应用的存活性(liveness)
两套多 线程 方案
- 消费者程序启动多个线程,每个线程维护专属的 KafkaConsumer 实例,负责完整的消息获取、消息处理流程。
- 消费者程序使用单或多线程获取消息,同时创建多个消费线程执行消息处理逻辑
TCP连接
何时创建
构建 KafkaConsumer 实例时是不会创建任何 TCP 连接的。TCP 连接是在调用 KafkaConsumer.poll 方法时被创建的。
- 发起 FindCoordinator 请求时:消费者程序会向集群中当前负载最小的那台 Broker 发送请求,希望 Kafka 集群告诉它哪个 Broker 是管理它的协调者(仅仅是为了首次获取元数据而创建的,后面就会被废弃掉)
- 连接协调者时:消费者知晓了真正的协调者后,会创建连向该 Broker 的 Socket 连接
- 消费数据时:消费者会为每个要消费的分区创建与该分区领导者副本所在 Broker 连接的 TCP
- 当第三类 TCP 连接成功创建后,消费者程序就会废弃第一类 TCP 连接
何时关闭
手动调用 KafkaConsumer.close() 方法,或者是执行 Kill 命令。
消费者组消费进度监控如何实现
使用 Kafka 自带的命令行工具 kafka-consumer-groups 脚本。
使用 Kafka Java Consumer API 编程。
使用 Kafka 自带的 JMX 监控指标。
拦截器
客户端监控、端到端系统性能检测、消息审计。
-
生产者端拦截器
- onSend
- onAcknowledgement
-
消费者拦截器
- onConsume
- onCommit
消息丢失
-
生产者程序丢失数据:
- Producer 永远要使用带有回调通知的发送 API,也就是说不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。
- 消息没有发送成功原因:网络抖动,导致消息压根就没有发送到 Broker 端;或者消息本身不合格导致 Broker 拒绝接收(比如消息太大了,超过了 Broker 的承受能力)
-
消费者程序丢失数据
- 维持先消费消息,再更新位移的顺序。这种处理方式可能带来的问题是消息的重复处理。
- 如果是多线程异步处理消费消息,Consumer 程序不要开启自动提交位移,而是要应用程序手动提交位移。
消息交付可靠性保障
语义
- 最多一次(at most once):消息可能会丢失,但绝不会被重复发送。
- 至少一次(at least once):消息不会丢失,但有可能被重复发送。(kafka默认)
- 精确一次(exactly once):消息不会丢失,也不会被重复发送。
精确处理一次语义的实现
-
幂等性producer
- 在 Broker 端多保存一些字段。当 Producer 发送了具有相同字段值的消息后,Broker 能够自动知晓这些消息已经重复了,于是可以在后台默默地把它们“丢弃”掉
- 1.只保证单分区的幂等性。2.只保证单会话幂等性
-
事务
- 事务能够保证跨分区、跨会话间的幂等性
- read_uncommitted,read_committed
服务端
处理请求
Reactor 模式是事件驱动架构的一种实现方式,特别适合应用于处理多个客户端并发向服务器端发送请求的场景。
控制器
在 Kafka 集群中会有一个或多个 broker,其中有一个 broker 会被选举为控制器,它负责管理整个集群中所有分区和副本的状态,kafka 集群中只能有一个控制器。
Broker 在启动时,会尝试去 ZooKeeper 中创建 /controller 节点。Kafka 当前选举控制器的规则是:第一个成功创建 /controller 节点的 Broker 会被指定为控制器。
-
作用
- 主题管理(创建、删除、增加分区)
- 分区重分配:当为某个 topic 增加分区数量时,由控制器负责分区的重新分配。
- Preferred 领导者选举:当某个分区的 leader 副本出现故障时,由控制器负责为该分区选举新的 leader 副本。
- 集群成员管理(新增 Broker、Broker 主动关闭、Broker 宕机)
- 数据服务
高水位和Leader Epoch
- 消费者只能消费已提交消息, 分区的高水位就是其 Leader 副本的高水位
- 分区 ISR 集合中的每个副本都会维护自己的 LEO,而 ISR 集合中最小的LEO 即为分区的 HW
ISR、OSR、 AR 是什么?
ISR:In-Sync Replicas 副本同步队列
OSR:Out-of-Sync Replicas
AR:Assigned Replicas 所有副本
ISR是由leader维护,follower从leader同步数据有一些延迟(具体可以参见 图文了解 Kafka 的副本复制机制),超过相应的阈值会把 follower 剔除出 ISR, 存入OSR(Out-of-Sync Replicas )列表,新加入的follower也会先存放在OSR中。AR=ISR+OSR。
LEO 、HW、 LSO 、LW等分别代表什么?
LEO:是 LogEndOffset 的简称,代表当前日志文件中下一条
HW:水位或水印(watermark)一词,也可称为高水位(high watermark),通常被用在流式处理领域(比如Apache Flink、Apache Spark等),以表征元素或事件在基于时间层面上的进度。在Kafka中,水位的概念反而与时间无关,而是与位置信息相关。严格来说,它表示的就是位置信息,即位移(offset)。取 partition 对应的 ISR中 最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置上一条信息。
LSO:是 LastStableOffset 的简称,对未完成的事务而言,LSO 的值等于事务中第一条消息的位置(firstUnstableOffset),对已完成的事务而言,它的值同 HW 相同
LW:Low Watermark 低水位, 代表 AR 集合中最小的 logStartOffset 值。
Leader Epoch
Leader 副本高水位更新和 Follower 副本高水位更新在时间上是存在错配的。这种错配是很多“数据丢失”或“数据不一致”问题的根源。Leader Epoch 可以解决这个问题。
集群
-
本质就是一个只能追加写消息的提交日志。
-
基于领导者的副本机制
- 追随者副本不处理客户端请求,它唯一的任务就是从领导者副本异步拉取消息,并写入到自己的提交日志中,从而实现与领导者副本的同步。
- 当领导者副本挂掉了,Kafka 依托于 ZooKeeper 提供的监控功能能够实时感知到。
-
好处:方便实现“Read-your-writes”:能让消费者立刻读到信息。方便实现单调读(Monotonic Reads)。
-
AR:Assigned Replicas
-
In-sync Replicas(ISR)
-
Unclean 领导者选举(Unclean Leader Election):选非同步副本为ld,可能会造成数据丢失
不支持读写分离
Kafka 是不支持读写分离的,那么读写分离的好处是什么?主要就是让一个节点去承担另一个节点的负载压力,也就是能做到一定程度的负载均衡,而且 Kafka 不通过读写分离也可以一定程度上去实现负载均衡。
但是对于 Kafka 的架构来说,读写分离有两个很大的缺点
- 数据不一致的问题:读写分离必然涉及到数据的同步,只要是不同节点之间的数据同步,必然会有数据不一致的问题存在。
- 延时问题:由于 Kafka 独特的数据处理方式,导致如果将数据从一个节点同步到另一个节点必然会经过主节点磁盘和从节点磁盘,对一些延时性要求较高的应用来说,并不太适用。
负载均衡
实现
Kafka 的负责均衡主要是通过分区来实现的,我们知道 Kafka 是主写主读的架构,如下图:
共三个 broker ,里面各有三个副本,总共有三个 partation, 深色的是 leader,浅色的是 follower,上下灰色分别代表生产者和消费者,虚线代表 follower 从 leader 拉取消息。
我们从这张图就可以很明显的看出来,每个 broker 都有消费者拉取消息,每个 broker 也都有生产者发送消息,每个 broker 上的读写负载都是一样的,这也说明了 kafka 独特的架构方式可以通过主写主读来实现负载均衡。
负载不均匀
- broker 端分配不均:当创建 topic 的时候可能会出现某些 broker 分配到的分区数多,而有些 broker 分配的分区少,这就导致了 leader 多副本不均。
- 生产者写入消息不均:生产者可能只对某些 broker 中的 leader 副本进行大量的写入操作,而对其他的 leader 副本不闻不问。
- 消费者消费不均:消费者可能只对某些 broker 中的 leader 副本进行大量的拉取操作,而对其他的 leader 副本不闻不问。
- leader 副本切换不均:当主从副本切换或者分区副本进行了重分配后,可能会导致各个 broker 中的 leader 副本分配不均匀。
Kafka 面试要点
-
你是否了解 Kafka?回答 kafka 的基本结构,也就是从 partition, broker 等几个基本概念开始回答
-
你用消息队列做过什么?
-
Kafka 的高性能是如何保证的?核心是零拷贝,Page Cache,顺序写,批量操作,数据压缩,日志分段存储;
-
Kafka 的 ISR 是如何工作的?核心是理解Kafka 如何维护 ISR,什么情况下会导致一个 partition 进去(或者出来)ISR
-
Kafka 的负载均衡策略有哪些?列举策略,要注意分析优缺点。更进一步可以讨论更加宽泛的负载均衡的做法,和 RPC 之类的负载均衡结合做对比
-
为什么 Kafka 的从 Partition 不能读取?违背直觉的问题,关键是要协调偏移量的代价太大;
-
为什么 Kafka 在消费者端采用了拉(PULL)模型?注意和 PUSH 模型做对比。最好是能够举一个适用 PUSH 的例子。
- 因为消费者以自身性能为基准,broker不知道消费者的消费能力
-
分区过多会引起什么问题?又是一个违背直觉的问题,核心在于顺序写
-
如何确定合适的分区数量?
- 确保consumer来得及消费
-
如何解决 Topic 的分区数量过多的问题?
- 开更多kafka集群
-
如何保证消息有序性?方案有什么缺点?
- 抓住核心,相关的消息要确保发送到同一个分区,例如 ID 为1的永远发到分区1
-
Kafka能不能重复消费?当然可以。但是要强调,一般的消费者都要考虑幂等的问题
-
如何保证消息消费的幂等性?就是去重,简单就是数据库唯一索引,高级就是布隆过滤器 + 唯一索引
-
如何保证只发送(或者只消费)一次?属实没必要,做好消费幂等简单多了
-
Rebalance 发生时机,rebalance 过程,rebalance 有啥影响?如何避免 rebalance?核心把 rebalance 的过程背下来
-
消息积压怎么办?没啥好办法,也就是加快消费,合并消息
Rebalance
在Kafka中,当有新消费者加入或者订阅的topic数发生变化时,会触发Rebalance(再均衡:在同一个消费者组当中,分区的所有权从一个消费者转移到另外一个消费者)机制,Rebalance顾名思义就是重新均衡消费者消费。Rebalance的过程如下:
第一步:所有成员都向coordinator发送请求,请求入组。一旦所有成员都发送了请求,coordinator会从中选择一个consumer担任leader的角色,并把组成员信息以及订阅信息发给leader。
第二步:leader开始分配消费方案,指明具体哪个consumer负责消费哪些topic的哪些partition。一旦完成分配,leader会将这个方案发给coordinator。coordinator接收到分配方案之后会把方案发给各个consumer,这样组内的所有成员就都知道自己应该消费哪些分区了。
JoinGroup 请求的处理过程。
SyncGroup 请求的处理流程。
kafka 为什么这么快/高性能?
-
顺序读写
- Kafka 的数据,可以看做是 AOF (append only file),它只允许追加数据,而不允许修改已有的数据。(后面是亮点)该手段 也在数据库如 MySQL,Redis上很常见,所以Kafka 用机械硬盘就可以了,机械磁盘 Kafka 和 SSD Kafka 在性能上差距不大; 磁盘分为顺序读写与随机读写,基于磁盘的随机读写确实很慢,但磁盘的顺序读写性能却很高,kafka 这里采用的就是顺序读写。
-
Page Cache 为了优化读写性能,Kafka 利用了操作系统本身的 Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。Kafka 允许落盘的时候,是写到 Page Cache的时候就返回,还是一定要刷新到磁盘(主要就是mmp之后要不要强制刷新 磁盘),类似的机制在 MySQL, Redis上也是常见,如果写到 Page Cache 就返回,那么会存在数据丢失 的可能。
-
零拷贝 Kafka使用了零拷贝技术,也就是直接将数据从内核空间的读缓冲区直接拷贝到内核空间的 socket 缓冲区,然后再写入到 NIC 缓冲区,避免了在内核空间和用户空间之间穿梭。在 Linux 上 Kafka 使用了两种手段,mmp (内存映射,一般我都记成妈卖批,哈哈哈) 和 sendfile,前者用于解决 Producer 写 入数据,后者用于 Consumer 读取数据;
-
分区分段+索引
- Kafka 的 message 是按 topic分 类存储的,topic 中的数据又是按照一个一个的 partition 即分区存储到不同 broker 节点。每个 partition 对应了操作系统上的一个文件夹,partition 实际上又是按照segment分段存储的。
- 通过这种分区分段的设计,Kafka 的 message 消息实际上是分布式存储在一个一个小的 segment 中的,每次文件操作也是直接操作的 segment。为了进一步的查询优化,Kafka 又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。
- Kafka 将日志分成不同的段,只有最新的段可以写,别的段都只能读。同时为每一个段保存了偏移量索引文件和时间戳索 引文件,采用二分法查找数据,效率极高。同时 Kafka 会确保索引文件能够全部装入内存,以避免读取索引引发磁盘 IO。(这里有一点很有 意思,就是在 MySQL 上,我们也会尽量说把索引大小控制住,能够在内存装下,在讨论数据库磁盘 IO 的时候,我们很少会计算索引无法装 入内存引发的磁盘 IO,而是只计算读取数据的磁盘 IO)
-
批量读写
- Kafka 数据读写也是批量的而不是单条的,这样可以避免在网络上频繁传输单个消息带来的延迟和带宽开销。假设网络带宽为10MB/S,一次性传输10MB的消息比传输1KB的消息10000万次显然要快得多。
- 包括 Producer 批量发送,也包括 Broker 批量落盘。批量能够放大顺序写的优势,比如说 Producer 还没攒够一批数据发送就 宕机,就会导致数据丢失;(充分利用操作系统buffer,减少IO)
-
数据压缩
- Kafka 提供了数据压缩选项,采用数据压缩能减少数据传输量,提高效率。
- Kafka 把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络 IO 损耗,通过 mmap 提高 I/O 速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合 sendfile 进行直接读取。
零拷贝
正常情况
稍微改进
零拷贝
Kafka 的 ISR 是如何工作的
ISR 是分区同步的概念。Kafka 为每个主分区维护了一个 ISR,处于 ISR 的分区意味着与主分区保持了同 步(所以主分区也在 ISR 里面)。
当 Producer 写入消息的时候,需要等 ISR 里面分区的确认,当 ISR 确认之后,就被认为消息已经提交成 功了。ISR 里面的分区会定时从主分区里面拉取数据,如果长时间未拉取,或者数据落后太多,分区会被 移出 ISR。ISR 里面分区已经同步的偏移量被称为 LEO(Log End Offset),最小的 LEO 称为 HW,也就是消费者可以消费的最新消息。
(高水位,high water,这个用木桶来比喻就很生动,ISR 里面的分区已同步消息就是木板,高水位就取 决于最短的那个木板,也就是同步最落后的) 当主分区挂掉的时候,会从 ISR 里面选举一个新的主分区出来。
我们在 Producer 里面可以控制 ACK 机制。Producer 可以配置成三种:
- Producer 发出去就算成功;
- Producer 发出去,主分区写入本地磁盘就算成功;
- Producer 发出去,ISR 所有的分区都写入磁盘,就算成功; 其性能依次下降,但是可靠性依次上升。
因为 ISR 里面包含了主分区,也就是说,如果整个 ISR 只有主分区,那么全部写入就退化为主分区写入。 所以在可靠性要求非常高的情况下,我们要求 ISR 中分区不能少于三个(有人说两个)。该参数可以在 Broker 中配置(min.insync.replicas)
ISR 的同步机制和其它中间件机制也是类似的,在涉及主从同步的时候都要在性能和可靠性之间做取舍。 通常的选项都是:
-
主写入就认为成功
-
主写入,至少一个从写入就认为成功;
-
主写入,大部分从库写入就认为成功(一般“大部分”是可以配置的,从这个意义上来说,2和3可以 合并为一点);
-
主写入,所有从库写入就认为成功;
为什么 Kafka 的从 Partition 不能读取?
首先是 Kafka 自身的限制,即 Kafka 强制要求一个 Partition 只能有一个 Consumer,因此 Consumer 天然只需要消 费主 Partition 就可以。 那么假如说 Kafka 放开这种限制,比如说有多个 Consumer,分别从主 Partition 和从 Partition 上读取数据,那么会 出现一个问题:即偏移量如何同步的问题。例如一个 Consumer 从 Partition A 读取了 0- 100 的消息,那么另外一个 Consumer 从 Partition B 上读取,就只能读取 100 之后的数据。那么 Kafka 就需要在不同的 Partition 之间协调这个 已读取偏移量。而这是分布式一致性的问题,难以解决。
MySQL 的主从模式比起来,并没有这种问题,即 MySQL 不需要进行类似偏移量的协商。 而从另外一个角度来说,Kafka 的读取压力是远小于 MySQL 的,毕竟一个 Topic,是不会有特别多的消费者的。并且 Kafka 也不需要支持复杂查询,所以完全没必要读取从 Partition 的数据。
Kafka 的可靠性是怎么保证的?
- acks
这个参数用来指定分区中有多少个副本收到这条消息,生产者才认为这条消息是写入成功的,这个参数有三个值:
- 1.acks = 1,默认为1。生产者发送消息,只要 leader 副本成功写入消息,就代表成功。这种方案的问题在于,当返回成功后,如果 leader 副本和 follower 副本还没有来得及同步,leader 就崩溃了,那么在选举后新的 leader 就没有这条消息,也就丢失了。
- 2.acks = 0。生产者发送消息后直接算写入成功,不需要等待响应。这个方案的问题很明显,只要服务端写消息时出现任何问题,都会导致消息丢失。
- 3.acks = -1 或 acks = all。生产者发送消息后,需要等待 ISR 中的所有副本都成功写入消息后才能收到服务端的响应。毫无疑问这种方案的可靠性是最高的,但是如果 ISR 中只有leader 副本,那么就和 acks = 1 毫无差别了。
- 消息发送的方式
生产者发送消息有三种方式,发完即忘,同步和异步。我们可以通过同步或者异步获取响应结果,失败做重试来保证消息的可靠性。
- 手动提交位移
默认情况下,当消费者消费到消息后,就会自动提交位移。但是如果消费者消费出错,没有进入真正的业务处理,那么就可能会导致这条消息消费失败,从而丢失。我们可以开启手动提交位移,等待业务正常处理完成后,再提交offset。
-
通过副本 LEO 来确定分区 HW
什么情况下 kafka 会丢失消息?
Kafka 有三次消息传递的过程: 生产者发消息给 Broker,Broker 同步消息和持久化消息,Broker 将消息传递给消费者。
这其中每一步都有可能丢失消息.
-
1.生产者发送数据: 在第 11 问中的 acks中有说到
- 当 acks 为 0,只要服务端写消息时出现任何问题,都会导致消息丢失。
- 当 acks 配置为 1 时,生产者发送消息,只要 leader 副本成功写入消息,就代表成功。这种方案的问题在于,当返回成功后,如果 leader 副本和 follower 副本还没有来得及同步,leader 就崩溃了,那么在选举后新的 leader 就没有这条消息,也就丢失了。
-
2.Broker 存储数据:kafka 通过 Page Cache 将数据写入磁盘。
- Page Cache 就是当往磁盘文件写入的时候,系统会先将数据流写入缓存中,但是什么时候将缓存的数据写入文件中是由操作系统自行决定。所以如果此时机器突然挂了,也是会丢失消息的。
-
3.消费者消费数据:在开启自动提交 offset 时,只要消费者消费到消息,那么就会自动提交偏移量,如果业务还没有来得及处理,那么消息就会丢失。
kafka日志分段存储
midkuro.github.io/2021/02/27/…
kafka里面数据文件就叫日志文件。一个分区下面可以有n多个日志文件,默认一个日志文件的大小是1G。
如果不分段存储,是一个大大的文件,加载到内存以及写的时候,会非常耗费io。
分段存储还可以使消费者不容易产生竞争。