最近在公司内部进行了一个知识分享,主题是kafka。分享ppt我会上传放在文末。我为了准备这个分享,几乎花了完整两周时间,看了两本书,阅读了几十篇博客,从中提炼出几十页ppt拿出来演讲,分享完后总感觉自己还有很多东西没有讲出来。
这次就把我两个星期的学习成果形成博客。便于自己回顾和大家分享。
旧:在kafka0.8.x版本的时候,kafka主要是作为一个分布式的、可分区的、具有副本数的日志服务系统, 具有高水平扩展性、高容错性、访问速度快、分布式等特性;主要应用场景是:日志收集系统和消息系统
新:0.10.x版本及以上,Kafka是一个分布式的流处理平台(数据注入功能,数据存储功能,流处理功能)
注:今天主要还是讲它作为一个消息中间件的功能作用,kafka各个版本内部处理有差异,如何分享是一个挑战。学习的时候一定要带上版本的概念,因为网上很多资料都没有说明版本,然后会造成理解偏差。我们这次主要分享新版本kafka
解读
ProducerRecord:每个消息是一个ProducerRecord对象,其中Topic和Value值必填,partition和key非必填。
过程
send()方法大致过程为:
设置序列化器->设置分区->放入队列缓存->等待时机push到broker
注:
不是直接发送给服务端,而是先在客户端把消息放入队列中, 然后由一个消息发送线程从队列中拉取消息,以批量的方式发送消息给服务端。 Kafka的记录收集器( RecordAccumulator)负责缓存生产者 客户端产生的消息,发送线程(Sender)负责读取记录收集器的批量消息,通过网络发送给服务端。为了保证客户端网络请求的快速响应,Kafka使用选择器( Selector) 处理网络连接和读写处理,使用 网络连接( NetworkClient)处理客户端网络请求。
选择分区流程图如下:
其中,散列化方法为:Utils.murmur2(keyBytes)
可以发现,两个方法其实都是异步返回。
同步方式,第一种,调用send()后,马上get(),实现同步调用。
异步方式,第二种,在callback中进行内容处理,实现异步调用。
序列化器
可以使用内置序列化器,比如StringSerializer,IntegerSerializer,ByteArraySerializer等基本的序列化器。
也可以自定义,需要实现org.apache.kafka.common.serialization.Serializer接口
注:官方建议不要自定义序列化器,因为在消费端,需要使用同样的反序列化器。使用kafka自带的,可以避免很多问题。
kafka重要的配置文件有三个:
server.properties
broker.properties
consumer.properties
都可以去这里查看配置的意义和解释:官网配置
这里讲一下生产者重要的几个配置
前面讲到Broker其实可以当作一个服务器来理解,它上面有很多partition和partition的副本。那么partition和副本直接的数据是如何同步呢?如下图:
这个图信息量很大,可以看出,副本与leader之间的数据同步是副本去leader那里pull的过程。生产者发送消息到broker后,会根据配置的acks值,来决定何时返回。这个acks值,就是说这里的副本复制情况。
Partition是消息的分区队列,一个topic写入不用的partition,写入过程中会更新offset,过程如下:
上面图中,有几个重要的名词:
1.ISR(In-sync Replication)
ISR中的副本都要同步leader中的数据,只有都同步完成了数据才认为是成功提交了,成功提交之后才能供外界访问。
在这个同步的过程中,数据即使已经写入也不能被外界访问,这个过程是通过LEO-HW机制来实现的。
2.OSR(Out-sync Replication)
OSR内的副本是否同步了leader的数据,不影响数据的提交,OSR内的follower尽力的去同步leader,可能数据版本会落后。
最开始所有的副本都在ISR中,在kafka工作的过程中,如果某个副本同步速度慢于replica.lag.time.max.ms指定的阈值,则被踢出ISR存入OSR,如果后续速度恢复可以回到ISR中。
3.LEO
LogEndOffset:分区的最新的数据的offset,当数据写入leader后,LEO就立即执行该最新数据。相当于最新数据标识位。
4.HW
HighWatermark:只有写入的数据被同步到所有的ISR中的副本后,数据才认为已提交,HW更新到该位置,HW之前的数据才可以被消费者访问,保证没有同步完成的数据不会被消费者访问到。相当于所有副本同步数据标识位。
Kafka使用消费组(consumer group)统一 了上面两种消息模型 。Kafka使用队列模型时,它可以将处理
工作平均分配给消费组中的消费者成员;使用发布订阅模式时,它可以将消息 广播给多个消费组。
采用多个消费组结合多个消费者,既可以线性扩展消息的处理能力,也允许消息被多个消费组订阅。
kafka的消费模式:
Kafka采用消费组保证了“一个分区只可被消费组中的一个消费者所消费” ,这意味着:
(1)在一个消费组中,一个消费者可以消费多个分区 。
(2)不同的消费者消费的分区一定不会重复,所有消费者一起消费所有 的分区 。
(3)在不同消费组中,每个消费组都会悄费所有的分区 。
(4)同一个消费组下消费者对分区是互斥的,而不同消费组之间是共享的。
由图中,我们知道,kafka的消费者客户端不断德调用poll()方法去轮询,从Broker中拉取消息。
topic下的一个分区只能被同一个consumer group下的一个consumer线程来消费,但反之并不成立,即一个consumer线程可以消费多个分区的数据
由图中,我们知道,新版本客户端中,消费者提交offset不再提交到zookeeper中,而是提交到Broker中的topic为_consumer_offset的分区上。
简单举个例子,假设目前某个consumer group下有两个consumer: A和B,当第三个成员加入时,kafka会触发rebalance并根据默认的分配策略重新为A、B和C分配分区,如图所示
消费组分配partition过程如图:
注:在coordinator收集到所有成员请求前,它会把已收到请求放入一个叫purgatory(炼狱)的地方
新版kafka有两个协调器:消费者协调器(ConsumerCoordinator)和组协调器(GroupCoordinator),这里图中提到的是消费者协调器?
Server.properties配置文件中,有log.dirs配置,指向的就是kafka文件存储位置。
我本机安装了一个单机版的kafka,文件如下:
文件目录解释:
kafka的partition其实就是一个个文件,然后kafka会根据配置把这些文件进行分段,每一段就叫做segment,如下图所示:
segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀".index"和“.log”分别表示为segment索引文件、数据文件.
segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。其中以索引文件中元数据3,497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message)、以及该消息的物理偏移地址为497。
zookeeper是什么,大家肯定不陌生,直接上图看下我的ppt:
上面可知zookeeper是存在于内存重的类似文件节点。那么,Kafka在zookeeper内部的存储结构是怎样的呢?一图说明一切:
通过查看kafka源码,我们知道,Kafka使用的是zkclient(https://github.com/sgroschupf/zkclient)开源第三方客户端。通信方式为监听器。
主要有以下三种监听器:
以下是基于ZK选举器选举主控制器的流程 (虽然每个代理节点都有一个控制器对象,但 Kafka集群只有一个主控制器 。)
如上图所示,读取OSR作为主副本会造成数据丢失。所以kafka会定时检查是否有所有ISR都为存活状态
以下两个问题经常出现在面试中,但是深入学习kafka之后,你会发现,能问出这种问题的一般都不怎么懂kafka,如果你深入了解Kafka,你可以从kafka的消息着手发问,而不是问一个什么条件都没有,半小时也讲不清楚的问题。
如何保证消息不丢失
默认保证at least once
挑战两种情况:
1,auto.commit.enable=true
2,auto.commit.enable=false
如何保证有序
分区有序。如何保证全局有序?(伪命题)