基础篇
主题:
主题是一个逻辑上的概念,它还可以细分为多个分区,一个分区只属于单个主题,很多时候也会把分区称为主题分区(Topic-Partition)。同一主题下的不同分区包含的消息是不同的,分区在存储层面可以看作一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)
。
offset 是消息在分区中的唯一标识,Kafka 通过它来保证消息在分区内的顺序性
,不过 offset 并不跨越分区
,也就是说,Kafka 保证的是分区有序而不是主题有序
。
分区:
Kafka 中的分区可以分布在不同的服务器(broker
)上,也就是说,一个主题可以横跨多个 broker,以此来提供比单个 broker 更强大的性能。
副本:
Kafka 为分区引入了多副本(Replica)机制
,通过增加副本数量可以提升容灾能力。
同一分区的不同副本中保存的是相同的消息
(在同一时刻,副本之间并非完全一样),副本之间是“一主多从”的关系,其中 leader 副本负责处理读写请求,follower 副本只负责与 leader 副本的消息同步
。副本处于不同的 broker 中,当 leader 副本出现故障时,从 follower 副本中重新选举新的 leader 副本对外提供服务。Kafka 通过多副本机制实现了故障的自动转移,当 Kafka 集群中某个 broker 失效时仍然能保证服务可用。
容灾:kafka消费者也具备容灾的能力
,当消费者使用pull从服务端拉去消息,并且保存了消费的具体offset
,当消费者宕机恢复后,会根据之前保存的消费者位置重新进行消费,这样就不会造成消息丢失。
生产者
拦截器:
拦截器是在Kafka0.10.0.0版本中就已经引入的一个功能,Kafka一共有两种拦截器。生产者拦截器
和消费者拦截器
。
close() 方法主要用于在关闭拦截器时执行一些资源的清理工作
。在这3个方法中抛出的异常
都会被捕获并记录到日志
中,但并不会再向上传递
。
Kafka中不仅可以指定一个拦截器还可以指定多个拦截器形成一个拦截器链
。拦截器链会根据配置时配置的拦截器顺序来执行(配置的时候,各个拦截器之间使用逗号隔开)。如果拦截器链中的某个拦截器的执行需要依赖上一个拦截器的输出,那么就有可能产生“副作用”。如果第一个拦截器因为异常执行失败,那么第二个也就不能继续执行。在拦截链中,如果某个拦截器执行失败,那么下一个拦截器会接着从上一个执行成功的拦截器继续执行
。
序列化器:生产者
需要用序列化器(Serializer)将key和value序列化成字节数组
才可以将消息传入Kafka。消费者需要用反序列化器(Deserializer)
把从Kafka中收到的字节数组转化成相应的对象。
分区器:
消息通过send()方法发送broker
的过程中,有可能会经过拦截器
,序列化器,之后,就会需要确定消息要发往的分区。如果ProducerRecord中指定了partition字段,那么就不需要分区器的作用。因为partition代表的就是索要发往的分区号
。
在默认分区器 DefaultPartitioner 的实现中,close()是空方法,而 partition() 方法
中定义了主要的分区分配逻辑
。如果 key 不为 null
,那么默认分区器会对 key 进行哈希
(采用 MurmurHash2 算法,具备高运算性能及低碰撞率)根据最终得到的哈希值,与分区的数量取模运算得到分区编号来匹配分区
,相同key得到的哈希值是一样的,所以当key一致,分区数量不变
的情况下,会将消息写入同一个分区
(注意:在不改变主题分区数量的情况下,key 与分区之间的映射可以保持不变。不过,一旦主题增加了分区,那么就难以保证key与分区的映射关系)。如果,key 是 null
,那么消息会以轮询的方式写入分区
。(注意:如果 key 不为null,那么计算得到的分区号会是所有分区中的一个。如果 key 为 null 并且有可用的分区的时候,那么计算得到的分区号仅为可用分区中的任意一个。)
消息累加器:
RecordAccumulator也叫消息累加器,主要用来缓存消息
以便Sender线程可以批量发送
,进而减少网络传输的资源消耗来提升性能
。 是在客户端开辟出的一块内存区域。
生产者客户端整体架构
整个生产者客户端主要有两个线程,主线程
以及Sender线程
。Producer在主线程
中产生消息
,然后通过拦截器
,序列化器
,分区器
之后缓存到消息累加器RecordAccumulator中。Sender线程从RecordAccumulator中获取消息并发送到kafka中。
RecordAccumulator
主要用来缓存消息
,这样发送的时候进行批量发送以便减少相应的网络传输
。RecordAccumulator缓存的大小可以通过配置参数buffer.memory配置,默认是32M。如果创建消息的速度过快,超过sender发送给kafka服务器的速度,会导致缓存空间不足,这个时候sender线程可能会阻塞或者抛出异常,max.block.ms配置决定阻塞的最大时间。
RecordAccumulator中为每个分区维护了一个双端队列
,队列中的内容是ProducerBatch,即Deque,创建消息写入到尾部,发送消息从头部读取。ProducerBatch是消息发送的一个批次,里面包含了一个或多个ProducerRecord
。
消费者
消费者和消费组:
- 当消息发布到主题后,只会被投递给订阅它的
每个消费组中的一个消费者
。 -
每个分区
只能被一个消费组中
的一个消费者
所消费。 - 如果所有的消费者都属于同一个消费组,所有消息都均衡的投递给每个消费者,这是
点对点模式
- 如果所有的消费者都属于不同的消费组,所有的消息都会广播给所有的消费者,这个就是
发布/订阅模式
- Kafka解决重复消费问题使用
commitSync()
方法拉取最新的位移,commitSync()方法是同步执行
的,会耗费一定的性能。
再均衡:
分区的所有权从一个消费者转移到另一个消费者
,这样的行为被称为分区再均衡
https://cloud.tencent.com/developer/article/1708081
多线程
kafkaProducer
是线程安全
,kafkaConsumer
是线程非安全
,KafkaConsumer 中定义了一个acquire()
方法,用来检测当前是否只有一个线程在操作,若有其他线程正在操作则会抛出ConcurrentModifcationException
异常.acquire()
方法通过线程操作计数标记
的方式来检测线程是否发生了并发
操作,以此保证只有一个线程
在操作。acquire() 方法和 release() 方法
成对出现,表示相应的加锁和解锁操作。
KafkaConsumer实现多线程时通常由两种实现方法:
主题分区
分区副本分配
优选副本选举
分区重分配
复制限流
日志存储
文件目录布局
消息压缩
日志索引
日志清理
日志压缩
页缓存
kafka中大量使用了页缓存,这是kafka实现高吞吐量的重要元素之一。
零拷贝
服务端和客户端
服务端
时间轮
延时操作
控制器
客户端
分区策略
RangeAssignor 分配策略:
RoundRobinAssignor 分配策略:
stickyAssignor 分配策略:
事务
幂等
幂等对接口多次调用所产生的结果和调用一次是一致的。幂等性并不能跨多个分区运作,而事务可以弥补这个缺陷。事务可以保证对多个分区写入操作的原子性。
为什么不支持读写分离
可靠性分析
kafka应用
实时流式计算
近几年来实时流式计算发展迅速,主要原因是实时数据的价值和对于数据处理架构体系的影响。实时流式计算包含了 无界数据 近实时 一致性 可重复结果 等等特征。a type of data processing engine that is designed with infinite data sets in mind 一种考虑了无线数据集的数据处理引擎。
1、无限数据
:一种不断增长的,基本上无限的数据集。这些通常被称为“流式数据”。无限的流式数据集可以称为无界数据,相对而言有限的批量数据就是有界数据。
2、无界数据处理
:一种持续的数据处理模式,应用于上面的无界数据。批量处理数据(离线计算)也可以重复运行来处理数据,但是会有性能的瓶颈。
3、低延迟,近实时的结果
:相对于离线计算而言,离线计算并没有考虑延迟的问题。
解决了两个问题,流处理可以提代批处理系统:
1、正确性
:有了这个,就和批量计算等价了。
Streaming需要能随着时间的推移依然能计算一定时间窗口的数据。Spark Streaming通过微批的思想解决了这个问题,实时与离线系统进行了一致性的存储,这一点在未来的实时计算系统中都应该满足。
2、推理时间的工具
:这可以让我们超越批量计算。
好的时间推理工具对于处理不同事件的无界无序数据至关重要。
而时间又分为事件时间和处理时间。
Kafka Streams
Kafka Streams被认为是开发实时应用程序的最简单方法。它是一个Kafka的客户端API库,编写简单的java和scala代码就可以实现流式处理。
优势:
- 弹性,高度可扩展,容错
- 部署到容器,VM,裸机,云
- 同样适用于小型,中型和大型用例
- 与Kafka安全性完全集成
- 编写标准Java和Scala应用程序
- 在Mac,Linux,Windows上开发
- Exactly-once 语义
消息路由
Kafka监控
监控数据来源
监控模块