Kafka总结
Kafka
1. Kafka概述
1.1. 什么是Kafka
Apache Kafka是分布式发布-订阅消息系统(消息中间件)。它最初由LinkedIn公司开发,之后成为Apache项目的一部分。Kafka是一种快速、可扩展的、设计内在就是分布式的,分区的和可复制的提交日志服务。
简单说明什么是Kafka:
举个例子,生产者消费者,生产者生产鸡蛋,消费者消费鸡蛋,生产者生产一个鸡蛋,消费者就消费一个鸡蛋,假设消费者消费鸡蛋的时候噎住了(系统宕机了),生产者还在生产鸡蛋,那新生产的鸡蛋就丢失了。再比如生产者很强劲(大交易量的情况),生产者1秒钟生产100个鸡蛋,消费者1秒钟只能吃50个鸡蛋,那要不了一会,消费者就吃不消了(消息堵塞,最终导致系统超时),消费者拒绝再吃了,”鸡蛋“又丢失了,这个时候我们放个篮子在它们中间,生产出来的鸡蛋都放到篮子里,消费者去篮子里拿鸡蛋,这样鸡蛋就不会丢失了,都在篮子里,而这个篮子就是”Kafka“。
鸡蛋其实就是“数据流”,系统之间的交互都是通过“数据流”来传输的(就是tcp、http什么的),也称为报文,也叫“消息”。
传统消息中间件服务RabbitMQ、Apache ActiveMQ等。
Apache Kafka与传统消息系统相比,有以下不同:
l 它是分布式系统,易于向外扩展;
l 它同时为发布和订阅提供高吞吐量;
l 它支持多订阅者,当失败时能自动平衡消费者;
l 它将消息持久化到磁盘,因此可用于批量消费,例如ETL,以及实时应用程序。容错
1.2. Kafka术语
|
术语 |
解释 |
|
Broker |
Kafka集群包含一个或多个服务器,这种服务器被称为broker,用来存储消息,消费者将从broker拉取消息 |
|
Topic
|
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)这使得kafka吞吐率可以水平扩展 |
|
Partition |
Partition是物理上的概念,每个Topic包含一个或多个Partition. |
|
Producer |
负责发布消息到Kafka broker |
|
Consumer |
消息消费者,向Kafka broker读取消息的客户端 |
|
Consumer Group |
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)同一个消费者组可以并发的消费多个分区的消息,同一个partition也可以由多个消费者组并发消费,但是在消费者组中一个partition只能由一个consumer消费 |
|
replica |
partition 的副本,保障 partition 的高可用 |
|
leader |
replica 中的一个角色, producer 和 consumer 只跟 leader 交互 |
|
follower |
replica 中的一个角色,从 leader 中复制数据 |
|
controller |
Kafka 集群中的其中一个服务器,用来进行 leader election 以及各种 failover(故障检测&&故障转移) |
小白理解:
- producer:生产者,就是它来生产“鸡蛋”的。
- consumer:消费者,生出的“鸡蛋”它来消费。
- topic:把它理解为标签,生产者每生产出来一个鸡蛋就贴上一个标签(topic),消费者可不是谁生产的“鸡蛋”都吃的,这样不同的生产者生产出来的“鸡蛋”,消费者就可以选择性的“吃”了。
- broker:就是篮子了。
如果从技术角度,topic标签实际就是队列,生产者把所有“鸡蛋(消息)”都放到对应的队列里了,消费者到指定的队列里取。
1.3. Kafka为什么吞吐量大,速度快
- 顺序读写
Kafka的message是不断追加到本地文件末尾的,而不是随机写入的。基于磁盘的顺序写入性能甚至高于内存的随机写入,缺点就是不能删除数据,但kafka有数据删除的机制
- Page Cache
kafka利用操作系统本身的Page Cache,利用操作系统自身的内存而不是jvm内存
好处:
(1 避免Object消耗:如果使用java堆,java对象内存消耗较大,通常是数据的两倍
(2 避免GC问题
相比于jvm,in-memory等数据结构,Page Cache更加简洁可靠。首先,操作系统层面的缓存利用率增高,存储的都是紧凑的字节结构而不是对象,其次操作系统本身对Page Cache做了大量优化,提供了write-behind,read-ahead以及flush等多种机制,再者即使服务进程重启,系统缓存也不会丢失,避免了in-process cache重建缓存的过程。
- 零拷贝
linux操作系统 “零拷贝” 机制使用了sendfile方法, 允许操作系统将数据从Page Cache 直接发送到网络,只需要最后一步的copy操作将数据复制到 NIC 缓冲区, 这样避免重新复制数据 。示意图如下:
- 分段+索引
下文会做详细介绍
- 批量读写
除了底层之外,kafka在应用程序层面也做了提升:批次写入
- 批量压缩
对于kafka来说,cup不是最大的瓶颈,网络IO才是,kafaka利用批量压缩数据的方式了来实现这一点。
Kafka允许使用递归的消息集合,批量的消息可以使用压缩的形式传输并且在日志中也可以保留压缩格式,直到被消费者消费。
Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。
1.4. 如何 增加kafka吞吐量
- buffer.memory:buffer设置大了有助于提升吞吐性,但是batch太大会增大延迟,可搭配linger_ms参数使用
- linger_ms:如果batch太大,或者producer qps不高,batch添加的会很慢,我们可以强制在linger_ms时间后发送batch数据
1.5. 重平衡reblance
https://mp.weixin.qq.com/s/4DFup_NziFJ1xdc4bZnVcg
组订阅topic数变更
topic partition数变更
consumer成员变更
consumer 加入群组或者离开群组的时候
consumer被检测为崩溃的时候
在新版本中,消费组的协调管理已经依赖于 Broker 端某个节点,该节点即是该消费组的 Coordinator, 并且每个消费组有且只有一个 Coordinator,它负责消费组内所有的事务协调,其中包括分区分配,重平衡触发,消费者离开与剔除等等,整个消费组都会被 Coordinator 管控着,在每个过程中,消费组都有一个状态,Kafka 为消费组定义了 5 个状态,如下:
1.Empty:消费组没有一个活跃的消费者;2.PreparingRebalance:消费组准备进行重平衡,此时的消费组可能已经接受了部分消费者加入组请求;3.AwaitingSync:全部消费者都已经加入组并且正在进行重平衡,各个消费者等待 Broker 分配分区方案;4.Stable:分区方案已经全部发送给消费者,消费者已经在正常消费;5.Dead:该消费组被 Coordinator 彻底废弃。
可以看出,重平衡发生在 PreparingRebalance 和 AwaitingSync 状态机中,重平衡主要包括以下两个步骤:
- 加入组(JoinGroup):当消费者心跳包响应 REBALANCE_IN_PROGRESS 时,说明消费组正在重平衡,此时消费者会停止消费,并且发送请求加入消费组;
- 2.同步更新分配方案:当 Coordinator 收到所有组内成员的加入组请求后,会选出一个consumer Leader,然后让consumer Leader进行分配,分配完后会将分配方案放入SyncGroup请求中发送会Coordinator,Coordinator根据分配方案发送给每个消费者。
1.6. ISR副本同步机制
ISR(in-sync replica) 就是 Kafka 为某个分区维护的一组同步集合,即每个分区都有自己的一个 ISR 集合,处于 ISR 集合中的副本,意味着 follower 副本与 le处于 ISR 集合中的副本才有资ader 副本保持同步状态,只有格被选举为 leader。一条 Kafka 消息,只有被 ISR 中的副本都接收到,才被视为“已同步”状态。这跟 zk 的同步机制不一样,zk 只需要超过半数节点写入,就可被视为已写入成功。
在 0.9.0.0 版本之后,Kafka 给出了一个更好的解决方案,去除了 replica.lag.max.messages(即允许 follower 副本落后 leader 副本的消息数量,超过这个数量后,follower 会被踢出 ISR),用 replica.lag.time.max.ms 参数来代替,该参数的意思指的是允许 follower 副本不同步消息的最大时间值,即只要在 replica.lag.time.max.ms 时间内 follower 有同步消息,即认为该 follower 处于 ISR 中,这就很好地避免了在某个瞬间生产者一下子发送大量消息到 leader 副本导致该分区 ISR 频繁收缩与扩张的问题了。
1.7. 水印备份机制
为了解决 HW 更新时机是异步延迟的,而 HW 又是决定日志是否备份成功的标志,从而造成数据丢失和数据不一致的现象,Kafka 引入了 leader epoch 机制,在每个副本日志目录下都创建一个 leader-epoch-checkpoint 文件,用于保存 leader 的 epoch 信息
https://mp.weixin.qq.com/s/WSdebVgIpvJ_c4DpFYqO4w
1.8. preferred leader
因为在 kafka 集群长时间运行中,broker 的宕机或崩溃是不可避免的,leader 就会发生转移,即使 broker 重新回来,也不会是 leader 了。在众多 leader 的转移过程中,就会产生 leader 不均衡现象,可能一小部分 broker 上有大量的 leader,影响了整个集群的性能,所以就需要把 leader 调整会最初的 broker 上,这就需要 preferred leader 选举
2. Kafka安装
2.1. 下载
Apache kafka 官方: http://kafka.apache.org/downloads.html
Scala 2.11 - kafka_2.11-0.10.2.0.tgz (asc, md5)
2.2. Kafka集群安装
2.2.1. 安装JDK &配置JAVA_HOME
2.2.2. 安装Zookeeper
参照Zookeeper官网搭建一个ZK集群, 并启动ZK集群。
2.2.3. 解压Kafka安装包
tar -zxvf kafka_2.11-0.10.2.1.tgz -C /apps/
2.2.3.1. 修改配置文件config/server.properties
|
vi server.properties
broker.id=0 //为依次增长的:0、1、2、3、4,集群中唯一id
delete.topic.enable=true #删除主题的配置,默认是false
listeners=PLAINTEXT://kk-01:9092 # 监听的主机及端口号
log.dirs=/kafkaData/logs// Kafka的消息数据存储路径
num.partitions=3 #创建主题的时候,默认有3个分区
zookeeper.connect=master:2181,slave1:2181,slave2:2181 //zookeeperServers列表,各节点以逗号分开
# 将Kafka server.properties 文件拷贝到其他节点机器 KAFKA_HOME/config>scp server.properties xx:$PWD |
2.2.3.2. 启动Kafka
在每台节点上启动:
bin/kafka-server-start.sh [-daemon] config/server.properties &
2.2.3.3. 测试集群
1-进入kafka根目录,创建Topic名称为: test的主题
bin/kafka-topics.sh --create --zookeeper 192.168.1.xx:2181,192.168.1.xx:2181,192.168.1.xx:2181 --replication-factor 3 --partitions 1 --topic test
2-列出已创建的topic列表
bin/kafka-topics.sh --list --zookeeper localhost:2181
3-查看Topic的详细信息
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
Topic:test PartitionCount:1 ReplicationFactor:3 Configs:
Topic: test Partition: 0 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
第一行是对所有分区的一个描述,然后每个分区对应一行,因为只有一个分区所以下面一行。
leader:负责处理消息的读和写,leader是从所有节点中随机选择的.
replicas:列出了所有的副本节点,不管节点是否在服务中.
isr:是正在服务中的节点.
在例子中,节点1是作为leader运行。
4-模拟客户端去发送消息
bin/kafka-console-producer.sh --broker-list 192.168.1.xx:9092,192.168.1.xx:9092 --topic test
5-模拟客户端去接受消息
bin/kafka-console-consumer.sh --bootstrap-server kk-03:9092 --from-beginning --topic hellotopic
6-测试一下容错能力.
Kill -9 pid[leader节点]
另外一个节点被选做了leader,node 1 不再出现在 in-sync 副本列表中:
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
Topic:test PartitionCount:1 ReplicationFactor:3 Configs:
Topic: test Partition: 0 Leader: 2 Replicas: 1,2,0 Isr: 2,0
虽然最初负责续写消息的leader down掉了,但之前的消息还是可以消费的:
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --from-beginning --topic test
3. Kafka客户端开发
3.1. Java Client
3.1.1. 添加pom.xml依赖
|
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>0.10.2.0</version> </dependency>
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.10.2.0</version> </dependency> |
3.1.2. Producer 生产者
|
public static void main(String[] args) throws Exception{
String topic = "test";
Properties props = new Properties(); props.put("bootstrap.servers", "master:9092");
//0是不获取反馈(消息有可能传输失败) //1是获取消息传递给leader后反馈(其他副本有可能接受消息失败) //-1 | all是所有in-sync replicas接受到消息时的反馈 props.put("acks", "all"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(props);
for (int i = 0; i < 100; i++) { ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>(topic, "Key" + i, "message-" + i); kafkaProducer.send(producerRecord, new Callback() { public void onCompletion(RecordMetadata recordMetadata, Exception e) { if (e != null) { System.out.println("e = " + e.getMessage()); } else { System.out.println("recordMetadata = " + recordMetadata.topic() +"" + recordMetadata.offset()); } } }); } kafkaProducer.close(); } |
3.1.3. Consumer 消费者
|
public static void main(String[] args) {
Properties props = new Properties(); props.put("bootstrap.servers", "master:9092"); props.put("group.id", "test"); props.put("enable.auto.commit", "true"); props.put("auto.commit.interval.ms", "1000"); props.put("session.timeout.ms", "30000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(props);
kafkaConsumer.subscribe(Arrays.asList("test"));
while (true) { ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(100); for (ConsumerRecord<String, String> record : consumerRecords) { System.out.printf("offset = %d, key = %s, value = %s\\n", record.offset(), record.key(), record.value()); } } } |
4. Kafka原理
4.1. Kafka的拓扑结构
如上图所示,一个典型的Kafka集群中包含若干Producer,若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。
4.2. Zookeeper节点
4.3. Producer发布消息
- producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 partition 中,属于顺序写磁盘。
- producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个partition。
|
1. 指定了 partition,则直接使用; 2. 未指定 partition 但指定 key,通过对 key 的 value 进行hash 选出一个 partition 3. partition 和 key 都未指定,使用轮询选出一个 partition 。 发送消息详细步骤:(蓄水池机制,底层分区算法,集群信息获取等我就不详细介绍了)
ProducerRecord中指定了partition就使用指定的,key和topic的partition取余,要不随机生成一个counter(每次单调递增)和topic的partition取余。
每条消息先从MetaDate里面获取分区信息,再申请一个Buffer空间形成一个批处理空间然后RecordAccumulator 将消息append进buffer缓冲区,最后将每个批次压入队列中,队列的操作是加锁的,所以batch中消息是有序的。当batch中的消息满时,唤醒sender线程,该线程异步操作。
3.1 确定tp relica leader所在的broker Kafka中 每台broker都保存了kafka集群的metadata信息,metadata信息里包括了每个topic的所有partition的信息: leader, leader_epoch, controller_epoch, isr, replicas等;Kafka客户端从任一broker都可以获取到需要的metadata信息;sender线程通过metadata信息可以知道tp leader的brokerId 3.2幂等性发送 为了实现Producer幂等性,kafka引入了producerID和Sequence Number。对于每个PID,该Producer发送消息的每个topic-partition都对应着一个单调递增的Sequence Number,同样,在broker端也会为每一个<PID,topic,Partition>维护一个序号,并且没commit一条消息时,将其对应的序号递增。对于接受的消息,如果其序号比broker维护的序号大1,则broker会接受它,否则将其丢弃。 如果消息序号比Broker维护的序号差值比一大,说明中间有数据尚未写入,即乱序,此时Broker拒绝该消息,Producer抛出InvalidSequenceNumber
一旦broker处理完Sender的produce请求,就会发送produce response给Sender,此时producer将执行我们为send()设置的回调函数。至此producer的send执行完毕。 |
4.3.1. 写数据流程
|
1. producer 先从 zookeeper 的 "/brokers/.../state" 节点找到该 partition 的 leader 2. producer 将消息发送给该 leader 3. leader 将消息写入本地 log 4. followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK 5. leader 收到所有 ISR(in-sync replicas) 中的 replica 的 ACK 后向 producer 发送 ACK |
4.4. Broker存储消息
4.4.1. 消息存储方式
物理上把 topic 分成一个或多个 partition(对应 server.properties 中的 num.partitions=3 配置),每个 partition 物理上对应一个文件夹(该文件夹存储该 partition 的所有消息和索引文件),如下:
Kafka的消息以二进制的方式紧凑地存储,节省了很大空间
此外消息存在ByteBuffer而不是堆,这样broker进程挂掉时,数据不会丢失,同时避免了gc问题
通过零拷贝和顺序寻址,让消息存储和读取速度都非常快
处理fetch请求的时候通过zero-copy 加快速度
4.4.2. 消息存储策略
无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:
log.retention.hours=168 #基于时间
log.retention.bytes=1073741824 #基于大小
4.5. Kafka log的存储解析
Partition中的每条Message由offset来表示它在这个partition中的偏移量,这个offset不是该Message在partition数据文件中的实际存储位置,而是逻辑上一个值,它唯一确定了partition中的一条Message。因此,可以认为offset是partition中Message的id。partition中的每条Message包含了以下三个属性:
l offset
l MessageSize
l data
其中offset为long型,MessageSize为int32,表示data有多大,data为message的具体内容。
我们来思考一下,如果一个partition只有一个数据文件会怎么样?
1) 新数据是添加在文件末尾,不论文件数据文件有多大,这个操作永远都是高效的。
2) 查找某个offset的Message是顺序查找的。因此,如果数据文件很大的话,查找的效率就低。
那Kafka是如何解决查找效率的的问题呢?有两大法宝:1) 分段 2) 索引。
- 数据文件的分段
Kafka解决查询效率的手段之一是将数据文件分段,比如有100条Message,它们的offset是从0到99。假设将数据文件分成5段,第一段为0-19,第二段为20-39,以此类推,每段放在一个单独的数据文件里面,数据文件以该段中最小的offset命名。这样在查找指定offset的Message的时候,用二分查找就可以定位到该Message在哪个段中。
- 为数据文件建索引
数据文件分段使得可以在一个较小的数据文件中查找对应offset的Message了,但是这依然需要顺序扫描才能找到对应offset的Message。为了进一步提高查找的效率,Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index。
索引文件中包含若干个索引条目,每个条目表示数据文件中一条Message的索引。索引包含两个部分,分别为相对offset和position。
l 相对offset:因为数据文件分段以后,每个数据文件的起始offset不为0,相对offset表示这条Message相对于其所属数据文件中最小的offset的大小。举例,分段后的一个数据文件的offset是从20开始,那么offset为25的Message在index文件中的相对offset就是25-20 = 5。存储相对offset可以减小索引文件占用的空间。
l position,表示该条Message在数据文件中的绝对位置。只要打开文件并移动文件指针到这个position就可以读取对应的Message了。
index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。
我们以几张图来总结一下Message是如何在Kafka中存储的,以及如何查找指定offset的Message的。
Message是按照topic来组织,每个topic可以分成多个的partition,比如:有5个partition的名为为page_visits的topic的目录结构为:
partition是分段的,每个段叫Segment,包括了一个数据文件和一个索引文件,下图是某个partition目录下的文件:
可以看到,这个partition有4个Segment。
图示Kafka是如何查找Message的。
比如:要查找绝对offset为7的Message:
首先是用二分查找确定它是在哪个LogSegment中,自然是在第一个Segment中。
打开这个Segment的index文件,也是用二分查找找到offset小于或者等于指定offset的索引条目中最大的那个offset。自然offset为6的那个索引是我们要找的,通过索引文件我们知道offset为6的Message在数据文件中的位置为9807。
打开数据文件,从位置为9807的那个地方开始顺序扫描直到找到offset为7的那条Message。
这套机制是建立在offset是有序的。索引文件被映射到内存中,所以查找的速度还是很快的。
一句话,Kafka的Message存储采用了分区(partition),分段(LogSegment)和稀疏索引这几个手段来达到了高效性。
4.6. 1. spark 从 kafka 消费的两种方式,分别有什么特点
(1) Receiver 方式
使用kafka的高层次API进行消费,然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL)。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复。
需要注意的是:
1、Kafka中的topic的partition,与Spark中的RDD的partition是没有关系的。所以,在KafkaUtils.createStream()中,提高partition的数量,只会增加一个Receiver中,读取partition的线程的数量。不会增加Spark处理数据的并行度。
2、如果基于容错的文件系统,比如HDFS,启用了预写日志机制,接收到的数据都会被复制一份到预写日志中。因此,在KafkaUtils.createStream()中,设置的持久化级别是StorageLevel.MEMORY_AND_DISK_SER。
receiver方式的api
(2) 基于Direct的方式
使用kafka更加底层的api,自己维护偏移量。
这种方式有如下优点:
1、简化并行读取:如果要读取多个partition,不需要创建多个输入DStream然后对它们进行union操作。Spark会创建跟Kafka partition一样多的RDD partition,并且会并行从Kafka中读取数据。所以在Kafka partition和RDD partition之间,有一个一对一的映射关系。
3、高性能:receiver方式为了保证数据不丢失,需要开启WAL机制,这样同样的数据会保存两份。而基于direct的方式,不依赖Receiver,不需要开启WAL机制,只要Kafka中作了数据的复制,那么就可以通过Kafka的副本进行恢复。
Direct 方式的api
Kafka 的ack机制(数据可靠性保证)
0:不等待broker返回确认消息
1:等待topic中某个partition leader保存成功的状态反馈
-1:等待topic中某个partition 所有副本都保存成功的状态反馈
仅设置acks=-1也不能保证数据不丢失,当Isr列表中只有Leader时,同样有可能造成数据丢失。要保证数据不丢除了设置acks=-1, 还要保证ISR的大小大于等于2,具体参数设置:
l request.required.acks:设置为-1 等待所有ISR列表中的Replica接收到消息后采算写成功;
l min.insync.replicas: 设置为大于等于2,保证ISR中至少有两个Replica
注意:Producer要在吞吐率和数据可靠性之间做一个权衡
2. 如何保证kafka消费者消费数据是全局有序的
伪命题
如果要全局有序的,必须保证生产有序,存储有序,消费有序。
由于生产可以做集群,存储可以分片,消费可以设置为一个consumerGroup,要保证全局有序,就需要保证每个环节都有序。
只有一个可能,就是一个生产者,一个partition,一个消费者。这种场景和大数据应用场景相悖。
3. Kafka数据一致性保证
一致性定义:若某条消息对client可见,那么即使Leader挂了,在新Leader上数据依然可以被读到。
HW-HighWaterMark: client可以从Leader读到的最大msg offset,即对外可见的最大offset, HW=max(replica.offset)
对于Leader新收到的msg,client不能立刻消费,Leader会等待该消息被所有ISR中的replica同步后,更新HW,此时该消息才能被client消费,这样就保证了如果Leader fail,该消息仍然可以从新选举的Leader中获取。
对于来自内部Broker的读取请求,没有HW的限制。同时,Follower也会维护一份自己的HW,Folloer.HW = min(Leader.HW, Follower.offset)
4. Kafka在zookeeper上记录了哪些信息
一共在zookeeper上记录了这些节点:consumers、admin、config、controller、brokers、controller_epoch。
其中controller_epoch节点记录了kafka中的center controller选举的次数,这个值初始为1,当controller挂掉后重新选举时这个值+1。
Controller节点记录了中央控制器所在哪一台broker的信息。
5. Zookeeper在kafka中的作用
(1) Controller选举、(2) 记录集群中有哪些broker、(3) 记录集群中有哪些topic,topic都有哪些partition,副本在哪里,leader是谁。
浙公网安备 33010602011771号