一、kafka基本介绍
1、kafka特性
kafka是一个分布式流式处理平台,kafka 有如以下特性:
1、通过o(1)的磁盘数据结构提供消息的持久化,这种结构对于海量的消息存储也能有保持长时间的稳定性能(高性能)
2、高吞吐量,即使非常普通的兴建kafka也可以支持每秒钟每秒数百万的消息(高并发)
3、支持通过kafka服务器和消费集群来分区信息(高可靠)
4、支持hadoop并行数据加载
5、支持各种语言丰富的客户端
2、kafka基本概念
kafka包括若干个producr、broker、consumer以及一个zookeeper集群。其中kafka中的消息以topic(主题)进行归类,生产者负责将消息发送到特定的主题,消费者负责订阅主题消费
broker:kafka集群包括一个或多个服务器,这种服务器称为broker;
topic:每条发布到kafka集群的消息都有一个类别,这个类别被称为topic,实际开发中通常称为队列,topic名称,即叫做kafka队名称(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处);
partition:每个topic包含一个或者多个partition
segment:partition物理上由多个segment组成
producer:负责发布消息到kafka broker
consumer:消息消费者,向kafka broker读取消息的客户端
consumer group:每个consumer属于一个特定的consumer group可为每个consumer指定group name。若不指定group name则属于默认的group。
3、kafka在服务端的架构
1、一个topic下包含一个或多个partition,2-3个副本,这个在创建topic的时候可以指定
2、多个partition可以提高读写的吞吐量,多个副本可以提高kafka可靠性
3、数据不需要保序的时候创建多个partition最好,局部保序的情况下,同一个partition消费保序即可;在合局保序的情况下,一个topic对应一个partition,例如数据库实时同步场景
4、每一个Partition理论上是一个无长的队列,包含多个文件,文件以时间戳+最小的offset命名,不能修改,只能以只能是以append的方式写入,会通过NIO内存映射文件的方持久化到硬盘上,文件的大小固定,大小达到阈值以后,关闭旧文件 ,重新新建一个内存映射文件;在flush硬盘的时候是顺序IO,因此写入Partition的速度会非常非常快,过期的文件根据保存时间,后台异步线程定期清除,释放磁盘空间;
kafka
kafka架构图
为什么有消息系统?
- 解藕:允许你独立的扩展或修改两边的处理过程,只需要确保他们遵守同样的接口约束
- 冗余:消息队列把数据进行持久化直到他们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的插入-获取-删除 范式中,再把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕
- 扩展性:因为消息队列解藕了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可
- 灵活&峰值处理能力:在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
- 可恢复性:系统的一部分组件失效的时候,不会影响整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
- 顺序保证:在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性)
- 缓冲:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
- 异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
producer:消费生产者,发布消息到kafka集群的终端或服务
broker:kafka集群中的包含的服务器,一个broker表示kafka集群中的一个节点
topic:每条发布到Kafka集群的消息属于的类别,即Kafka是面向 topic 的。 更通俗的说Topic就像一个消息队列,生产者可以向其写入消息,消费者可以从中读取消息,一个Topic支持多个生产者或消费者同时订阅它,所以其扩展性很好。
partition:每个 topic 包含一个或多个partition。Kafka分配的单位是partition
replica:partition 的副本,保障 partition 的高可用。
consumer:从Kafka集群中消费消息的终端或服务
consumer group:每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 Consumer 消费,但可以被多个 consumer group 消费。
leader:每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的partition。 producer 和 consumer 只跟 leader 交互
follower:Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。
controller: 知道大家有没有思考过一个问题,就是Kafka集群中某个broker宕机之后,是谁负责感知到他的宕机,以及负责进行Leader Partition的选举?如果你在Kafka集群里新加入了一些机器,此时谁来负责把集群里的数据进行负载均衡的迁移?包括你的Kafka集群的各种元数据,比如说每台机器上有哪些partition,谁是leader,谁是follower,是谁来管理的?如果你要删除一个topic,那么背后的各种partition如何删除,是谁来控制?还有就是比如Kafka集群扩容加入一个新的broker,是谁负责监听这个broker的加入?如果某个broker崩溃了,是谁负责监听这个broker崩溃?这里就需要一个Kafka集群的总控组件,Controller。他负责管理整个Kafka集群范围内的各种东西。
zookeeper:(1) Kafka 通过 zookeeper 来存储集群的meta元数据信息 (2)一旦controller所在broker宕机了,此时临时节点消失,集群里其他broker会一直监听这个临时节点,发现临时节点消失了,就争抢再次创建临时节点,保证有一台新的broker会成为controller角色。
offset:消费者在对应分区上已经消费的消息数(位置),offset保存的地方跟kafka版本有一定的关系。 kafka0.8 版本之前offset保存在zookeeper上。 kafka0.8 版本之后offset保存在kafka集群上。
isr机制:光是依靠多副本机制能保证Kafka的高可用性,但是能保证数据不丢失吗?不行,因为如果leader宕机,但是leader的数据还没同步到follower上去,此时即使选举了follower作为新的leader,当时刚才的数据已经丢失了。
ISR是:in-sync replica,就是跟leader partition保持同步的follower partition的数量,只有处于ISR列表中的follower才可以在leader宕机之后被选举为新的leader,因为在这个ISR列表里代表他的数据跟leader是同步的。
2. kafka的文件存储机制
2.1 概述
同一个topic下有多个不同的partition,每个partition为一个目录,partition命名的规则是topic的名称加上一个序号,序号从0开始。 每一个partition目录下的文件被平均切割成大小相等(默认一个文件是1G,可以手动去设置)的数据文件,每一个数据文件都被称为一个段(segment file),但每个段消息数量不一定相等,这种特性能够使得老的segment可以被快速清除。默认保留7天的数据。 每次满1G后,在写入到一个新的文件中。
另外每个partition只需要支持顺序读写就可以。如上图所示: 首先00000000000000000000.log是最早产生的文件,该文件达到1G后又产生了新的00000000000002025849.log文件,新的数据会写入到这个新的文件里面。 这个文件到达1G后,数据又会写入到下一个文件中。也就是说它只会往文件的末尾追加数据,这就是顺序写的过程,生产者只会对每一个partition做数据的追加(写操作)。
2.2 数据消费问题讨论
问题:如何保证消息消费的有序性呢?比如说生产者生产了0到100个商品,那么消费者在消费的时候按照0到100这个从小到大的顺序消费?
*** 那么kafka如何保证这种有序性呢?*** 难度就在于,生产者生产出0到100这100条数据之后,通过一定的分组策略存储到broker的partition中的时候, 比如0到10这10条消息被存到了这个partition中,10到20这10条消息被存到了那个partition中,这样的话,消息在分组存到partition中的时候就已经被分组策略搞得无序了。
那么能否做到消费者在消费消息的时候全局有序呢? 遇到这个问题,我们可以回答,在大多数情况下是做不到全局有序的。但在某些情况下是可以做到的。比如我的partition只有一个,这种情况下是可以全局有序的。
那么可能有人又要问了,只有一个partition的话,哪里来的分布式呢?哪里来的负载均衡呢? 所以说,全局有序是一个伪命题!全局有序根本没有办法在kafka要实现的大数据的场景来做到。但是我们只能保证当前这个partition内部消息消费的有序性。
结论:一个partition中的数据是有序的吗?回答:间隔有序,不连续。
针对一个topic里面的数据,只能做到partition内部有序,不能做到全局有序。特别是加入消费者的场景后,如何保证消费者的消费的消息的全局有序性, 这是一个伪命题,只有在一种情况下才能保证消费的消息的全局有序性,那就是只有一个partition。
2.3 Segment文件
-
Segment file是什么
生产者生产的消息按照一定的分区策略被发送到topic中partition中,partition在磁盘上就是一个目录,该目录名是topic的名称加上一个序号,在这个partition目录下,有两类文件,一类是以log为后缀的文件,一类是以index为后缀的文件,每一个log文件和一个index文件相对应,这一对文件就是一个segment file,也就是一个段。 其中的log文件就是数据文件,里面存放的就是消息,而index文件是索引文件,索引文件记录了元数据信息。log文件达到1个G后滚动重新生成新的log文件
-
Segment文件特点
segment文件命名的规则:partition全局的第一个segment从0(20个0)开始,后续的每一个segment文件名是上一个segment文件中最后一条消息的offset值。
那么这样命令有什么好处呢? 假如我们有一个消费者已经消费到了368776(offset值为368776),那么现在我们要继续消费的话,怎么做呢?
看下图,分2个步骤; 第1步是从所有文件log文件的的文件名中找到对应的log文件,第368776条数据位于上图中的“00000000000000368769.log”这个文件中, 这一步涉及到一个常用的算法叫做“二分查找法”(假如我现在给你一个offset值让你去找,你首先是将所有的log的文件名进行排序,然后通过二分查找法进行查找, 很快就能定位到某一个文件,紧接着拿着这个offset值到其索引文件中找这条数据究竟存在哪里); 第2步是到index文件中去找第368776条数据所在的位置。
索引文件(index文件)中存储这大量的元数据,而数据文件(log文件)中存储这大量的消息。
索引文件(index文件)中的元数据指向对应的数据文件(log文件)中消息的物理偏移地址。
2.4 kafka如何快速查询数据
上图的左半部分是索引文件,里面存储的是一对一对的key-value,其中key是消息在数据文件(对应的log文件)中的编号,比如“1,3,6,8……”,
分别表示在log文件中的第1条消息、第3条消息、第6条消息、第8条消息……,那么为什么在index文件中这些编号不是连续的呢?
这是因为index文件中并没有为数据文件中的每条消息都建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。
这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。
但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。
其中以索引文件中元数据8,1686为例,其中8代表在右边log数据文件中从上到下第8个消息(在全局partiton表示第368777个消息),其中1686表示该消息的物理偏移地址(位置)为1686。
要是读取offset=368777的消息,从00000000000000368769.log文件中的1325的位置进行读取,那么怎么知道何时读完本条消息,否则就读到下一条消息的内容了?
参数说明:
| 关键字 | 解释说明 |
|---|---|
| 8 byte offset | 在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message |
| 4 byte message size | message大小 |
| 4 byte CRC32 | 用crc32校验message |
| 1 byte “magic" | 表示本次发布Kafka服务程序协议版本号 |
| 1 byte “attributes" | 表示为独立版本、或标识压缩类型、或编码类型。 |
| 4 byte key length | 表示key的长度,当key为-1时,K byte key字段不填 |
| K byte key | 可选 |
| value bytes payload | 表示实际消息数据。 |
这个就需要涉及到消息的物理结构了,消息都具有固定的物理结构,包括:offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段,可以确定一条消息的大小,即读取到哪里截止。
2.5 kafka高效文件存储设计特点
- Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
- 通过索引信息可以快速定位message
- 通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。
- 通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。 blog.csdn.net/qq_31329893…
3. 为什么Kafka速度那么快
Kafka是大数据领域无处不在的消息中间件,目前广泛使用在企业内部的实时数据管道,并帮助企业构建自己的流计算应用程序。
Kafka虽然是基于磁盘做的数据存储,但却具有高性能、高吞吐、低延时的特点,其吞吐量动辄几万、几十上百万,这其中的原由值得我们一探究竟。
3.1 顺序读写
- 磁盘顺序读写性能要高于内存的随机读写
众所周知Kafka是将消息记录持久化到本地磁盘中的,一般人会认为磁盘读写性能差,可能会对Kafka性能如何保证提出质疑。实际上不管是内存还是磁盘,快或慢关键在于寻址的方式,磁盘分为顺序读写与随机读写,内存也一样分为顺序读写与随机读写。基于磁盘的随机读写确实很慢,但磁盘的顺序读写性能却很高,一般而言要高出磁盘随机读写三个数量级,一些情况下磁盘顺序读写性能甚至要高于内存随机读写。
磁盘的顺序读写是磁盘使用模式中最有规律的,并且操作系统也对这种模式做了大量优化,Kafka就是使用了磁盘顺序读写来提升的性能。Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升
3.2 Page Cache
为了优化读写性能,Kafka利用了操作系统本身的Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。这样做的好处有:
(1)避免Object消耗:如果是使用Java堆,Java对象的内存消耗比较大,通常是所存储数据的两倍甚至更多。
(2)避免GC问题:随着JVM中数据不断增多,垃圾回收将会变得复杂与缓慢,使用系统缓存就不会存在GC问题。
3.3 零拷贝(sendfile)
-
零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。通常是说在IO读写过程中。
Kafka利用linux操作系统的 "零拷贝(zero-copy)" 机制在消费端做的优化。-
首先来了解下数据从文件发送到socket网络连接中的常规传输路径
比如:读取文件,再用socket发送出去 传统方式实现: 先读取、再发送,实际经过1~4四次copy。 buffer = File.read Socket.send(buffer)- 第一步:操作系统从磁盘读取数据到内核空间(kernel space)的Page Cache缓冲区
- 第二步:应用程序读取内核缓冲区的数据copy到用户空间(user space)的缓冲区
- 第三步:应用程序将用户空间缓冲区的数据copy回内核空间到socket缓冲区
- 第四步:操作系统将数据从socket缓冲区copy到网卡,由网卡进行网络传输
-
传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的。实际IO读写,需要进行IO中断,需要CPU响应中断(带来上下文切换),尽管后来引入DMA来接管CPU的中断请求,但四次copy是存在“不必要的拷贝”的。
重新思考传统IO方式,会注意到实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。
显然,第二次和第三次数据copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的意义。
这种场景:是指读取磁盘文件后,不需要做其他处理,直接用网络发送出去。试想,如果读取磁盘的数据需要用程序进一步处理的话,必须要经过第二次和第三次数据copy,让应用程序在内存缓冲区处理。
此时我们会发现用户态“空空如也”。数据没有来到用户态,而是直接在核心态就进行了传输,但这样依然还是有多次复制。首先数据被读取到read buffer中,然后发到socket buffer,最后才发到网卡。虽然减少了用户态和核心态的切换,但依然存在多次数据复制。
如果可以进一步减少数据复制的次数,甚至没有数据复制是不是就会做到最快呢?
-
DMA
- DMA,全称叫Direct Memory Access,一种可让某些硬件子系统去直接访问系统主内存,而不用依赖CPU的计算机系统的功能。听着是不是很厉害,跳过CPU,直接访问主内存。传统的内存访问都需要通过CPU的调度来完成。如下图:
- DMA,则可以绕过CPU,硬件自己去直接访问系统主内存。如下图
- 回到本文中的文件传输,有了DMA后,就可以实现绝对的零拷贝了,因为网卡是直接去访问系统主内存的。如下图:
- 总结 Kafka采用顺序读写、Page Cache、零拷贝以及分区分段等这些设计,再加上在索引方面做的优化,另外Kafka数据读写也是批量的而不是单条的,使得Kafka具有了高性能、高吞吐、低延时的特点。这样Kafka提供大容量的磁盘存储也变成了一种优点
Java的NIO提供了FileChannle,它的transferTo、transferFrom方法就是Zero Copy。
1. kafka内核原理
1.1 ISR机制
光是依靠多副本机制能保证Kafka的高可用性,但是能保证数据不丢失吗?
不行,因为如果leader宕机,但是leader的数据还没同步到follower上去,此时即使选举了follower作为新的leader,当时刚才的数据已经丢失了。
ISR是:in-sync replica,就是跟leader partition保持同步的follower partition的数量,只有处于ISR列表中的follower才可以在leader宕机之后被选举为新的leader,因为在这个ISR列表里代表他的数据跟leader是同步的。
如果要保证写入kafka的数据不丢失,首先需要保证ISR中至少有一个follower,其次就是在一条数据写入了leader partition之后,要求必须复制给ISR中所有的follower partition,才能说代表这条数据已提交,绝对不会丢失,这是Kafka给出的承诺
1.2 HW&LEO原理
-
LEO
last end offset,日志末端偏移量,标识当前日志文件中下一条待写入的消息的offset。举一个例子,若LEO=10,那么表示在该副本日志上已经保存了10条消息,位移范围是[0,9]。 -
HW
Highwatermark,俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个offset之前的消息。任何一个副本对象的HW值一定不大于其LEO值。 小于或等于HW值的所有消息被认为是“已提交的”或“已备份的”。HW它的作用主要是用来判断副本的备份进度. 下图表示一个日志文件,这个日志文件中只有9条消息,第一条消息的offset(LogStartOffset)为0,最有一条消息的offset为8,offset为9的消息使用虚线表示的,代表下一条待写入的消息。日志文件的 HW 为6,表示消费者只能拉取offset在 0 到 5 之间的消息,offset为6的消息对消费者而言是不可见的。
leader持有的HW即为分区的HW,同时leader所在broker还保存了所有follower副本的leo
(1)关系:leader的leo >= follower的leo >= leader保存的follower的leo >= leader的hw >= follower的hw (2)原理:上面关系反应出各个值的更新逻辑的先后
* ==**更新LEO的机制**==
* 注意
* follower副本的LEO保存在2个地方
~~~
(1)follower副本所在的broker缓存里。
(2)leader所在broker的缓存里,也就是leader所在broker的缓存上保存了该分区所有副本的LEO。
~~~
* 更新LEO的时机
* follower更新LEO
~~~
(1)follower的leo更新时间
每当follower副本写入一条消息时,leo值会被更新
(2)leader端的follower副本的leo更新时间
当follower从leader处fetch消息时,leader获取follower的fetch请求中offset参数,更新保存在leader端follower的leo。
~~~
* leader更新LEO
~~~
(1)leader本身的leo的更新时间:leader向log写消息时
~~~
-
==更新HW的机制==
-
follower更新HW
follower更新HW发生在其更新完LEO后,即follower向log写完数据,它就会尝试更新HW值。具体算法就是比较当前LEO(已更新)与fetch响应中leader的HW值,取两者的小者作为新的HW值。 -
leader更新HW
- leader更新HW的时机
(1)producer 向 leader 写消息时 (2)leader 处理 follower 的 fetch 请求时 (3)某副本成为leader时 (4)broker 崩溃导致副本被踢出ISR时- leader更新HW的方式
当尝试确定分区HW时,它会选出所有满足条件的副本,比较它们的LEO(当然也包括leader自己的LEO),并选择最小的LEO值作为HW值。
这里的满足条件主要是指副本要满足以下两个条件之一:
(1)处于ISR中
(2)副本LEO落后于leader LEO的时长不大于replica.lag.time.max.ms参数值(默认值是10秒)
-
2.1 producer核心流程概览
- 1、ProducerInterceptors是一个拦截器,对发送的数据进行拦截
ps:说实话这个功能其实没啥用,我们即使真的要过滤,拦截一些消息,也不考虑使用它,我们直接发送数据之前自己用代码过滤即可
-
2、Serializer 对消息的key和value进行序列化
-
3、通过使用分区器作用在每一条消息上,实现数据分发进行入到topic不同的分区中
-
4、RecordAccumulator收集消息,实现批量发送
它是一个缓冲区,可以缓存一批数据,把topic的每一个分区数据存在一个队列中,然后封装消息成一个一个的batch批次,最后实现数据分批次批量发送。 -
5、Sender线程从RecordAccumulator获取消息
-
6、构建ClientRequest对象
-
7、将ClientRequest交给 NetWorkClient准备发送
-
8、NetWorkClient 将请求放入到KafkaChannel的缓存
-
9、发送请求到kafka集群
-
10、调用回调函数,接受到响应
3.1 常见异常处理
-
不管是异步还是同步,都可能让你处理异常,常见的异常如下:
1)LeaderNotAvailableException:这个就是如果某台机器挂了,此时leader副本不可用,会导致你写入失败,要等待其他follower副本切换为leader副本之后,才能继续写入,此时可以重试发送即可。如果说你平时重启kafka的broker进程,肯定会导致leader切换,一定会导致你写入报错,是LeaderNotAvailableException 2)NotControllerException:这个也是同理,如果说Controller所在Broker挂了,那么此时会有问题,需要等待Controller重新选举,此时也是一样就是重试即可 3)NetworkException:网络异常,重试即可 我们之前配置了一个参数,retries,他会自动重试的,但是如果重试几次之后还是不行,就会提供Exception给我们来处理了。 -
retries
- 重新发送数据的次数
-
retry.backoff.ms
- 两次重试之间的时间间隔
3.2 提升消息吞吐量
-
buffer.memory
- 设置发送消息的缓冲区,默认值是33554432,就是32MB
如果发送消息出去的速度小于写入消息进去的速度,就会导致缓冲区写满,此时生产消息就会阻塞住,所以说这里就应该多做一些压测,尽可能保证说这块缓冲区不会被写满导致生产行为被阻塞住 -
compression.type
- producer用于压缩数据的压缩类型。默认是none表示无压缩。可以指定gzip、snappy
- 压缩最好用于批量处理,批量处理消息越多,压缩性能越好。
-
batch.size
- producer将试图批处理消息记录,以减少请求次数。这将改善client与server之间的性能。
- 默认是16384Bytes,即16kB,也就是一个batch满了16kB就发送出去
如果batch太小,会导致频繁网络请求,吞吐量下降;如果batch太大,会导致一条消息需要等待很久才能被发送出去,而且会让内存缓冲区有很大压力,过多数据缓冲在内存里。 -
linger.ms
- 这个值默认是0,就是消息必须立即被发送
一般设置一个100毫秒之类的,这样的话就是说,这个消息被发送出去后进入一个batch,如果100毫秒内,这个batch满了16kB,自然就会发送出去。 但是如果100毫秒内,batch没满,那么也必须把消息发送出去了,不能让消息的发送延迟时间太长,也避免给内存造成过大的一个压力。
3.3 请求超时
- ==max.request.size==
- 这个参数用来控制发送出去的消息的大小,默认是1048576字节,也就1mb
- 这个一般太小了,很多消息可能都会超过1mb的大小,所以需要自己优化调整,把他设置更大一些(企业一般设置成10M)
- ==request.timeout.ms==
- 这个就是说发送一个请求出去之后,他有一个超时的时间限制,默认是30秒
- 如果30秒都收不到响应,那么就会认为异常,会抛出一个TimeoutException来让我们进行处理
3.4 ACK参数
acks参数,其实是控制发送出去的消息的持久化机制的。
-
==acks=0==
- 生产者只管发数据,不管消息是否写入成功到broker中,数据丢失的风险最高
producer根本不管写入broker的消息到底成功没有,发送一条消息出去,立马就可以发送下一条消息,这是吞吐量最高的方式,但是可能消息都丢失了。 你也不知道的,但是说实话,你如果真是那种实时数据流分析的业务和场景,就是仅仅分析一些数据报表,丢几条数据影响不大的。会让你的发送吞吐量会提升很多,你发送弄一个batch出去,不需要等待人家leader写成功,直接就可以发送下一个batch了,吞吐量很大的,哪怕是偶尔丢一点点数据,实时报表,折线图,饼图。 -
==acks=1==
- 只要leader写入成功,就认为消息成功了.
默认给这个其实就比较合适的,还是可能会导致数据丢失的,如果刚写入leader,leader就挂了,此时数据必然丢了,其他的follower没收到数据副本,变成leader. -
==acks=all,或者 acks=-1==
- 这个leader写入成功以后,必须等待其他ISR中的副本都写入成功,才可以返回响应说这条消息写入成功了,此时你会收到一个回调通知.
这种方式数据最安全,但是性能最差。 -
==如果要想保证数据不丢失,得如下设置==
(1)min.insync.replicas = 2 ISR里必须有2个副本,一个leader和一个follower,最最起码的一个,不能只有一个leader存活,连一个follower都没有了。 (2)acks = -1 每次写成功一定是leader和follower都成功才可以算做成功,这样leader挂了,follower上是一定有这条数据,不会丢失。 (3)retries = Integer.MAX_VALUE 无限重试,如果上述两个条件不满足,写入一直失败,就会无限次重试,保证说数据必须成功的发送给两个副本,如果做不到,就不停的重试。 除非是面向金融级的场景,面向企业大客户,或者是广告计费,跟钱的计算相关的场景下,才会通过严格配置保证数据绝对不丢失
3.5 重试乱序
- max.in.flight.requests.per.connection
- 每个网络连接可以忍受 producer端发送给broker 消息然后消息没有响应的个数
消息重试是可能导致消息的乱序的,因为可能排在你后面的消息都发送出去了,你现在收到回调失败了才在重试,此时消息就会乱序,所以可以使用“max.in.flight.requests.per.connection”参数设置为1,这样可以保证producer同一时间只能发送一条消息
4. broker核心参数
-
server.properties配置文件核心参数
【broker.id】 每个broker都必须自己设置的一个唯一id 【log.dirs】 这个极为重要,kafka的所有数据就是写入这个目录下的磁盘文件中的,如果说机器上有多块物理硬盘,那么可以把多个目录挂载到不同的物理硬盘上,然后这里可以设置多个目录,这样kafka可以数据分散到多块物理硬盘,多个硬盘的磁头可以并行写,这样可以提升吞吐量。 【zookeeper.connect】 连接kafka底层的zookeeper集群的 【Listeners】 broker监听客户端发起请求的端口号,默认是9092 【unclean.leader.election.enable】 默认是false,意思就是只能选举ISR列表里的follower成为新的leader,1.0版本后才设为false,之前都是true,允许非ISR列表的follower选举为新的leader 【delete.topic.enable】 默认true,允许删除topic 【log.retention.hours】 可以设置一下,要保留数据多少个小时(默认168小时),这个就是底层的磁盘文件,默认保留7天的数据,根据自己的需求来就行了
5. consumer消费原理
5.1 Offset管理
每个consumer内存里数据结构保存对每个topic的每个分区的消费offset,定期会提交offset,老版本是写入zk,但是那样高并发请求zk是不合理的架构设计,zk是做分布式系统的协调的,轻量级的元数据存储,不能负责高并发读写,作为数据存储。所以后来就是提交offset发送给内部topic:__consumer_offsets,提交过去的时候,key是group.id+topic+分区号,value就是当前offset的值,每隔一段时间,kafka内部会对这个topic进行compact。也就是每个group.id+topic+分区号就保留最新的那条数据即可。而且因为这个 __consumer_offsets可能会接收高并发的请求,所以默认分区50个,这样如果你的kafka部署了一个大的集群,比如有50台机器,就可以用50台机器来抗offset提交的请求压力,就好很多。
5.2 Coordinator
-
Coordinator的作用
每个consumer group都会选择一个broker作为自己的coordinator,他是负责监控这个消费组里的各个消费者的心跳,以及判断是否宕机,然后开启rebalance. 根据内部的一个选择机制,会挑选一个对应的Broker,Kafka总会把你的各个消费组均匀分配给各个Broker作为coordinator来进行管理的. consumer group中的每个consumer刚刚启动就会跟选举出来的这个consumer group对应的coordinator所在的broker进行通信,然后由coordinator分配分区给你的这个consumer来进行消费。coordinator会尽可能均匀的分配分区给各个consumer来消费。 -
如何选择哪台是coordinator
首先对消费组的groupId进行hash,接着对consumer_offsets的分区数量取模,默认是50,可以通过offsets.topic.num.partitions来设置,找到你的这个consumer group的offset要提交到consumer_offsets的哪个分区。 比如说:groupId,"membership-consumer-group" -> hash值(数字)-> 对50取模 -> 就知道这个consumer group下的所有的消费者提交offset的时候是往哪个分区去提交offset,找到consumer_offsets的一个分区,consumer_offset的分区的副本数量默认来说1,只有一个leader,然后对这个分区找到对应的leader所在的broker,这个broker就是这个consumer group的coordinator了,consumer接着就会维护一个Socket连接跟这个Broker进行通信。
6. consumer消费者Rebalance策略
比如我们消费的一个topic主题有12个分区:p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11
假设我们的消费者组里面有三个消费者。
6.1 range策略
range策略就是按照partiton的序号范围
p0~3 consumer1
p4~7 consumer2
p8~11 consumer3
默认就是这个策略
6.2 round-robin策略
consumer1: 0,3,6,9
consumer2: 1,4,7,10
consumer3: 2,5,8,11
但是前面的这两个方案有个问题:
假设consuemr1挂了:p0-5分配给consumer2,p6-11分配给consumer3
这样的话,原本在consumer2上的的p6,p7分区就被分配到了 consumer3上
6.3 sticky策略
最新的一个sticky策略,就是说尽可能保证在rebalance的时候,让原本属于这个consumer
的分区还是属于他们,然后把多余的分区再均匀分配过去,这样尽可能维持原来的分区分配的策略
consumer1: 0-3
consumer2: 4-7
consumer3: 8-11
假设consumer3挂了
consumer1:0-3,+8,9
consumer2: 4-7,+10,11
7. consumer核心参数
【heartbeat.interval.ms】
默认值:3000
consumer心跳时间,必须得保持心跳才能知道consumer是否故障了,然后如果故障之后,就会通过心跳下发rebalance的指令给其他的consumer通知他们进行rebalance的操作
【session.timeout.ms】
默认值:10000
kafka多长时间感知不到一个consumer就认为他故障了,默认是10秒
【max.poll.interval.ms】
默认值:300000
如果在两次poll操作之间,超过了这个时间,那么就会认为这个consume处理能力太弱了,会被踢出消费组,分区分配给别人去消费,一遍来说结合你自己的业务处理的性能来设置就可以了
【fetch.max.bytes】
默认值:1048576
获取一条消息最大的字节数,一般建议设置大一些
【max.poll.records】
默认值:500条
一次poll返回消息的最大条数,
【connections.max.idle.ms】
默认值:540000
consumer跟broker的socket连接如果空闲超过了一定的时间,此时就会自动回收连接,但是下次消费就要重新建立socket连接,这个建议设置为-1,不要去回收
【auto.offset.reset】
earliest
当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
latest
当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从当前位置开始消费
none
topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
注:我们生产里面一般设置的是latest
【enable.auto.commit】
默认值:true
设置为自动提交offset
【auto.commit.interval.ms】
默认值:60 * 1000
每隔多久更新一下偏移量
数据传输的事物定义有哪三种?
数据传输的事务定义通常有以下三种级别:
(1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输
(2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输.
(3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而
且仅仅被传输一次,这是大家所期望的
kafka消息是采用pull模式还是push模式
Kafka 最初考虑的问题是,customer 应该从 brokes 拉取消息还是 brokers 将消息推送到
consumer,也就是 pull 还 push。在这方面,Kafka 遵循了一种大部分消息系统共同的传统
的设计:producer 将消息推送到 broker,consumer 从 broker 拉取消息
一些消息系统比如 Scribe 和 Apache Flume 采用了 push 模式,将消息推送到下游的
consumer。这样做有好处也有坏处:由 broker 决定消息推送的速率,对于不同消费速率的
consumer 就不太好处理了。消息系统都致力于让 consumer 以最大的速率最快速的消费消
息,但不幸的是,push 模式下,当 broker 推送的速率远大于 consumer 消费的速率时,
consumer 恐怕就要崩溃了。最终 Kafka 还是选取了传统的 pull 模式
Pull 模式的另外一个好处是 consumer 可以自主决定是否批量的从 broker 拉取数据。Push
模式必须在不知道下游 consumer 消费能力和消费策略的情况下决定是立即推送每条消息还
是缓存之后批量推送。如果为了避免 consumer 崩溃而采用较低的推送速率,将可能导致一
次只推送较少的消息而造成浪费。Pull 模式下,consumer 就可以根据自己的消费能力去决
定这些策略
Pull 有个缺点是,如果 broker 没有可供消费的消息,将导致 consumer 不断在循环中轮询,
直到新消息到 t 达。为了避免这点,Kafka 有个参数可以让 consumer 阻塞知道新消息到达
(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发
kafka的ack机制
request.required.acks 有三个值 0 1 -1
0:生产者不会等待 broker 的 ack,这个延迟最低但是存储的保证最弱当 server 挂掉的时候
就会丢数据
1:服务端会等待 ack 值 leader 副本确认接收到消息后发送 ack 但是如果 leader 挂掉后他
不确保是否复制完成新 leader 也会导致数据丢失
-1:同样在 1 的基础上 服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出
的 ack,这样数据不会丢失
ack剖析:mp.weixin.qq.com/s?__biz=MzU…
kafka中consumer group 是什么概念
同样是逻辑上的概念,是Kafka实现单播和广播两种消息模型的手段。同一个topic的数据,会广播给不同的group;同一个group中的worker,只有一个worker能拿到这个数据。换句话说,对于同一个topic,每个group都可以拿到同样的所有数据,但是数据进入group后只能被其中的一个worker消费。group内的worker可以使用多线程或多进程来实现,也可以将进程分散在多台机器上,worker的数量通常不超过partition的数量,且二者最好保持整数倍关系,因为Kafka在设计时假定了一个partition只能被一个worker消费(同一group内)。
kafka中消息是否会消失和重复消费
要确定Kafka的消息是否丢失或重复,从两个方面分析入手:消息发送和消息消费。
1、消息发送
Kafka消息发送有两种方式:同步(sync)和异步(async),默认是同步方式,可通过producer.type属性进行配置。Kafka通过配置request.required.acks属性来确认消息的生产:
0---表示不进行消息接收是否成功的确认;
1---表示当Leader接收成功时确认;
-1---表示Leader和Follower都接收成功时确认;
综上所述,有6种消息生产的情况,下面分情况来分析消息丢失的场景:
(1)acks=0,不和Kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;
(2)acks=1、同步模式下,只有Leader确认接收成功后但挂掉了,副本没有同步,数据可能丢失;
2、消息消费
Kafka消息消费有两个consumer接口,Low-level API和High-level API:
Low-level API:消费者自己维护offset等值,可以实现对Kafka的完全控制;
High-level API:封装了对parition和offset的管理,使用简单;
如果使用高级接口High-level API,可能存在一个问题就是当消息消费者从集群中把消息取出来、并提交了新的消息offset值后,还没来得及消费就挂掉了,那么下次再消费时之前没消费成功的消息就“诡异”的消失了;
解决办法:
针对消息丢失:同步模式下,确认机制设置为-1,即让消息写入Leader和Follower之后再确认消息发送成功;异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态;
针对消息重复:将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可。
重复消费 www.javazhiyin.com/22910.html
kafka如何保证数据不丢失
该问题已经成为了Kafka面试的惯例,如同Java的HashMap,属于高频出现的面试问题。那么,我们该怎么理解这个问题呢?问题是Kafka如何保障数据不丢失,即Kafka的Broker提供了什么机制保证数据不丢失的。
其实对于Kafka的Broker而言,Kafka 的复制机制和分区的多副本架构是Kafka 可靠性保证的核心。把消息写入多个副本可以使Kafka 在发生崩溃时仍能保证消息的持久性。
搞清楚了问题的核心,再来看一下该怎么回答这个问题:主要包括三个方面
1.Topic 副本因子个数:replication.factor >= 3
2.同步副本列表(ISR):min.insync.replicas = 2
3.禁用unclean选举:unclean.leader.election.enable=false
下面将会逐步分析上面的三个配置:
副本因子
Kafka的topic是可以分区的,并且可以为分区配置多个副本,该配置可以通过replication.factor参数实现。Kafka中的分区副本包括两种类型:领导者副本(Leader Replica)和追随者副本(Follower Replica),每个分区在创建时都要选举一个副本作为领导者副本,其余的副本自动变为追随者副本。在 Kafka 中,追随者副本是不对外提供服务的,也就是说,任何一个追随者副本都不能响应消费者和生产者的读写请求。所有的请求都必须由领导者副本来处理。换句话说,所有的读写请求都必须发往领导者副本所在的 Broker,由该 Broker 负责处理。追随者副本不处理客户端请求,它唯一的任务就是从领导者副本异步拉取消息,并写入到自己的提交日志中,从而实现与领导者副本的同步。
一般来说,副本设为3可以满足大部分的使用场景,也有可能是5个副本(比如银行)。如果副本因子为N,那么在N-1个broker 失效的情况下,仍然能够从主题读取数据或向主题写入数据。所以,更高的副本因子会带来更高的可用性、可靠性和更少的故障。另一方面,副本因子N需要至少N个broker ,而且会有N个数据副本,也就是说它们会占用N倍的磁盘空间。实际生产环境中一般会在可用性和存储硬件之间作出权衡。
除此之外,副本的分布同样也会影响可用性。默认情况下,Kafka会确保分区的每个副本分布在不同的Broker上,但是如果这些Broker在同一个机架上,一旦机架的交换机发生故障,分区就会不可用。所以建议把Broker分布在不同的机架上,可以使用broker.rack参数配置Broker所在机架的名称。
同步副本列表
In-sync replica(ISR)称之为同步副本,ISR中的副本都是与Leader进行同步的副本,所以不在该列表的follower会被认为与Leader是不同步的。那么,ISR中存在是什么副本呢?首先可以明确的是:Leader副本总是存在于ISR中。而follower副本是否在ISR中,取决于该follower副本是否与Leader副本保持了“同步”。
Kafka的broker端有一个参数replica.lag.time.max.ms, 该参数表示follower副本滞后与Leader副本的最长时间间隔,默认是10秒。这就意味着,只要follower副本落后于leader副本的时间间隔不超过10秒,就可以认为该follower副本与leader副本是同步的,所以哪怕当前follower副本落后于Leader副本几条消息,只要在10秒之内赶上Leader副本,就不会被踢出出局。
可以看出ISR是一个动态的,所以即便是为分区配置了3个副本,还是会出现同步副本列表中只有一个副本的情况(其他副本由于不能够与leader及时保持同步,被移出ISR列表)。如果这个同步副本变为不可用,我们必须在可用性和一致性之间作出选择(CAP理论)。
根据Kafka 对可靠性保证的定义,消息只有在被写入到所有同步副本之后才被认为是已提交的。但如果这里的“所有副本”只包含一个同步副本,那么在这个副本变为不可用时,数据就会丢失。如果要确保已提交的数据被写入不止一个副本,就需要把最小同步副本数量设置为大一点的值。对于一个包含3 个副本的主题分区,如果min.insync.replicas=2 ,那么至少要存在两个同步副本才能向分区写入数据。
如果进行了上面的配置,此时必须要保证ISR中至少存在两个副本,如果ISR中的副本个数小于2,那么Broker就会停止接受生产者的请求。尝试发送数据的生产者会收到NotEnoughReplicasException异常,消费者仍然可以继续读取已有的数据。
禁用unclean选举
选择一个同步副本列表中的分区作为leader 分区的过程称为clean leader election。注意,这里要与在非同步副本中选一个分区作为leader分区的过程区分开,在非同步副本中选一个分区作为leader的过程称之为unclean leader election。由于ISR是动态调整的,所以会存在ISR列表为空的情况,通常来说,非同步副本落后 Leader 太多,因此,如果选择这些副本作为新 Leader,就可能出现数据的丢失。毕竟,这些副本中保存的消息远远落后于老 Leader 中的消息。在 Kafka 中,选举这种副本的过程可以通过Broker 端参数 unclean.leader.election.enable控制是否允许 Unclean 领导者选举。开启 Unclean 领导者选举可能会造成数据丢失,但好处是,它使得分区 Leader 副本一直存在,不至于停止对外提供服务,因此提升了高可用性。反之,禁止 Unclean Leader 选举的好处在于维护了数据的一致性,避免了消息丢失,但牺牲了高可用性。分布式系统的CAP理论说的就是这种情况。
不幸的是,unclean leader election的选举过程仍可能会造成数据的不一致,因为同步副本并不是完全同步的。由于复制是异步完成的,因此无法保证follower可以获取最新消息。比如Leader分区的最后一条消息的offset是100,此时副本的offset可能不是100,这受到两个参数的影响:
replica.lag.time.max.ms:同步副本滞后与leader副本的时间
zookeeper.session.timeout.ms:与zookeeper会话超时时间
简而言之,如果我们允许不同步的副本成为leader,那么就要承担丢失数据和出现数据不一致的风险。如果不允许它们成为leader,那么就要接受较低的可用性,因为我们必须等待原先的首领恢复到可用状态。
关于unclean选举,不同的场景有不同的配置方式。对数据质量和数据一致性要求较高的系统会禁用这种unclean的leader选举(比如银行)。如果在可用性要求较高的系统里,比如实时点击流分析系统, 一般不会禁用unclean的leader选举。
如何解决Kafka数据丢失问题?
你可能会问:这个问题跟Q1有什么区别呢?其实一般在面试问题中可以理解成一个问题。之所以在这里做出区分,是因为两者的解决方式不一样。Q1问题是从Kafka的Broker侧来看待数据丢失的问题,而Q2是从Kafka的生产者与消费者的角度来看待数据丢失的问题。
先来看一下如何回答这个问题:主要包括两个方面:
Producer
retries=Long.MAX_VALUE
设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。
acks=all
设置 acks = all。acks 是 Producer 的一个参数,代表了你对“已提交”消息的定义。如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。这是最高等级的“已提交”定义。
max.in.flight.requests.per.connections=1
该参数指定了生产者在收到服务器晌应之前可以发送多少个消息。它的值越高,就会占用越多的内存,不过也会提升吞吐量。把它设为1 可以保证消息是按照发送的顺序写入服务器的,即使发生了重试。
Producer要使用带有回调通知的API,也就是说不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。
其他错误处理
使用生产者内置的重试机制,可以在不造成消息丢失的情况下轻松地处理大部分错误,不过
仍然需要处理其他类型的错误,例如消息大小错误、序列化错误等等。
Consumer
禁用自动提交:enable.auto.commit=false
消费者处理完消息之后再提交offset
配置auto.offset.reset
这个参数指定了在没有偏移量可提交时(比如消费者第l次启动时)或者请求的偏移量在broker上不存在时(比如数据被删了),消费者会做些什么。
这个参数有两种配置。一种是earliest:消费者会从分区的开始位置读取数据,不管偏移量是否有效,这样会导致消费者读取大量的重复数据,但可以保证最少的数据丢失。一种是latest(默认),如果选择了这种配置, 消费者会从分区的末尾开始读取数据,这样可以减少重复处理消息,但很有可能会错过一些消息。
kafka可以保证数据永久不丢失么
上面分析了一些保障数据不丢失的措施,在一定程度上可以避免数据的丢失。但是请注意:Kafka 只对“已提交”的消息(committed message)做有限度的持久化保证。所以说,Kafka不能够完全保证数据不丢失,需要做出一些权衡。
首先,要理解什么是已提交的消息,当 Kafka 的若干个 Broker 成功地接收到一条消息并写入到日志文件后,它们会告诉生产者程序这条消息已成功提交。此时,这条消息在 Kafka 看来就正式变为已提交消息了。所以说无论是ack=all,还是ack=1,不论哪种情况,Kafka 只对已提交的消息做持久化保证这件事情是不变的。
其次,要理解有限度的持久化保证,也就是说 Kafka 不可能保证在任何情况下都做到不丢失消息。必须保证Kafka的Broker是可用的,换句话说,假如消息保存在 N 个 Kafka Broker 上,那么这个前提条件就是这 N 个 Broker 中至少有 1 个存活。只要这个条件成立,Kafka 就能保证你的这条消息永远不会丢失。
总结一下,Kafka 是能做到不丢失消息的,只不过这些消息必须是已提交的消息,而且还要满足一定的条件。
如何保证数据有序
首先需要明确的是:Kafka的主题是分区有序的,如果一个主题有多个分区,那么Kafka会按照key将其发送到对应的分区中,所以,对于给定的key,与其对应的record在分区内是有序的。
Kafka可以保证同一个分区里的消息是有序的,即生产者按照一定的顺序发送消息,Broker就会按照这个顺序将他们写入对应的分区中,同理,消费者也会按照这个顺序来消费他们。
在一些场景下,消息的顺序是非常重要的。比如,先存钱再取钱与先取钱再存钱是截然不同的两种结果。
上面的问题中提到一个参数max.in.flight.requests.per.connections=1,该参数的作用是在重试次数大于等于1时,保证数据写入的顺序。如果该参数不为1,那么当第一个批次写入失败时,第二个批次写入成功,Broker会重试写入第一个批次,如果此时第一个批次重试写入成功,那么这两个批次消息的顺序就反过来了。
一般来说,如果对消息的顺序有要求,那么在为了保障数据不丢失,需要先设置发送重试次数retries>0,同时需要把max.in.flight.requests.per.connections参数设为1,这样在生产者尝试发送第一批消息时,就不会有其他的消息发送给broker,虽然会影响吞吐量,但是可以保证消息的顺序。
除此之外,还可以使用单分区的Topic,但是会严重影响吞吐量
如何确定合适的Kafka主题的分区数量?
选择合适的分区数量可以达到高度并行读写和负载均衡的目的,在分区上达到均衡负载是实现吞吐量的关键。需要根据每个分区的生产者和消费者的期望吞吐量进行估计。
举个栗子:假设期望读取数据的速率(吞吐量)为1GB/Sec,而一个消费者的读取速率为50MB/Sec,此时至少需要20个分区以及20个消费者(一个消费者组)。同理,如果期望生产数据的速率为1GB/Sec,而每个生产者的生产速率为100MB/Sec,此时就需要有10个分区。在这种情况下,如果设置20个分区,既可以保障1GB/Sec的生产速率,也可以保障消费者的吞吐量。通常需要将分区的数量调整为消费者或者生产者的数量,只有这样才可以同时实现生产者和消费者的吞吐量。
一个简单的计算公式为:分区数 = max(生产者数量,消费者数量)
生产者数量=整体生产吞吐量/每个生产者对单个分区的最大生产吞吐量
消费者数量=整体消费吞吐量/每个消费者从单个分区消费的最大吞吐量
是什么消费者组
consumer group:是kafka提供的可扩展且具有容错性的消费者机制。既然是一个组,那么组内必然
可以有多个消费者或消费者实例(consumer instance),它们共享一个公共的ID,即group ID。
组内的所有消费者协调在一起来消费订阅主题(subscribed topics)的所有分区(partition)。
当然,每个分区只能由同一个消费组内的一个consumer来消费。
位移提交:老版本的位移是提交到zookeeper中的,图就不画了,总之目录结构是:/consumers/<group.id>/offsets/<topic>/<partitionId>,
但是zookeeper其实并不适合进行大批量的读写操作,尤其是写操作。因此kafka提供了另一种解决方案:增加 __consumeroffsets topic,
将offset信息写入这个topic,摆脱对zookeeper的依赖(指保存offset这件事情)。
__consumer_offsets中的消息保存了每个consumer group某一时刻提交的offset信息。
rebalance
本质上是一种协议,规定了一个consumer group下的所有consumer如何达成一致来分配订阅topic的每个分区
组成员发生变更(新consumer加入组、已有consumer主动离开组或已有consumer崩溃了——这两者的区别后面会谈到)
rebalance的触发条件
- 组成员发生变更(新consumer加入组、已有consumer主动离开组或已有consumer崩溃了——这两者的区别后面会谈到)
- 订阅主题数发生变更——这当然是可能的,如果你使用了正则表达式的方式进行订阅,那么新建匹配正则表达式的topic就会触发rebalance
- 订阅主题的分区数发生变更 blog.csdn.net/lijingjingc…
consumer的分区策略
consumer的两种分区策略:Range 和 RoundRobin(轮询) 我们假设有一个名为Top1的主题,它有十个分区,然后我们有两个消费者(C1、C2)来消费这十个分区里面的数据,而且C1的num.streams=1,C2的num.streams=2。
下面介绍第一个默认的分区策略:Range Startegy(根据范围消费) Range startegy是对每个主题而言的 , 首先对同一个主题里面的分区按照序号进行排序,并对消费者按照字母进行排序。在对十个分区排序的话是0-9;消费者线程排完序是C1-0,C2-0,C2-1。然后用partitions的总数除以消费者的总数来决定每个消费者线程消费几个分区。如果有余数,那么前面的几个消费者线程将会多消费一个分区。在我们的例子里面,我们有十个分区,三个消费者线程,10/3=3---1,那么消费者线程C1-0 将会多消费一个分区,所以最后分区分配的结构看起来是这样的:
C1-0将消费0,1,2,3分区
C2-0将消费4,5,6分区
C2-1将消费7,8,9分区
如果有第十一个分区的话,那么分区是这样的
C1-0将消费0,1,2,3分区
C2-0将消费4,5,6,7分区
C2-1将消费8,9,10分区
如果我们有2个主题(T1和T2),分别都有十个分区,那么最后的分配结果是:
C1-0将消费T1主题中的0,1,2,3分区以及T2主题中0,1,2,3分区
C2-0将消费T1主题中的4,5,6分区以及T2主题中的4,5,6,分区
C2-1将消费T1主题中的7,8,9分区以及T2主题中的7,8,9分区
这就是消费的策略! 就是用总的分区数/消费者线程总数=每个消费者线程应该消费的分区数。当还有余数的时候就将余数分别分发到另外的消费组线程中。
在这里我们不难看出来。C1-0消费者线程比其他消费者线程多消费了两个分区,这就是Range Strategy的一个明显的弊端。 当分区很多的时候,会有个别的线程压力巨大!
下面介绍的是第二个默认的分区策略:RoundRobin strategy(轮询的消费策略) 在使用RoundRobin Starategy的时候我们必须满足两个条件:
1、同一个consumer Group里面的所有消费者的num.streams必须相等;
2、每个消费者订阅的 主题必须相同
在这里我们假设2个消费者的num.streams=2. RoundRobin starategy的工作原理: 将所有主题的分区组成TopicAndPartition列表,然后对TopAndPartition列表按照hashcode进行排序
最后按照round-robin风格将分区分别分配给不同的消费者线程。
在我们的例子中,假如按照hashcode排序完的topic-partition组依次为T1-5,T1-3,T1-0.T1-8.T1-2,T1-1,T1-4,T1-6,T1-9,我们的消费者线程排序为C1-0, C1-1 ,C2-0,C2-1,最后分区分配结果为:
C1-0将消费T1-5 , T1-2 , T1-6 分区;
C1-1将消费T1-3 , T1-1 , T1-9 分区;
C2-0将消费T1-0 , T1-4分区;
C2-1将消费T1-8 , T1-7分区;
kafka 的分区为什么只绑定一个消费者的一个消费者
假设 kafka的分区可以绑定多个消费者。现在一个分区p1,数据分布如下,有两个消费者c1, c2。
如果c1 已经消费了1-10,那么该分区的消费index 需要移动到10的位置。当c2希望消费10-20时,index再继续进行移动。
此时
- 假设broker对index进行维护。但是consumer 是进行pull操作的,拉取操作一般来说都是由拉取方提供index,数据方根据index 返回数据。如果由数据方维护index,会增加获取index 的通信开销。
- 假设consumer端进行多人的index维护,那么就得引入中心的概念,大家都在中心去获得当前的index,这会增加复杂性。
- 在consumer端维护单人index,可以方便的pull数据。牺牲了分区的分布式消费。由于可以有多个分区,权衡之下是较为合理的方案。
kafka如何保证多分区且顺序
需要顺序的场景 1.数据库中的binlog 2.一些业务需要,比如希望把某个订单的数据写入一个partion
为何消息会错乱 1.由于消费者消费消息之后,消费之后,有可能交给很多个线程去处理数据(如下图),这样就导致数据顺序错乱
解决办法 为了保证一个消费者中多个线程去处理时,不会使得消息的顺序被打乱,则可以在消费者中,消息分发至不同的线程时,加一个队列,消费者去做hash分发,将需要放在一起的数据,分发至同一个队列中,最后多个线程从队列中取数据,如下图所示。
Kafka 为什么不支持读写分离?
eader/Follower 模型并没有规定 Follower 副本不可以对外提供读服务。很多框架都是允 许这么做的,只是 Kafka 最初为了避免不一致性的问题,而采用了让 Leader 统一提供服 务的方式。
不过,在开始回答这道题时,你可以率先亮出观点:自 Kafka 2.4 之后,Kafka 提供了有限度的读写分离,也就是说,Follower 副本能够对外提供读服务。
说完这些之后,你可以再给出之前的版本不支持读写分离的理由。
场景不适用。读写分离适用于那种读负载很大,而写操作相对不频繁的场景,可 Kafka 不属于这样的场景。 同步机制。Kafka 采用 PULL 方式实现 Follower 的同步,因此,Follower 与 Leader 存 在不一致性窗口。如果允许读 Follower 副本,就势必要处理消息滞后(Lagging)的问题。
简述 Follower 副本消息同步的完整流程
首先,Follower 发送 FETCH 请求给 Leader。接着,Leader 会读取底层日志文件中的消 息数据,再更新它内存中的 Follower 副本的 LEO 值,更新为 FETCH 请求中的 fetchOffset 值。最后,尝试更新分区高水位值。Follower 接收到 FETCH 响应之后,会把 消息写入到底层日志,接着更新 LEO 和 HW 值。
Leader 和 Follower 的 HW 值更新时机是不同的,Follower 的 HW 更新永远落后于 Leader 的 HW。这种时间上的错配是造成各种不一致的原因。
阐述下 Kafka 中的领导者副本(Leader Replica)和追随者副本 (Follower Replica)的区别
你可以这么回答:Kafka 副本当前分为领导者副本和追随者副本。只有 Leader 副本才能 对外提供读写服务,响应 Clients 端的请求。Follower 副本只是采用拉(PULL)的方 式,被动地同步 Leader 副本中的数据,并且在 Leader 副本所在的 Broker 宕机后,随时 准备应聘 Leader 副本。
通常来说,回答到这个程度,其实才只说了 60%,因此,我建议你再回答两个额外的加分 项。
强调 Follower 副本也能对外提供读服务。自 Kafka 2.4 版本开始,社区通过引入新的 Broker 端参数,允许 Follower 副本有限度地提供读服务。 强调 Leader 和 Follower 的消息序列在实际场景中不一致。很多原因都可能造成 Leader 和 Follower 保存的消息序列不一致,比如程序 Bug、网络问题等。这是很严重 的错误,必须要完全规避。你可以补充下,之前确保一致性的主要手段是高水位机制, 但高水位值无法保证 Leader 连续变更场景下的数据一致性,因此,社区引入了 Leader Epoch 机制,来修复高水位值的弊端。关于“Leader Epoch 机制”,国内的资料不是 很多,它的普及度远不如高水位,不妨大胆地把这个概念秀出来,力求惊艳一把。
https://blog.csdn.net/lijingjingchn/article/details/85258883
kafka面试:www.mianshigee.com/topic/1024t… kafka:www.jianshu.com/p/511962462…