kafka 最初由 Linkedin 公司开发,是一个 分布式、支持分区、多副本的,基于 zookeeper 协调的分布式发布订阅消息系统,该公司在 2010 年将 kafka 贡献给 apache 基金会,1年后升级为顶级项目。
kafka 由 scalar 语言编写, 并提供了多种语言的接口。
kafka 特性
kafka 是一个分布式系统,天然具有分布式的所有优势。
1. 高吞吐量:支持每秒百万级的消息处理,最低只有几毫秒的延迟
2. 可扩展性:集群优势
3. 容错:集群优势,允许部分节点挂机
4. 持久化:消息被持久化到磁盘,并支持副本
kafka 使用场景
kafka 可以使用到许多场景,包括实时和离线,这里举几个例子,以便扩展你的思维。
1. 日志存储:将各个服务的 log 统一存储到 kafka,并提供通用接口,方便各种调用,如 hadoop
2. 消息系统:kafka 本身就是消息队列
3. 用户活动跟踪:将各种 web / app 的用户行为存储到 kafka,用于实时监控分析,或者迁移到 hadoop、hdfs 等做离线分析
4. 运营监控:如各种大屏展示
5. 流式处理:如 spark storm
kafka 基础架构
kafka 的特点在于 发布订阅,其机制如下图
producer 向 kafka send 消息,send 之前,会对消息进行分类,即 topic
consumer 会与 kafka 建立长连接,当 topic 中有新的消息时,所有 consumer 会自动接收到;当然 consumer 也可以主动拉取历史消息
Broker
集群中的一个节点,很多核心的逻辑部署在 broker 上
controller:中央控制器,管理分区和副本状态,并负责管理 分区 的重新分配,包括 分区选举
Isr:同步副本
[root@localhost kafka_2.11-0.9.0.0]# bin/kafka-topics.sh --describe --zookeeper localhost:2191 --topic sq Topic:sq PartitionCount:4 ReplicationFactor:3 Configs: Topic: sq Partition: 0 Leader: 0 Replicas: 0,10,11 Isr: 0,10,11 Topic: sq Partition: 1 Leader: 10 Replicas: 10,11,12 Isr: 10,11,12 Topic: sq Partition: 2 Leader: 11 Replicas: 11,12,0 Isr: 11,12,0 Topic: sq Partition: 3 Leader: 12 Replicas: 12,0,10 Isr: 12,0,10
topic sq 4个分区,3个副本,每个分区有个 leader,作为读写的首选 partition
producer 详解
producer 处理流程如下图
以下是有顺序的解释
1. ProducerRecord 代表数据,数据必须包含 topic 和 value,key 和 partition 可选 --- 创建数据
2. Serializer 代表格式化 key 和 value --- 格式化
3. Partitioner 代表分区策略 --- 根据 key 计算分区
// 如果 ProducerRecord 中指定了分区,此步忽略,直接返回指定的 partition
4. 有了分区和topic,把该消息添加到对应的 topic 和分区上的 batch
5. 专有线程负责发送 batch record 到 broker
6. broker 接收到消息,返回一个 response
// 如果写入成功,返回的是 RecordMetadata,包括 topic、partition、offset
// 如果写入失败,返回 error,此时 producer 会自动重复发几次,直到 producer 返回 error
Producer 发送消息有 3 种方式
1. 立即发送:只管发送给 kafka,不关心是否发送成功
// 即使不成功,程序也不会提示;不过大部分情况是成功的,因为 kafka 具有高可用性,且自动重试;但不保证一定成功
2. 同步发送:通过 send 方法发送消息,并返回 future 对象,get 方法会等待 future 对象,检验是否发送成功
3. 异步发送:通过带有回调函数的 send 方法发送消息,当 producer 收到 broker 的 response 会触发回调函数
consumer 详解
由于 consumer 处理消息 和 commit 提交反馈这两个动作不是原子性的,或者说不在一个事务内,由此 consumer 具有 3 种消费模式。
1. 最多一次:表示这条消息最多被消费一次,可能是 0 次
// 客户端收到消息后,在处理消息前,自动提交,这样 kafka 就会认为这条消息被消费过了,offset 会加一,这样这条消息就不会再被消费,也就是只能被消费一次
实现方式: 较小时间间隔内自动提交
设置 enable.auto.commit为ture
设置 auto.commit.interval.ms为一个较小的时间间隔.
client 不要调用 commitSync(),kafka 在特定的时间间隔内自动提交。
2. 最少一次:表示这条消息最少被消费一次,可能是多次
// 客户端收到消息后,先处理消息,处理完了再提交,可是处理过程中提交前,kafka 服务可能挂了,或者网络原因等各种异常,导致没有提交成功,kafka 会认为这条消息没被消费
实现方式:手动提交或者较大时间间隔自动提交
方法一
设置 enable.auto.commit 为false
client 调用 commitSync(),增加消息偏移;
方法二
设置 enable.auto.commit 为ture
设置 auto.commit.interval.ms 为一个较大的时间间隔.
client调用commitSync(),增加消息偏移;
3. 正好一次:表示这条消息只好被消费一次
// 保证处理消息和提交在一个事务内
实现方式比较麻烦,参考 Kafka client 消息接收的三种模式
在消费者消费过程中, kafka 会使用 offset 来记录当前消费的位置
kafka 分区机制
1. broker 端支持 topic 分区,一个 topic 可以有多个分区,一个分区可以有多个副本,一个分区的多个副本分布在不同 broker 上;
2. partition 使得 kafka 作为 MQ 可以横向扩展,分区越多,吞吐量越大;
3. 副本中有一个 leader,其他都是 follower,message 先写到 leader 上,再由 leader 同步到 follower;
4. 一个 partition 中 message 的顺序就是 producer 发送的顺序,所以一个 partition 是一个有序的队列;
5. 最晚被接收的 message 会被最后消费;
6. partition 中每个 message 都有一个序列号叫 offset,作为消息的标识符;
7. 在 broker 中每个分区是一个目录,目录名字规则为 topic-副本序号,副本序号从 0 开始;
下面的五个文件和 partition 存在一起,都各有用处。
cleaner-offset-checkpoint:存了每个log的最后清理offset,log 指的是消息
meta.properties:broker.id 信息
recovery-point-offset-checkpoint:表示已经刷写到磁盘的记录。recoveryPoint以下的数据都是已经刷 到磁盘上的了。
replication-offset-checkpoint: 用来存储每个replica的HighWatermark的(high watermark (HW),表示已经被commited的message,HW以下的数据都是各个replicas间同步的,一致的。)
8. 每个 partition 由多个大小相等的 segment (段)组成,segment 是消息的真实载体,每个 segment 的消息数量不一定相等;
9. 每个 segment file 由两部分组成:index 和 log,分别存放 索引 和 数据
00000000000004909731.index 00000000000004909731.log // 1G文件--Segment 00000000000005048975.index // 数字是Offset 00000000000005048975.log
10. index 和 log 映射关系
.index文件存放的是message逻辑相对偏移量 (相对offset=绝对offset-base offset)与在相应的.log文件中的物理位置(position);
但.index并不是为每条message都指定到物理位置的映射,而是以entry为单位,每条entry可以指定连续n条消息的物理位置映射
例如:假设有20000~20009共10条消息,.index文件可配置为每条entry指定连续10条消息的物理位置映射,该例中,index entry会记录偏移量为20000的消息到其物理文件位置,一旦该条消息被定位,20001~20009可以很快查到。
每个entry大小8字节,前4个字节是这个message相对于该log segment第一个消息offset(base offset)的相对偏移量,后4个字节是这个消息在log文件中的物理位置。
11. segment 文件命名规则:partion 全局的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset 值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
12. 多个 segment 方便 old message 被删除;
13. segment 文件的生命周期有 kafka 参数决定;
消费组与分区的重平衡-reblance
之前讲到了消费者组的作用,不熟悉的看我之前的博客。
如果一个新的消费者加入消费者组,那么他应该会被分配 1 个或多个分区,而这些分区原来是由其他消费者负责;
当一个消费者离开消费组时,他所消费的分区会被分给其他消费者;
这种机制叫做 重平衡。
这是 kafka 的一个重要特性,它保证了高可用和横向扩展。
缺点
1. 在 重平衡 期间,所有消费者不能进行消费,导致整个消费组不可用;
2. 重平衡 会导致原来的消费者状态过期,消费者需要重新更新状态,导致消费性能降低
如何监控消费者
消费者需要定期向一个作为 组协调者 的 broker 发送心跳,注意 这个 broker 不是特定的,每个组的 broker 可能都不同;
消费者拉取或者提交时,就会发送心跳; 【老版本】
如果超过一定时间没有发送心跳,那么他的会话 session 就会过期,触发 重平衡。
从上可以看出,从宕机到被监测到宕机,是有一个时间段的,这段时间这个消费者负责的分区都没有消息被消费;
所以通常 我们需要 优雅的关闭,这样消费者会发生 离开 的消息到 组协调者,从而避免这个空白期
可能有些同学会发现,上面讲的定期发送心跳和拉取提交发送心跳似乎矛盾了,确实如此,所以新版的 kafka 将发送心跳和拉取消息进行分离,拉取消息的频率不影响心跳发送。
另外更高版本的Kafka支持配置一个消费者多长时间不拉取消息但仍然保持存活,这个配置可以避免活锁(livelock)。活锁,是指应用没有故障但是由于某些原因不能进一步消费。
kafka 与 zookeeper 关系
zookeeper 起到协调控制的作用。
1. 管理 broker 和 consumer 的加入与离开
2. 触发负载均衡,当有 broker 或者 consumer 加入或离开时,会触发负载均衡算法,是的一个 消费组 内的 多个消费者 消费平衡。
3. 维护消费关系 和 每个 partition 的消费信息
1. 每个broker启动后会在zookeeper上注册一个临时的broker registry,包含broker的ip地址和端口号,所存储的topics和partitions信息。 2. 每个consumer启动后会在zookeeper上注册一个临时的consumer registry:包含consumer所属的consumer group以及订阅的topics。 3. 每个consumer group关联一个临时的owner registry和一个持久的offset registry。对于被订阅的每个partition包含一个owner registry,内容为订阅这个partition的consumer id;同时包含一个offset registry,内容为上一次订阅的offset。
新版的 kafka 已经把 consumer 部分信息移到 kafka 上了。
参考资料:
https://blog.csdn.net/lingbo229/article/details/80761778 史上最详细
https://blog.csdn.net/luanpeng825485697/article/details/81036028 很不错的教程,原理与实操都比较深入
https://www.jianshu.com/p/d3e963ff8b70