
Kafka的高吞吐能力、缓存机制能有效的解决高峰流量冲击问题。实践表明,在未将kafka引入系统前,当互联网关发送的数据量较大时,往往会挂起关系数据库,数据常常丢失。在引入kafka后,更新程序能够结合能力自主处理消息,不会引起数据丢失,关系型数据库的压力波动不会发生过于显著的变化,不会出现数据库挂起锁死现象。
依靠kafka的订阅分发机制,实现了一次发布,各分支依据需求自主订阅的功能。避免了各分支机构直接向数据中心请求数据,或者数据中心依次批量向分支机构传输数据以致实时性不足的情况。kafka提高了实时性,减轻了数据中心的压力,提高了效率。为了帮助大家让学习变得轻松、高效,给大家免费分享一大批资料,帮助大家在成为大数据工程师,乃至架构师的路上披荆斩棘。在这里给大家推荐一个大数据学习交流圈:658558542 欢迎大家进群交流讨论,学习交流,共同进步。
当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。
但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有有效资源还是很有必要的。
消费者是以consumer group消费者组的方式工作,由一个或者多个消费者组成一个组,共同消费一个topic。每个分区在同一时间只能由group中的一个消费者读取,但是多个group可以同时消费这个partition。在图中,有一个由三个消费者组成的group,有一个消费者读取主题中的两个分区,另外两个分别读取一个分区。某个消费者读取某个分区,也可以叫做某个消费者是某个分区的拥有者。
在这种情况下,消费者可以通过水平扩展的方式同时读取大量的消息。另外,如果一个消费者失败了,那么其他的group成员会自动负载均衡读取之前失败的消费者读取的分区。
消费方式
consumer采用pull(拉)模式从broker中读取数据。
push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。
对于Kafka而言,pull模式更合适,它可简化broker的设计,consumer可自主控制消费消息的速率,同时consumer可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直等待数据到达。为了避免这种情况,我们在我们的拉请求中有参数,允许消费者请求在等待数据到达的“长轮询”中进行阻塞(并且可选地等待到给定的字节数,以确保大的传输大小)。
消费者组的偏移量等信息存储在zookeeper中的consumers节点中。
61 Kafka Producer 压力测试
record-size 是一条信息有多大,单位是字节。
num-records 是总共发送多少条信息。
throughput 是每秒多少条信息,设成-1,表示不限流,可测出生产者最大吞吐量。
consumer 采用 pull(拉)模式从 broker 中读取数据。
push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。
pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数
据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有
数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。
分区中的所有副本统称为 AR(Assigned Replicas)。所有与 leader 副本保持一定程度同步的副本(包括 leader 副本在内)组成ISR(In-Sync Replicas),ISR 集合是 AR 集合中的一个子集。
可以通过分区策略体现消息顺序性。分区策略有轮询策略、随机策略、按消息键保序策略。
处理顺序 :拦截器->序列化器->分区器
消息在通过 send() 方法发往 broker 的过程中,有可能需要经过拦截器(Interceptor)、序列化器(Serializer)和分区器(Partitioner)的一系列作用之后才能被真正地发往 broker。拦截器一般不是必需的,而序列化器是必需的。消息经过序列化之后就需要确定它发往的分区,如果消息 ProducerRecord 中指定了 partition 字段,那么就不需要分区器的作用,因为 partition 代表的就是所要发往的分区号。
整个生产者客户端由两个线程协调运行,这两个线程分别为主线程和 Sender 线程(发送线程)。
一般来说如果消费者过多,出现了消费者的个数大于分区个数的情况,就会有消费者分配不到任何分区。开发者可以继承AbstractPartitionAssignor实现自定义消费策略,从而实现同一消费组内的任意消费者都可以消费订阅主题的所有分区。
当前消费者需要提交的消费位移是offset+1
在旧消费者客户端中,消费位移是存储在 ZooKeeper 中的。而在新消费者客户端中,消费位移存储在 Kafka 内部的主题__consumer_offsets 中。
Kafka 中的消息是以主题为基本单位进行归类的,各个主题在逻辑上相互独立。每个主题又可以分为一个或多个分区。不考虑多副本的情况,一个分区对应一个日志(Log)。为了防止 Log 过大,Kafka 又引入了日志分段(LogSegment)的概念,将 Log 切分为多个 LogSegment,相当于一个巨型文件被平均分配为多个相对较小的文件。
Log 和 LogSegment 也不是纯粹物理意义上的概念,Log 在物理上只以文件夹的形式存储,而每个 LogSegment 对应于磁盘上的一个日志文件和两个索引文件,以及可能的其他文件。
每个日志分段文件对应了两个索引文件,主要用来提高查找消息的效率。
日志删除(Log Retention):按照一定的保留策略直接删除不符合条件的日志分段。
日志压缩(Log Compaction):针对每个消息的 key 进行整合,对于有相同 key 的不同 value 值,只保留最后一个版本。
在 Kafka 集群中会有一个或多个 broker,其中有一个 broker 会被选举为控制器(Kafka Controller),它负责管理整个集群中所有分区和副本的状态。当某个分区的 leader 副本出现故障时,由控制器负责为该分区选举新的 leader 副本。当检测到某个分区的 ISR 集合发生变化时,由控制器负责通知所有broker更新其元数据信息。当使用 kafka-topicssh 脚本为某个 topic 增加分区数量时,同样还是由控制器负责分区的重新分配。
Kafka 中有多种延时 *** 作,比如延时生产,还有延时拉取(DelayedFetch)、延时数据删除(DelayedDeleteRecords)等。
延时 *** 作创建之后会被加入延时 *** 作管理器(DelayedOperationPurgatory)来做专门的处理。延时 *** 作有可能会超时,每个延时 *** 作管理器都会配备一个定时器(SystemTimer)来做超时管理,定时器的底层就是采用时间轮(TimingWheel)实现的。
为了实现生产者的幂等性,Kafka 为此引入了 producer id(以下简称 PID)和序列号(sequence number)这两个概念。
Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在
初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。
Kafka中的事务可以使应用程序将消费消息、生产消息、提交消费位移当作原子 *** 作来处理,同时成功或失败,即使该生产或消费会跨多个分区。
生产者必须提供唯一的transactionalId,启动后请求事务协调器获取一个PID,transactionalId与PID一一对应。
每次发送数据给<Topic, Partition>前,需要先向事务协调器发送AddPartitionsToTxnRequest,事务协调器会将该<Transaction, Topic, Partition>存于__transaction_state内,并将其状态置为BEGIN。
在处理完 AddOffsetsToTxnRequest 之后,生产者还会发送 TxnOffsetCommitRequest 请求给 GroupCoordinator,从而将本次事务中包含的消费位移信息 offsets 存储到主题 __consumer_offsets 中
一旦上述数据写入 *** 作完成,应用程序必须调用KafkaProducer的commitTransaction方法或者abortTransaction方法以结束当前事务。
在发送延时消息的时候并不是先投递到要发送的真实主题(real_topic)中,而是先投递到一些 Kafka 内部的主题(delay_topic)中,这些内部主题对用户不可见,然后通过一个自定义的服务拉取这些内部主题中的消息,并将满足条件的消息再投递到要发送的真实的主题中,消费者所订阅的还是真实的主题。
Kafka 集群中有一个 broker 会被选举为 Controller,负责管理集群 broker 的上下线,所
有 topic 的分区副本分配和 leader 选举等工作。Controller 的管理工作都是依赖于 Zookeeper 的。
Kafka是一个由Scala和Java编写的企业级的消息发布和订阅系统,最早是由Linkedin公司开发,最终开源到Apache软件基金会的项目。Kafka是一个分布式的,支持分区的,多副本的和多订阅者的高吞吐量的消息系统,被广泛应用在应用解耦、异步处理、限流削峰和消息驱动等场景。本文将针对Kafka的架构和相关组件进行简单的介绍。在介绍Kafka的架构之前,我们先了解一下Kafk的核心概念。
在详细介绍Kafka的架构和基本组件之前,需要先了解一下Kafka的一些核心概念。
Producer: 消息的生产者,负责往Kafka集群中发送消息;
Consumer: 消息的消费者,主动从Kafka集群中拉取消息。
Consumer Group: 每个Consumer属于一个特定的Consumer Group,新建Consumer的时候需要指定对应的Consumer Group ID。
Broker: Kafka集群中的服务实例,也称之为节点,每个Kafka集群包含一个或者多个Broker(一个Broker就是一个服务器或节点)。
Message: 通过Kafka集群进行传递的对象实体,存储需要传送的信息。
Topic: 消息的类别,主要用于对消息进行逻辑上的区分,每条发送到Kafka集群的消息都需要有一个指定的Topic,消费者根据Topic对指定的消息进行消费。
Partition: 消息的分区,Partition是一个物理上的概念,相当于一个文件夹,Kafka会为每个topic的每个分区创建一个文件夹,一个Topic的消息会存储在一个或者多个Partition中。
Segment: 一个partition当中存在多个segment文件段(分段存储),每个Segment分为两部分,log文件和 index 文件,其中 index 文件是索引文件,主要用于快速查询log 文件当中数据的偏移量位置;
log文件: 存放Message的数据文件,在Kafka中把数据文件就叫做日志文件。一个分区下面默认有n多个log文件(分段存储)。一个log文件大默认1G,消息会不断追加在log文件中,当log文件的大小超过1G的时候,会自动新建一个新的log文件。
index文件: 存放log文件的索引数据,每个index文件有一个对应同名的log文件。
后面我们会对上面的一些核心概念进行更深入的介绍。在介绍完Kafka的核心概念之后,我们来看一下Kafka的对外提供的基本功能,组件及架构设计。
如上图所示,Kafka主要包含四个主要的API组件:
1 Producer API
应用程序通过Producer API向Kafka集群发送一个或多个Topic的消息。
2 Consumer API
应用程序通过Consumer API,向Kafka集群订阅一个或多个Topic的消息,并处理这些Topic下接收到的消息。
3 Streams API
应用程序通过使用Streams API充当流处理器(Stream Processor),从一个或者多个Topic获取输入流,并生产一个输出流到一个或者多个Topic,能够有效地将输入流进行转变后变成输出流输出到Kafka集群。
4 Connect API
允许应用程序通过Connect API构建和运行可重用的生产者或者消费者,能够把kafka主题连接到现有的应用程序或数据系统。Connect实际上就做了两件事情:使用Source Connector从数据源(如:DB)中读取数据写入到Topic中,然后再通过Sink Connector读取Topic中的数据输出到另一端(如:DB),以实现消息数据在外部存储和Kafka集群之间的传输。
接下来我们将从Kafka的架构出发,重点介绍Kafka的主要组件及实现原理。Kafka支持消息持久化,消费端是通过主动拉取消息进行消息消费的,订阅状态和订阅关系由客户端负责维护,消息消费完后不会立刻删除,会保留历史消息,一般默认保留7天,因此可以通过在支持多订阅者时,消息无需复制多分,只需要存储一份就可以。下面将详细介绍每个组件的实现原理。
1 Producer
Producer是Kafka中的消息生产者,主要用于生产带有特定Topic的消息,生产者生产的消息通过Topic进行归类,保存在Kafka 集群的Broker上,具体的是保存在指定的partition 的目录下,以Segment的方式(log文件和index文件)进行存储。
2 Consumer
Consumer是Kafka中的消费者,主要用于消费指定Topic的消息,Consumer是通过主动拉取的方式从Kafka集群中消费消息,消费者一定属于某一个特定的消费组。
3 Topic
Kafka中的消息是根据Topic进行分类的,Topic是支持多订阅的,一个Topic可以有多个不同的订阅消息的消费者。Kafka集群Topic的数量没有限制,同一个Topic的数据会被划分在同一个目录下,一个Topic可以包含1至多个分区,所有分区的消息加在一起就是一个Topic的所有消息。
4 Partition
在Kafka中,为了提升消息的消费速度,可以为每个Topic分配多个Partition,这也是就之前我们说到的,Kafka是支持多分区的。默认情况下,一个Topic的消息只存放在一个分区中。Topic的所有分区的消息合并起来,就是一个Topic下的所有消息。每个分区都有一个从0开始的编号,每个分区内的数据都是有序的,但是不同分区直接的数据是不能保证有序的,因为不同的分区需要不同的Consumer去消费,每个Partition只能分配一个Consumer,但是一个Consumer可以同时一个Topic的多个Partition。
5 Consumer Group
Kafka中的每一个Consumer都归属于一个特定的Consumer Group,如果不指定,那么所有的Consumer都属于同一个默认的Consumer Group。Consumer Group由一个或多个Consumer组成,同一个Consumer Group中的Consumer对同一条消息只消费一次。每个Consumer Group都有一个唯一的ID,即Group ID,也称之为Group Name。Consumer Group内的所有Consumer协调在一起订阅一个Topic的所有Partition,且每个Partition只能由一个Consuemr Group中的一个Consumer进行消费,但是可以由不同的Consumer Group中的一个Consumer进行消费。如下图所示:
在层级关系上来说Consumer Group好比是跟Topic对应的,而Consumer就对应于Topic下的Partition。Consumer Group中的Consumer数量和Topic下的Partition数量共同决定了消息消费的并发量,且Partition数量决定了最终并发量,因为一个Partition只能由一个Consumer进行消费。当一个Consumer Group中Consumer数量超过订阅的Topic下的Partition数量时,Kafka会为每个Partition分配一个Consumer,多出来的Consumer会处于空闲状态。当Consumer Group中Consumer数量少于当前定于的Topic中的Partition数量是,单个Consumer将承担多个Partition的消费工作。如上图所示,Consumer Group B中的每个Consumer需要消费两个Partition中的数据,而Consumer Group C中会多出来一个空闲的Consumer4。总结下来就是:同一个Topic下的Partition数量越多,同一时间可以有越多的Consumer进行消费,消费的速度就会越快,吞吐量就越高。同时,Consumer Group中的Consumer数量需要控制为小于等于Partition数量,且最好是整数倍:如1,2,4等。
6 Segment
考虑到消息消费的性能,Kafka中的消息在每个Partition中是以分段的形式进行存储的,即每1G消息新建一个Segment,每个Segment包含两个文件:log文件和index文件。之前我们已经说过,log文件就是Kafka实际存储Producer生产的消息,而index文件采用稀疏索引的方式存储log文件中对应消息的逻辑编号和物理偏移地址(offset),以便于加快数据的查询速度。log文件和index文件是一一对应,成对出现的。下图展示了log文件和index文件在Partition中的存在方式。
Kafka里面每一条消息都有自己的逻辑offset(相对偏移量)以及存在物理磁盘上面实际的物理地址便宜量Position,也就是说在Kafka中一条消息有两个位置:offset(相对偏移量)和position(磁盘物理偏移地址)。在kafka的设计中,将消息的offset作为了Segment文件名的一部分。Segment文件命名规则为:Partition全局的第一个Segment从0开始,后续每个segment文件名为上一个Partition的最大offset(Message的offset,非实际物理地偏移地址,实际物理地址需映射到log中,后面会详细介绍在log文件中查询消息的原理)。数值最大为64位long大小,由20位数字表示,前置用0填充。
上图展示了index文件和log文件直接的映射关系,通过上图,我们可以简单介绍一下Kafka在Segment中查找Message的过程:
1 根据需要消费的下一个消息的offset,这里假设是7,使用二分查找在Partition中查找到文件名小于(一定要小于,因为文件名编号等于当前offset的文件里存的都是大于当前offset的消息)当前offset的最大编号的index文件,这里自然是查找到了00000000000000000000index。
2 在index文件中,使用二分查找,找到offset小于或者等于指定offset(这里假设是7)的最大的offset,这里查到的是6,然后获取到index文件中offset为6指向的Position(物理偏移地址)为258。
3 在log文件中,从磁盘位置258开始顺序扫描,直到找到offset为7的Message。
至此,我们就简单介绍完了Segment的基本组件index文件和log文件的存储和查询原理。但是我们会发现一个问题:index文件中的offset并不是按顺序连续存储的,为什么Kafka要将索引文件设计成这种不连续的样子?这种不连续的索引设计方式称之为稀疏索引,Kafka中采用了稀疏索引的方式读取索引,kafka每当log中写入了4k大小的数据,就往index里以追加的写入一条索引记录。使用稀疏索引主要有以下原因:
(1) 索引稀疏存储,可以大幅降低index文件占用存储空间大小。
(2) 稀疏索引文件较小,可以全部读取到内存中,可以避免读取索引的时候进行频繁的IO磁盘 *** 作,以便通过索引快速地定位到log文件中的Message。
7 Message
Message是实际发送和订阅的信息是实际载体,Producer发送到Kafka集群中的每条消息,都被Kafka包装成了一个Message对象,之后再存储在磁盘中,而不是直接存储的。Message在磁盘中的物理结构如下所示。
其中 key 和 value 存储的是实际的Message内容,长度不固定,而其他都是对Message内容的统计和描述,长度固定。因此在查找实际Message过程中,磁盘指针会根据Message的 offset 和 message length 计算移动位数,以加速Message的查找过程。之所以可以这样加速,因为Kafka的log文件都是顺序写的,往磁盘上写数据时,就是追加数据,没有随机写的 *** 作。
8Partition Replicas
最后我们简单聊一下Kafka中的Partition Replicas(分区副本)机制,08版本以前的Kafka是没有副本机制的。创建Topic时,可以为Topic指定分区,也可以指定副本个数。kafka 中的分区副本如下图所示:
Kafka通过副本因子(replication-factor)控制消息副本保存在几个Broker(服务器)上,一般情况下副本数等于Broker的个数,且同一个副本因子不能放在同一个Broker中。副本因子是以分区为单位且区分角色;主副本称之为Leader(任何时刻只有一个),从副本称之为 Follower(可以有多个),处于同步状态的副本叫做in-sync-replicas(ISR)。Leader负责读写数据,Follower不负责对外提供数据读写,只从Leader同步数据,消费者和生产者都是从leader读写数据,不与follower交互,因此Kafka并不是读写分离的。同时使用Leader进行读写的好处是,降低了数据同步带来的数据读取延迟,因为Follower只能从Leader同步完数据之后才能对外提供读取服务。
如果一个分区有三个副本因子,就算其中一个挂掉,那么只会剩下的两个中,选择一个leader,如下图所示。但不会在其他的broker中,另启动一个副本(因为在另一台启动的话,必然存在数据拷贝和传输,会长时间占用网络IO,Kafka是一个高吞吐量的消息系统,这个情况不允许发生)。如果指定分区的所有副本都挂了,Consumer如果发送数据到指定分区的话,将写入不成功。Consumer发送到指定Partition的消息,会首先写入到Leader Partition中,写完后还需要把消息写入到ISR列表里面的其它分区副本中,写完之后这个消息才能提交offset。
到这里,差不多把Kafka的架构和基本原理简单介绍完了。Kafka为了实现高吞吐量和容错,还引入了很多优秀的设计思路,如零拷贝,高并发网络设计,顺序存储,以后有时间再说。
对于每个主题,Kafka群集都会维护一个分区日志,如下所示:
每个分区(Partition)都是有序的(所以每一个Partition内部都是有序的),不变的记录序列,这些记录连续地附加到结构化的提交日志中。分区中的每个记录均分配有一个称为偏移的顺序ID号,该ID 唯一地标识分区中的每个记录。
每个消费者保留的唯一元数据是该消费者在日志中的偏移量或位置。此偏移量由使用者控制:通常,使用者在读取记录时会线性地推进其偏移量,但实际上,由于位置是由使用者控制的,因此它可以按喜欢的任何顺序使用记录。例如,使用者可以重置到较旧的偏移量以重新处理过去的数据,或者跳到最近的记录并从“现在”开始使用。(类似于游标指针的方式顺序处理数据,并且该指标可以任意移动)
分区的设计结构
生产者分区策略是 决定生产者将消息发送到哪个分区的算法, 主要有以下几种:
kafka消息的有序性,是采用消息键保序策略来实现的。 一个topic,一个partition(分割),一个consumer,内部单线程消费,写N个内存queue,然后N个线程分别消费一个内存queue。
kafka发送进行消息压缩有两个地方,分别是生产端压缩和Broker端压缩。
生产者端压缩 生产者压缩通常采用的GZIP算法这样 Producer 启动后生产的每个消息集合都是经 GZIP 压缩过的,故而能很好地节省网络传输带宽以及 Kafka Broker 端的磁盘占用。 配置参数:
Broker压缩 大部分情况下 Broker 从 Producer 端接收到消息后仅仅是原封不动地保存而不会对其进行任何修改,但以下情况会引发Broker压缩
消费者端解压 Kafka 会将启用了哪种压缩算法封装进消息集合中,在Consummer中进行解压 *** 作。
kafka提供以下特性来保证其消息的不丢失,从而保证消息的可靠性
生产者确认机制 当 Kafka 的若干个 Broker(根据配置策略,可以是一个,也可以是ALL) 成功地接收到一条消息并写入到日志文件后,它们会告诉生产者程序这条消息已成功提交。此时,这条消息在 Kafka 看来就正式变为“已提交”消息了。 设置 acks = all。acks 是 Producer 的一个参数,代表了你对“已提交”消息的定义。如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。这是最高等级的“已提交”定义。
生产者失败回调机制 生产者不要使用 producersend(msg),而要使用 producersend(msg, callback)。记住,一定要使用带有回调通知的 send 方法。producersend(msg, callback) 采用异步的方式,当发生失败时会调用callback方法。
失败重试机制 设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。
消费者确认机制 确保消息消费完成再提交。Consumer 端有个参数 enableautocommit,最好把它设置成 false,并采用手动提交位移的方式。就像前面说的,这对于单 Consumer 多线程处理的场景而言是至关重要的。
副本机制 设置 replicationfactor >= 3。这也是 Broker 端的参数。其实这里想表述的是,最好将消息多保存几份,毕竟目前防止消息丢失的主要机制就是冗余。 设置 mininsyncreplicas > 1。这依然是 Broker 端参数,控制的是消息至少要被写入到多少个副本才算是“已提交”。设置成大于 1 可以提升消息持久性。在实际环境中千万不要使用默认值 1。 确保 replicationfactor > mininsyncreplicas。如果两者相等,那么只要有一个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要在不降低可用性的基础上完成。推荐设置成 replicationfactor = mininsyncreplicas + 1。
限定Broker选取Leader机制 设置 uncleanleaderelectionenable = false。这是 Broker 端的参数,它控制的是哪些 Broker 有资格竞选分区的 Leader。如果一个 Broker 落后原先的 Leader 太多,那么它一旦成为新的 Leader,必然会造成消息的丢失。故一般都要将该参数设置成 false,即不允许这种情况的发生。
由于kafka生产者确认机制、失败重试机制的存在,kafka的消息不会丢失但是存在由于网络延迟等原因造成重复发送的可能性。 所以我们要考虑消息幂等性的设计。 kafka提供了幂等性Producer的方式来保证消息幂等性。使用 的方式开启幂等性。
幂等性 Producer 的作用范围:
Kafka事务 事务型 Producer 能够保证将消息原子性地写入到多个分区中。这批消息要么全部写入成功,要么全部失败。另外,事务型 Producer 也不惧进程的重启。Producer 重启回来后,Kafka 依然保证它们发送消息的精确一次处理。 同样使用 的方式开启事务。
consumer group是kafka提供的可扩展且具有容错性的消费者机制。它是由一个或者多个消费者组成,它们共享同一个Group ID 组内的所有消费者协调在一起来消费订阅主题(subscribed topics)的所有分区(partition)。当然,每个分区只能由同一个消费组内的一个consumer来消费。
consummer group有以下的特性:
消费者位置 消费者位置,即位移。 消费者在消费的过程中需要记录自己消费了多少数据。 位移提交有自动、手动两种方式进行位移提交。
Kafka通过一个内置Topic(__consumer_offsets)来管理消费者位移。
rebalance本质上是一种协议,规定了一个consumer group下的所有consumer如何达成一致来分配订阅topic的每个分区。
Kafka提供了一个角色:coordinator来执行对于consumer group的管理。 Group Coordinator是一个服务,每个Broker在启动的时候都会启动一个该服务。Group Coordinator的作用是用来存储Group的相关Meta信息,并将对应Partition的Offset信息记录到Kafka内置Topic(__consumer_offsets)中。
Rebalance 过程分为两步:Join 和 Sync。 Join 顾名思义就是加入组。这一步中,所有成员都向coordinator发送JoinGroup请求,请求加入消费组。一旦所有成员都发送了JoinGroup请求,coordinator会从中选择一个consumer担任leader的角色,并把组成员信息以及订阅信息发给leader——注意leader和coordinator不是一个概念。leader负责消费分配方案的制定。
Sync,这一步leader开始分配消费方案,即哪个consumer负责消费哪些topic的哪些partition。一旦完成分配,leader会将这个方案封装进SyncGroup请求中发给coordinator,非leader也会发SyncGroup请求,只是内容为空。coordinator接收到分配方案之后会把方案塞进SyncGroup的response中发给各个consumer。这样组内的所有成员就都知道自己应该消费哪些分区了。
producer 是生产者,负责消息生产,上游程序中按照标准的消息格式组装(按照每个消息事件的字段定义)发送到指定的topic。producer生产消息的时候,不会因为consumer处理能力不够,而阻塞producer的生产。consumer会从指定的topic 拉取消息,然后处理消费,并提交offset(消息处理偏移量,消费掉的消息并不会主动删除,而是kafka系统根据保存周期自动消除)。
topic是消费分类存储的队列,可以按照消息类型来分topic存储。
replication是topic复制副本个数,用于解决数据丢失,防止leader topic宕机后,其他副本可以快代替。
broker是缓存代理,Kafka集群中的一台或多台服务器统称broker,用来保存producer发送的消息。Broker没有副本机制,一旦broker宕机,该broker的消息将都不可用。
partition是topic的物理分组,在创建topic的时候,可以指定partition 数量。每个partition是逻辑有序的,保证每个消息都是顺序插入的,而且每个消息的offset在不同partition的是唯一不同的
偏移量。kafka为每条在分区的消息保存一个偏移量offset,这也是消费者在分区的位置。比如一个偏移量是5的消费者,表示已经消费了从0-4偏移量的消息,下一个要消费的消息的偏移量是5。每次消息处理完后,要么主动提交offset,要么自动提交,把offset偏移到下一位,如处理offset=6消息。在kafka配置中,如果enable_auto_commit=True和auto_commit_interval_ms=xx,那表示每xx 毫秒自动提交偏移量
分组。是指在消费同一topic的不同consumer。每个consumer都有唯一的groupId,同一groupId 属于同一个group。不同groupId的consumer相互不影响。对于一个topic,同一个group的consumer数量不能超过 partition数量。比如,Topic A 有 16个partition,某一个group下有2个consumer,那2个consumer分别消费8个partition,而这个group的consumer数量最多不能超过16个。
kafka的配置主要分四类,分别是zookeeper、server、consumer、producer。其他的配置可以忽略。
zk的配置比较简单,也可以默认不改dataDir是zk存储节点配置的目录地址,clientPort是zk启动的端口,默认2181,maxClientCnxns是限制ip的连接此处,设置0表示无连接次数,一般情况根据业务部署情况,配置合理的值。
在 上文 中我们介绍了 Kafka 的网络通信,本文打算详细分析 Kafka 的核心 — 队列 的设计和实现,来对 Kafka 进行更深一步的了解。
队列是一种先进先出(FIFO)的数据结构,它是 Kafka 中最重要的部分,负责收集生产者生产的消息,并将这些消息传递给消费者。要实现一个队列有多种方式,Kafka 作为一个消息队列中间件,在设计队列时主要要考虑两个问题:
乍一看到这个问题,我们会想,内存的读取速度远快于磁盘,如果追求性能,内存也充足的话,当然是将生产者产生的消息数据写到内存(比如用一个数组或者链表来存储队列数据),供消费者消费。真的是这样吗?
下面我们依次分析下写内存和写磁盘文件的优缺点,首先,内存的优点是读写速度非常快,但是,如果我们的目标是设计「大数据量」下的「高吞吐量」的消息队列,会有以下几个问题:
接下来我们来分析一下磁盘,写磁盘文件方式存储队列数据的优点就是能规避上述内存的缺点,但其有很严重的缺点,就是读写速度慢,如果纯依靠磁盘,那消息队列肯定做不到「高吞吐量」这个目标。
分析了内存跟磁盘的优缺点,好像我们还是只能选写内存,但我们忽视了磁盘的两个情况:一是磁盘慢是慢在随机读写,如果是顺序读写,他的速度能达到 600MB/sec(RAID-5 磁盘阵列),并不慢,如果我们尽可能地将数据的读写设计成顺序的,可以大大提升性能。二是 现代的 *** 作系统会(尽可能地)将磁盘里的文件进行缓存 。
有了 *** 作系统级别的文件缓存,那用磁盘存储队列数据的方式就变得有优势了。首先,磁盘文件的数据会有文件缓存,所以不必担心随机读写的性能;其次,同样是使用内存,磁盘文件使用的是 *** 作系统级别的内存,相比于在 Java 内存堆中存储队列,它没有 GC 问题,也没有 Java 对象的额外内存开销,更可以规避应用重启后的内存 load 数据耗时的问题,而且,文件缓存是 *** 作系统提供的,因为我们只要简单的写磁盘文件,系统复杂性大大降低。
因此,Kafka 直接使用磁盘来存储消息队列的数据。
刚才我们已经决定用磁盘文件来存储队列数据,那么要如何选择数据结构呢?一般情况下,如果需要查找数据并随机访问,我们会用 B+ 树来存储数据,但其时间复杂度是 O(log N),由于我们设计的是消息队列,我们可以完全顺序的写收到的生产者消息,消费者消费时,只要记录下消费者当前消费的位置,往后消费就可以了,这样可以对文件尽可能的进行顺序读写,同时,时间复杂度是O(1)。其实,这跟我们写日志的方式很像,每条日志顺序 append 到日志文件。
之前我们已经确定采用直接顺序写磁盘文件的方式来存储队列数据,下面我们来剖析下具体的实现细节。
在 Kafka 中,用一个文件夹存储一条消息队列,成为一个 Log,每条消息队列由多个文件组成,每个文件称为一个 LogSegment,每当一个 LogSegment 的大小到达阈值,系统就会重新生成一个 LogSegment;当旧的 LogSegment 过期需要清理时(虽然磁盘空间相对于内存会宽裕很多,我们可以保存更长时间的消息数据,比如一周,以供消费者更灵活的使用,但还是需要定期清理太老的数据),系统会根据清理策略删除这些文件。
现在我们知道一个队列(Log)是由多个队列段文件(LogSegment)组成的,那么 Kafka 是如何将这些文件逻辑上连接从而组成一条有序队列的呢?在生成每个队列段文件时,Kafka 用该段的初始位移来对其命名,如在新建一个队列时,会初始化第一个队列段文件,那么其文件名就是0,假设每个段的大小是固定值 L,那么第二个段文件名就是 L,第 N 个就是 (N - 1) L。这样,我们就可以根据文件名对段文件进行排序,排序后的顺序就是整个队列的逻辑顺序。
了解了队列的基本实现,下面我们就来分析下队列的核心 *** 作—读和写。
写 *** 作发生在生产者向队列生产消息时,在上篇文章讲网络通信时我们已经说到,所有的客户端请求会根据协议转到一个 Handler 来具体处理,负责写 *** 作的 Handler 叫 ProducerHandler,整个写请求的流程如下:
之前我们说过,如果是顺序写,由于省掉了磁头寻址的时间,磁盘的性能还是很高的,我们看到 Kakfa 队列是以顺序方式写的,所以性能很高。但是,如果一台 Kafka 服务器有很多个队列,而硬盘的磁头是有限的,所以还是得在不同的队列直接来回切换寻址,性能会有所下降。
队列的读 *** 作发送在消费者消费队列数据时,由于队列是线性的,只需要记录消费者上次消费到了哪里(offset),接下去消费就好了。那么首先会有一个问题,由谁来记消费者到底消费到哪里了?
一般情况下,我们会想到让服务端来记录各个消费者当前的消费位置,当消费者来拉数据,根据记录的消费位置和队列的当前位置,要么返回新的待消费数据,要么返回空。让服务端记录消费位置,当遇到网络异常时会有一些问题,比如服务端将消息发给消费者后,如果网络异常消费者没有收到消息,那么这条消息就被「跳过」了,当然我们可以借鉴二阶段提交的思想,服务端将消息发送给消费者后,标记状态为「已发送」,等消费者消费成功后,返回一个 ack 给服务端,服务端再将其标记为「成功消费」。不过这样设计还是会有一个问题,如果消费者没有返回 ack 给服务端,此时这条消息可能在已经被消费也可能还没被消费,服务端无从得知,只能根据人为策略跳过(可能会漏消息)或者重发(可能存在重复数据)。另一个问题是,如果有很多消费者,服务端需要记录每条消息的每个消费者的消费状态,这在大数据的场景下,非常消耗性能和内存。
Kafka 将每个消费者的消费状态记录在消费者本身(隔一段时间将最新消费状态同步到 zookeeper),每次消费者要拉数据,就给服务端传递一个 offset,告诉服务端从队列的哪个位置开始给我数据,以及一个参数 length,告诉服务端最多给我多大的数据(批量顺序读数据,更高性能),这样就能使服务端的设计复杂度大大降低。当然这解决不了一致性的问题,不过消费者可以根据自己程序特点,更灵活地处理事务。
下面就来分析整个读的流程:
分布式系统中不可避免的会遇到一致性问题,主要是两块:生产者与队列服务端之间的一致性问题、消费者与队列服务端之间的一致性问题,下面依次展开。
当生产者向服务端投递消息时,可能会由于网络或者其他问题失败,如果要保证一致性,需要生产者在失败后重试,不过重试又会导致消息重复的问题,一个解决方案是每个消息给一个唯一的 id,通过服务端的主动去重来避免重复消息的问题,不过这一机制目前 Kafka 还未实现。目前 Kafka 提供配置,供用户不同场景下选择允许漏消息(失败后不重试)还是允许重复消息(失败后重试)。
由于在消费者里我们可以自己控制消费位置,就可以更灵活的进行个性化设计。如果我们在拉取到消息后,先增加 offset,然后再进行消息的后续处理,如果在消息还未处理完消费者就挂掉,就会存在消息遗漏的问题;如果我们在拉取到消息后,先进行消息处理,处理成功后再增加 offset,那么如果消息处理一半消费者挂掉,会存在重复消息的问题。要做到完全一致,最好的办法是将 offset 的存储与消费者放一起,每消费一条数据就将 offset+1。
本文介绍了 Kafka 的队列实现以及其读写过程。Kafka 认为 *** 作系统级别的文件缓存比 Java 的堆内存更省空间和高效,如果生产者消费者之间比较「和谐」的话,大部分的读写 *** 作都会落在文件缓存,且在顺序读写的情况下,硬盘的速度并不慢,因此选择直接写磁盘文件的方式存储队列。在队列的读写过程中,Kafka 尽可能地使用顺序读写,并使用零拷贝来优化性能。最后,Kafka 让消费者自己控制消费位置,提供了更加灵活的数据消费方式。
搭建Hadoop集群环境一般建议三个节点以上,一个作为Hadoop的NameNode节点。另外两个作为DataNode节点。在本次实验中,采用了三台CentOS 75作为实验环境。
将所需要的java 文件解压到合适的目录,并将整个java 目录添加进 /etc/profile ,并 source /etc/profile
需要说明的是ssh免密登录的配置不是双向的,是单向的。也就是说,每个节点都需要和另外两个节点进行ssh的免密配置。
此时会在用户目录的 ssh 下,生成秘钥文件。现在需要将此验证文件拷贝至slave1节点,
在 /etc/profile 目录下追加:
vim /home/postgres/hadoop/hadoop-330/etc/hadoop/hadoop-envsh 修改配置文件java路径
vim /home/postgres/hadoop/hadoop-330/etc/hadoop/core-sitexml 修改core-site文件
vim /home/postgres/hadoop/hadoop-330/etc/hadoop/hdfs-sitexml 修改hdfs-site文件
vim /home/postgres/hadoop/hadoop-330/etc/hadoop/yarn-sitexml 修改yarn-site文件
vim /home/postgres/hadoop/hadoop-330/etc/hadoop/mapred-sitexml 修改mapred-site文件
vim /home/postgres/hadoop/hadoop-330/etc/hadoop/slaves 修改slaves文件
手动创建文件夹:/home/postgres/hadoop/hdfs/ logs 和 data 目录,并分配777权限。
在hadoop初始化启动后,在master上xxx/name/namesecondary/下会自动创建/current/VERSION文件路径。
在master运行: hadoop namenode -format
如果有必要,运行DataNode命令: hadoop datanode -format
master+slave1+slave2启动集群: start-allsh
master : jps
slave1 : jps
slave2 : jps
hadoop dfsadmin -report
按照上述的配置情况:一个namenode节点,两个datanode节点,整个集群监控情况如下:
输入 >
这10款工具如下:
AKHQ
Kowl
Kafdrop
UI for Apache Kafka
Lenses
CMAK
Confluent CC
Conduktor
LogiKM
kafka-console-ui
如果上面这个地址可以打开,可以直接去看介绍,下文也不再重复说明。
关于前8款的对比,可以看下面这张,也是于上面,我直接copy过来了(可能有好多同学打不开上面这个链接,就直接看这张了解了下吧)
关于这8款工具的介绍,人家说的很清晰了,这里就不再重复说明了,并且这些工具,大部分我也没用过,也没资格评价太多。
考虑到很多同学可能打开github太慢,我下面会把相关基本信息整理一下,供大家快速了解,方便选型。
概览
AKHQ (previously known as KafkaHQ)
开发语言:后端是java为主
Kowl - A Web UI for Apache Kafka
ps github上完整的动图这里上传失败,就只放一个静态的截图了,如果可以打开github,建议打开下面的地址直接看吧。
但是这个并不是所有功能都是免费,有部分功能是商业版才有:
开发语言:后端是go为主
Kafdrop – Kafka Web UI
开发语言:后端以java为主
要求jdk11或更高版本
UI for Apache Kafka – Free Web UI for Apache Kafka
开发语言:后端以java为主
要求jdk13或更高版本
Lensesio
Apache Kafka 和 Kubernetes 的实时应用程序和数据 *** 作 #DataOps 门户。
CMAK (Cluster Manager for Apache Kafka, previously known as Kafka Manager)
这个想必很多同学都知道,原来的名字就是kafka manager。
开发语言:后端以scala为主
Confluent Inc
Apache
Conduktor
一个商业版本的桌面客户端
官网找到一个这样的,凑合看吧:
LogiKM
滴滴开源的一站式Apache Kafka集群指标监控与运维管控平台。
也是分社区版和商业版的。
这个建议直接看github说明吧,都是中文,内容清晰,相关的资料也都有。
我也简单的了解了下,有个逻辑集群的概念,对于规模比较大的kafka集群管理还是挺好的,不过,这里比较高端的特性都是不开源的,必须商业版才能用。
开发语言:后端以java为主
kafka-console-ui(kafka可视化管理平台)
一款轻量级的kafka可视化管理平台,安装配置快捷、简单易用。界面风格有点类似rocketmq-console。
这款权当是“王婆卖瓜,自卖自夸”吧,一个小工具,如果刚接触kafka的同学或者是中小型集群,想找个简单易用的,可以考虑一下。
开发语言:后端以java和scala为主
参考链接: >
以上就是关于大数据Kafka有哪些优势能力呢全部的内容,包括:大数据Kafka有哪些优势能力呢、Kafka总结、Kafka架构及基本原理简析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)