Kafka
# 一、Kafka基础
# Kafka是什么
Kafka 是一个分布式流式处理平台
流平台具有三个关键功能:
- 消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
- 容错的持久方式存储记录消息流:Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
- 流式处理平台: 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库
Kafka 主要有两大应用场景:
- 消息队列:建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
- 数据处理: 构建实时的流数据处理程序来转换或处理数据流。
# Kafka相比其他消息队列的优势
1、极致的性能:基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
2、生态系统兼容性无可匹敌:Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域
3、消息可靠性:: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
# Kafka为什么快
页缓存技术
kafka是基于操作系统的页缓存(page cache)来实现文件写入的,kafka在写入磁盘文件的时候,可以直接写入这个os cache里,也就是写入内存中,接下来由操作系统自己决定什么时候把os cache里的数据真正刷入磁盘文件中。
磁盘顺序写
kakfa在写数据的时候,是以磁盘顺序写的方式来写的,也就是说仅仅将数据追加到文件的末尾,不是在文件的随机位置来修改数据。
分区分段+索引
kakfa的message是按topic分类存储的,topic中的数据又是按照一个个partition即分区存储到不同broker节点。
通过这种分区分段的设计,Kafka 的 message 消息实际上是分布式存储在一个一个小的 segment 中的,每次文件操作也是直接操作的 segment。为了进一步的查询优化,Kafka 又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。
# Kafka基础概念
1、Producer(生产者) : 产生消息的一方。
2、Consumer(消费者) : 消费消息的一方。
3、Broker(代理) : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
4、Topic(主题) : Producer 将消息发送到特定的主题,Consumer 通过订阅特定的 Topic(主题) 来消费消息。
5、Partition(分区) : Partition 属于 Topic 的一部分。一个 Topic 可以有多个 Partition ,并且同一 Topic 下的 Partition 可以分布在不同的 Broker 上,这也就表明一个 Topic 可以横跨多个 Broker 。这正如我上面所画的图一样。
# Kafka多副本机制
Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。
生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。
Kafka 的多分区(Partition)以及多副本(Replica)机制有什么好处呢
1、Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。
2、Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力,不过也相应的增加了所需要的存储空间。
多副本同步流程
假设现在各种副本情况如下:
- leader副本:1、2、3
- follower1副本:1、2、3
- follower2副本:1、2、3
现在生产写入数据4和5,此时进行同步如下:
- leader副本:1、2、3、4、5
- foller1副本:1、2、3、4
- foller2副本:1、2、3、4
那么此刻HW为4,消费者只能消费到1-4的消息
# Kafka中AR、ISR、OSR三者的概念
AR
:分区中所有副本称为 ARISR
:所有与主副本保持一定程度同步的副本(包括主副本)称为 ISROSR
:与主副本滞后过多的副本组成 OSR
AR = ISR + OSR,一般情况下所有的follower副本都应该与leader副本保持一定程度的同步,AR=ISR
# Kakfa的ACK机制
- 0: 相当于异步操作,Producer 不需要Leader给予回复,发送完就认为成功,继续发送下一条(批)Message。此机制具有最低延迟,但是持久性可靠性也最差,当服务器发生故障时,很可能发生数据丢失。
- 1: Kafka 默认的设置。表示 Producer 要 Leader 确认已成功接收数据才发送下一条(批)Message。不过 Leader 宕机,Follower 尚未复制的情况下,数据就会丢失。此机制提供了较好的持久性和较低的延迟性。
- -1(all): Leader 接收到消息之后,还必须要求ISR列表里跟Leader保持同步的那些Follower都确认消息已同步,Producer 才发送下一条(批)Message。此机制持久性可靠性最好,但延时性最差
# kakfa生产者运行流程
- 一条消息发过来首先会被封装成一个 ProducerRecord 对象
- 对该对象进行序列化处理(可以使用默认,也可以自定义序列化)
- 对消息进行分区处理,分区的时候需要获取集群的元数据,决定这个消息会被发送到哪个主题的哪个分区
- 分好区的消息不会直接发送到服务端,而是放入生产者的缓存区,多条消息会被封装成一个批次(Batch),默认一个批次的大小是 16KB
- Sender 线程启动以后会从缓存里面去获取可以发送的批次
- Sender 线程把一个一个批次发送到服务端
# 合适分区数选择
没有标准的答案,要根据实际业务场景、软件条件、硬件条件来具体考量,一般设置为消费pod数量
# 分区数越多吞吐量越高
Kafka只允许单个分区中的消息被一个消费者线程消费,并非增加分区数就能一直增加吞吐量。
# 二、主从同步
kafka 通过配置 producer.type
来确定是异步还是同步,默认是同步
同步复制
Producer 会先通过Zookeeper识别到Leader,然后向 Leader 发送消息,Leader 收到消息后写入到本地 log文件。这个时候Follower 再向 Leader Pull 消息,Pull 回来的消息会写入的本地 log 中,写入完成后会向 Leader 发送 Ack 回执,等到 Leader 收到所有 Follower 的回执之后,才会向 Producer 回传 Ack。
异步复制
Kafka 中 Producer 异步发送消息是基于同步发送消息的接口来实现的,异步发送消息的实现很简单,客户端消息发送过来以后,会先放入一个 BlackingQueue
队列中然后就返回了。Producer 再开启一个线程 ProducerSendTread
不断从队列中取出消息,然后调用同步发送消息的接口将消息发送给 Broker。
# 1、为什么不支持读写分离
第一、主从分离与否没有绝对的优劣,它仅仅是一种架构设计,各自有适用的场景。
第二、Redis和MySQL都支持主从读写分离,我个人觉得这和它们的使用场景有关。对于那种读操作很多而写操作相对不频繁的负载类型而言,采用读写分离是非常不错的方案——我们可以添加很多follower横向扩展,提升读操作性能。反观Kafka,它的主要场景还是在消息引擎而不是以数据存储的方式对外提供读服务,通常涉及频繁地生产消息和消费消息,这不属于典型的读多写少场景,因此读写分离方案在这个场景下并不太适合。
第三、Kafka副本机制使用的是异步消息拉取,因此存在leader和follower之间的不一致性。如果要采用读写分离,必然要处理副本lag引入的一致性问题,比如如何实现read-your-writes、如何保证单调读(monotonic reads)以及处理消息因果顺序颠倒的问题。相反地,如果不采用读写分离,所有客户端读写请求都只在Leader上处理也就没有这些问题了——当然最后全局消息顺序颠倒的问题在Kafka中依然存在,常见的解决办法是使用单分区,其他的方案还有version vector,但是目前Kafka没有提供。
# 三、消息丢失和消息重复
# 如何保证顺序消费
# 重试机制
重试策略:Kafka提供了两种重试策略,即指数退避和恒定退避。指数退避会在每次重试时将重试等待时间乘以一个递增的倍数,直到达到最大重试等待时间。恒定退避则是每次重试都采用相同的等待时间间隔。
最大重试次数:Kafka允许设置最大重试次数,当达到最大重试次数后仍然无法成功发送消息,则会将消息放入错误日志中并进行相应的处理操作。
# 保证消费不丢失
出现原因
当前位移为x,然后拉取到x+8,提交了位移,如果消费在x+5的时候出现了异常,重新拉取,就从x+8开始,就存在消息丢失的情况
解决方案
手动提交位移
# 保证消息不重复消费
出现原因
位移提交的动作是在消费完所有拉取到的消息之后才执行的,当消费x+5的时候出现异常,在故障恢复之后,重新拉取消息是在x+2开始的,相当于就发生了重新消费。
解决方案
在发生异常时正确处理未提交的offset