kafka原理&架构深入

文章详细介绍了Kafka的下载安装、命令行操作,以及Kafka的核心组件——生产者、消费者的架构和工作原理,包括分区策略、数据可靠性保证(ExactlyOnce语义)、ISR、offset维护等。此外,还讨论了Broker的日志结构、存储策略,以及Kafka的高性能特性,如PageCache、零拷贝技术。
摘要由CSDN通过智能技术生成

1. 下载安装

https://www.cnblogs.com/zhangzhonghui/articles/12444070.html
kafka配置详解
若kafka运行在内网服务器允许外网访问,例如内网ip: 172.10.22.134,外网ip: 9.70.168.130
进行如下配置:

listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://9.70.168.130:9092

外网访问时使用9.70.168.130:9092访问即可

2. 命令行命令

cd kafka安装目录

cd ~/kafka
  1. 后台启动
# zookeeper
bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
# kafka
bin/kafka-server-start.sh -daemon config/server.properties
  1. 停止
bin/kafka-server-stop.sh
  1. topic
#创建topic
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic your_topic
# 查看topic
bin/kafka-topics.sh --zookeeper localhost:2181 --list
# 查看特定topic
bin/kafka-topics.sh --zookeeper localhost:2181 --topic your_topic --describe
  1. producer
# 发送数据
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic your_topic
  1. 查看 consumer-groups
# 新版
bin/kafka-consumer-groups.sh --new-consumer --bootstrap-server localhost:9092 --list
# 旧版
bin/kafka-consumer-groups.sh --zookeeper localhost:2181 --list

# 新版特定consumer-groups
bin/kafka-consumer-groups.sh --new-consumer --bootstrap-server localhost:9292 --group YOUR_GROUP_ID --describe

# 旧版特定consumer-groups
bin/kafka-consumer-groups.sh --zookeeper localhost:2181 --group YOUR_GROUP_ID --describe

  1. 开启consumer消费某个topic(从头消费)
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic TOPIC --from-beginning

3. 概述

3.1 定义

Kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域。Kafka对消息保存时根据Topic进行归类,发送消息者称为Producer,消息接受者称为Consumer,此外kafka集群有多个kafka实例组成,每个实例(server)成为broker。

3.2 基本架构

在这里插入图片描述
在这里插入图片描述
1)Producer :消息生产者,就是向kafka broker发消息的客户端。
2)Consumer :消息消费者,向kafka broker取消息的客户端
3)Topic :可以理解为一个队列。
4) Consumer Group (CG):消费者组,由多个consumer组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
5)Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。
6)Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证按一个partition中的顺序将消息发给consumer,不保证一个topic的整体(多个partition间)的顺序。
7)Replica:副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。
8)leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader。
9)follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的follower。
10)Offset:kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka

4. 架构深入

4.1 生产者

4.1.1 分区

1)分区的原因

  1. 方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;
  2. 可以提高并发,因为可以以Partition为单位读写了。

2)分区的原则
我们需要将producer发送的数据封装成一个ProducerRecord对象。
在这里插入图片描述

  1. 指明 partition 的情况下,直接将指明的值直接作为 partiton 值;
  2. 没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;
  3. 既没有 partition 值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin 算法。

4.1.2 数据可靠性保证

为保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后,都需要向producer发送ack(acknowledgement确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。
在这里插入图片描述
1)副本数据同步策略

方案优点缺点
半数以上完成同步,就发送ack延迟低选举新的leader时,为了容忍n台节点的故障,需要2n+1个副本
全部完成同步,才发送ack选举新的leader时,容忍n台节点的故障,只需要n+1个副本延迟高

Kafka选择了第二种方案,原因如下:

  1. 同样为了容忍n台节点的故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而Kafka的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。
  2. 虽然第二种方案的网络延迟会比较高,但网络延迟对Kafka的影响较小。

2)ISR

采用第二种方案之后,设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?

Leader维护了一个动态的in-sync replica set (ISR),即leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。Leader发生故障之后,就会从ISR中选举新的leader。之前旧版本还有保留 replica.lag.max.messages,但后续被删除了, 因为假如某个时刻数据量很大,follower还没反应过来就被踢了,但follower本身是健康的,这显然是不合理的。

3)ack应答机制

对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的follower全部接收成功。所以Kafka为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置。
acks:

  • 0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据

  • 1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据
    在这里插入图片描述

  • -1(all):producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复
    在这里插入图片描述
    4)故障处理细节
    在这里插入图片描述
    (1)follower故障
    follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始向leader进行同步。等该follower的LEO大于等于该Partition的HW,即follower追上leader之后,就可以重新加入ISR了。
    (2)leader故障
    leader发生故障之后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。

注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。数据的不丢失或不重复由ack来决定

4.1.3 Exactly Once语义

对于某些比较重要的消息,我们需要保证exactly once语义,即保证每条消息被发送且仅被发送一次。
在0.11版本之后,Kafka引入了幂等性机制(idempotent),配合acks = -1时的at least once语义,实现了producer到broker的exactly once语义(消费者的exactly once后续再说)。

使用时,只需将enable.idempotence属性设置为true,kafka自动将acks属性设为-1

在Kafka中幂等性指相同的多条数据只会保存一条,那如何确认是相同的数据?kafka中对于每条数据都有个id,id由producerId+SequenceNumber组成,当数据到达kafka后,id将会被暂时缓存起来,若此时producer没有收到ack会重发数据,发现数据跟缓存中的数据id是一致的则不持久化
在这里插入图片描述

Properties props = new Properties();
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
props.put("acks", "all"); // 当 enable.idempotence 为 true,这里默认为 all
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer producer = new KafkaProducer(props);
producer.send(new ProducerRecord(topic, "test");

4.1.4 发送消息流程

Kafka的Producer发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程——main线程和Sender线程,以及一个线程共享变量——RecordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。
在这里插入图片描述
相关参数:

  • batch.size:只有数据积累到batch.size之后,sender才会发送数据。
  • linger.ms:如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。
  • buffer.memory:RecordAccumulator缓冲区大小
package com.atguigu.kafka;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class CustomProducer {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Properties props = new Properties();
        props.put("bootstrap.servers", "hadoop102:9092");//kafka集群,broker-list
        props.put("acks", "all");
        props.put("retries", 1);//重试次数
        props.put("batch.size", 16384);//批次大小
        props.put("linger.ms", 1);//等待时间
        props.put("buffer.memory", 33554432);//RecordAccumulator缓冲区大小
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        for (int i = 0; i < 100; i++) {
        	// 同步方式 -- get()
        	// producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i))).get();
        	// 异步:不带回调函数
            // producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)));
            // 异步:带回调函数
            producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)), new Callback() {

                //回调函数,该方法会在Producer收到ack时调用,为异步调用
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null) {
                        System.out.println("success->" + metadata.offset());
                    } else {
                        exception.printStackTrace();
                    }
                }
            });
        }
        producer.close();
    }
}

4.2 broker

4.2.1 日志结构

Kafka中消息是以topic进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。
topic是逻辑上的概念,而partition是物理上的概念,每个partition对应于一个log文件,该log文件中存储的就是producer生产的数据。Producer生产的数据会被不断追加到该log文件末端,且每条数据都有自己的offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个offset,以便出错恢复时,从上次的位置继续消费。

在这里插入图片描述

由于生产者生产的消息会不断追加到log文件末尾,为防止log文件过大导致数据定位效率低下,Kafka采取了分片和索引机制,将每个partition分为多个segment。每个segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic名称+分区序号。例如,first这个topic有三个分区,则其对应的文件夹为first-0,first-1,first-2,每个文件夹下有如下文件:

00000000000000000000.index
00000000000000000000.log
00000000000000170410.index
00000000000000170410.log
00000000000000239430.index
00000000000000239430.log

“.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中message的物理偏移地址,index和log文件以当前segment的第一条消息的offset命名
在这里插入图片描述

1) .log文件
在这里插入图片描述
一个Message由固定长度的header和一个变长的消息体body组成

  • 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表示实际消息数据。

2) .index文件
我们来思考一下,如果一个partition只有一个数据文件会怎么样?

新数据是添加在文件末尾,不论文件数据文件有多大,这个操作永远都是高效的。
查找某个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

    • 相对offset:因为数据文件分段以后,每个数据文件的起始offset不为0,相对offset表示这条Message相对于其所属数据文件中最小的offset的大小。举例,分段后的一个数据文件的offset是从20开始,那么offset为25的Message在index文件中的相对offset就是25-20 = 5。存储相对offset可以减小索引文件占用的空间。
    • position: 表示该条Message在数据文件中的绝对位置。只要打开文件并移动文件指针到这个position就可以读取对应的Message了。
      index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。稀疏索引是基于跳跃列表的数据结构,是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表

4.2.2 存储策略

无论消息是否被消费,kafka都会保留所有消息,当然可对消息进行压缩或者删除。
有两种策略可以删除旧数据:
1)基于时间:log.retention.hours=168
2)基于大小:log.retention.bytes=1073741824
需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。

4.2.3 Controller & ZooKeeper

Kafka集群中有一个broker会被选举为Controller(先到先得),负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作,Controller的管理工作都是依赖于Zookeeper的。
以下为partition的leader选举过程:
在这里插入图片描述
(1) 到zookeeper中的brokers/ids/注册节点信息 [0,1,2]
(2) 从ISR中获取选取leader 0,到/brokers/topics/first/partitions/0/state更新 topic的partition信息
(3) leader 0发生故障,通知 /brokers/ids/节点,更新为 [1,2]
(4) Controller监听到节点发生变化,则重新获取ISD,选举新leader,再更新leader及ISR

4.2.4 高效读写数据

1)顺序写磁盘
Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。

2) Page Cache
为了优化读写性能,Kafka 利用了操作系统本身的 Page Cache。数据通过mmap内存映射的方式直接写入page cache,定时刷新脏页到磁盘。消费者拉取消息时,如果数据在page cache中,甚至能不需要去读磁盘io。读操作可直接在 Page Cache 内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过 Page Cache)交换数据。

3)零拷贝技术
在这里插入图片描述
传统路径:File -> Page Cache -> Application Cache -> Socket Cache -> NIC
Kafka零拷贝过程:File -> Page Cache -> NIC
为啥可以进行零拷贝?因为kafka写进去.log文件的数据就是经过序列化的,读取出来的数据就不要经过用户空间的处理了,减少了不必要的拷贝次数和用户态和内核态的切换,大大减少读取的时延

参考:kafka的零拷贝

4) 分区分段+稀疏索引
Kafka 的 message 是按 topic分 类存储的,topic 中的数据又是按照一个一个的 partition 即分区存储到不同 broker 节点。每个 partition 对应了操作系统上的一个文件夹,partition 实际上又是按照segment分段存储的。通过这种分区分段的设计,Kafka 的 message 消息实际上是分布式存储在一个一个小的 segment 中的,每次文件操作也是直接操作的 segment。为了进一步的查询优化,Kafka 又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。

5) 批量读写
生产者可以借助累加器,批量发送消息,消费者也可以批量拉取消费

4.3 消费者

4.3.1 消费方式

push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。
所以consumer采用pull(拉)模式从broker中读取数据。
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。

4.3.2 分区分配策略

一个consumer group中有多个consumer,一个 topic有多个partition,所以必然会涉及到partition的分配问题,即确定那个partition由哪个consumer来消费。
Kafka有三种消费者分区分配策略:
1.RoundRobin
2.Range
3.Sticky

kafka在0.11版本引入了Sticky分区分配策略,它的两个主要目的是:

  • 分区的分配要尽可能的均匀,分配给消费者者的主题分区数最多相差一个;
  • 分区的分配尽可能的与上次分配的保持相同。

当两者发生冲突时,第一个目标优先于第二个目标。

参考:kafka的消费者分区分配策略

4.3.3 offset的维护

由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets

4.3.4 Eactly Once语义

需要保证消费数据和提交offset是原子性的

4.4 重平衡(rebalance)

kafka 重平衡(Rebalance)

4.5 拦截器

Producer拦截器(interceptor)是在Kafka 0.10版本被引入的,主要用于实现clients端的定制化控制逻辑。
对于producer而言,interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor的实现接口是org.apache.kafka.clients.producer.ProducerInterceptor,其定义的方法包括:

(1)configure(configs)
获取配置信息和初始化数据时调用。
(2)onSend(ProducerRecord):
该方法封装进KafkaProducer.send方法中,即它运行在用户主线程中。Producer确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区,否则会影响目标分区的计算。
(3)onAcknowledgement(RecordMetadata, Exception):
该方法会在消息从RecordAccumulator成功发送到Kafka Broker之后,或者在发送过程中失败时调用。并且通常都是在producer回调逻辑触发之前。onAcknowledgement运行在producer的IO线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率。
(4)close:
关闭interceptor,主要用于执行一些资源清理工作
如前所述,interceptor可能被运行在多个线程中,因此在具体实现时用户需要自行确保线程安全。另外倘若指定了多个interceptor,则producer将按照指定顺序调用它们,并仅仅是捕获每个interceptor可能抛出的异常记录到错误日志中而非在向上传递。这在使用过程中要特别留意。

案例:
在消息发送前将时间戳信息加到消息value的最前部

package com.atguigu.kafka.interceptor;
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

public class TimeInterceptor implements ProducerInterceptor<String, String> {

	@Override
	public void configure(Map<String, ?> configs) {

	}

	@Override
	public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
		// 创建一个新的record,把时间戳写入消息体的最前部
		return new ProducerRecord(record.topic(), record.partition(), record.timestamp(), record.key(),
				System.currentTimeMillis() + "," + record.value().toString());
	}
	@Override
	public void onAcknowledgement(RecordMetadata metadata, Exception exception) {

	}

	@Override
	public void close() {

	}
}

主程序

package com.atguigu.kafka.interceptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;

public class InterceptorProducer {

	public static void main(String[] args) throws Exception {
		// 1 设置配置信息
		Properties props = new Properties();
		props.put("bootstrap.servers", "hadoop102:9092");
		props.put("acks", "all");
		props.put("retries", 0);
		props.put("batch.size", 16384);
		props.put("linger.ms", 1);
		props.put("buffer.memory", 33554432);
		props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
		props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
		
		// 2 构建拦截链
		List<String> interceptors = new ArrayList<>();
		interceptors.add("com.atguigu.kafka.interceptor.TimeInterceptor");
		props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
		 
		String topic = "first";
		Producer<String, String> producer = new KafkaProducer<>(props);
		
		// 3 发送消息
		for (int i = 0; i < 10; i++) {
			
		    ProducerRecord<String, String> record = new ProducerRecord<>(topic, "message" + i);
		    producer.send(record);
		}
		 
		// 4 一定要关闭producer,这样才会调用interceptor的close方法
		producer.close();
	}
}

4.6 相关参数配置

  1. 生产消费最大消息
    这里的消息大小是指的一批次发送的消息大小,不是一条消息大小
  • max.request.size:生产端能发送的最大消息大小 默认0.9M
  • message.max.bytes: Broker端 可以接受 Producer 发送过来的消息大小,默认值为0.9M
  • replica.fetch.max.bytes: broker端可复制的消息的最大字节数。这个值应该比message.max.bytes大,否则broker会接收此消息,但无法将此消息复制出去,从而造成数据丢失。默认: 1MB
  • fetch.max.bytes:消费者单次从 Broker 获取消息的最大字节数,默认50
  • max.message.bytes: topic属性配置,它只针对某个主题生效,可动态配置,可覆盖全局的 message.max.bytes,好处就是可以针对不同主题去设置 Broker 接收消息的大小,而且不用重启 Broker

以上三个参数设置大小要求:

max.request.size < message.max.bytes < fetch.max.bytes
max.request.siz < replica.fetch.max.bytes 

5. 面试题

  1. Kafka中的ISR、AR又代表什么?
    ISR:与leader保持同步的follower集合
    AR:分区的所有副本

  2. Kafka中的HW、LEO等分别代表什么?
    LEO:没个副本的最后条消息的offset
    HW:一个分区中所有副本最小的offset

  3. Kafka中是怎么体现消息顺序性的?
    每个分区内,每条消息都有一个offset,故只能保证分区内有序。

  4. Kafka中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序是什么?
    拦截器 -> 序列化器 -> 分区器
    但拦截器中主要有两个方法,onSend() 和 onAcknowledgement(),onSend()是在消息被序列化以及计算分区前调用该方法,onAcknowledgement()成功发送到Kafka Broker之后,或者在发送过程中失败时调用

  5. Kafka生产者客户端的整体结构是什么样子的?使用了几个线程来处理?分别是什么?
    两个线程:主线程和sender线程

  6. “消费组中的消费者个数如果超过topic的分区,那么就会有消费者消费不到数据”这句话是否正确?
    正确

  7. 消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?
    offset+1

  8. 有哪些情形会造成重复消费?
    生产者:ack=-1时,leader和follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复。
    消费者:先消费后提交offset

  9. 那些情景会造成消息漏消费?
    生产者:ack=0,broker还没完成写入 或 ack=-1,leader落盘返回ack,但follower还没完成同步leader就挂了
    消费者:先提交offset,后消费,有可能造成数据的遗漏

  10. 当你使用kafka-topics.sh创建(删除)了一个topic之后,Kafka背后会执行什么逻辑?
    1)会在zookeeper中的/brokers/topics节点下创建一个新的topic节点,如:/brokers/topics/first
    2)触发Controller的监听程序
    3)kafka Controller 负责topic的创建工作,并更新metadata cache

  11. topic的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
    可以增加
    bin/kafka-topics.sh --zookeeper localhost:2181/kafka --alter --topic topic-config --partitions 3

  12. topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
    不可以减少,被删除的分区数据难以处理(该分区的数据该如何被其他分区消费?如何分配?从哪里开始消费?等一系列难以处理的问题,所以kafka不支持)。

  13. Kafka有内部的topic吗?如果有是什么?有什么所用?
    __consumer_offsets,保存消费者offset

  14. Kafka分区分配的概念?
    一个topic多个分区,一个消费者组多个消费者,故需要将分区分配个消费者(roundrobin、range、Sticky)

  15. 简述Kafka的日志目录结构?
    每个分区对应一个文件夹,文件夹的命名为topic-0,topic-1,内部为.log和.index文件

  16. 聊一聊Kafka Controller的作用?
    负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。

  17. Kafka中有那些地方需要选举?这些地方的选举策略又有哪些?
    partition leader(ISR),controller(先到先得)

  18. 失效副本是指什么?有那些应对措施?
    不能及时与leader同步,暂时踢出ISR,恢复后读取磁盘中持久化的HW,从HW开始消费,等其追上leader之后再重新加入

  19. Kafka的那些设计让它有如此高的性能?
    1)借助累加器,生产者main线程批量发送消息,sender线程也可以批量拉取消费;
    2)利用page cache,定时刷新脏页到磁盘,同时也是顺序写磁盘;
    3)同样消费者在消费数据时,如果数据在page cache中(消费和生产速度相当),甚至不需要通过物理磁盘,直接通过 Page Cache交换数据;就算数据只在磁盘,也可以通过零拷贝技术减少了不必要的拷贝次数和用户态和内核态的切换,大大减少读取的时延(为啥可以进行零拷贝?因为kafka写进去.log文件的数据就是经过序列化的)
    4)分区分段+稀疏索引:topic-partition-segment Kafka 的 message 消息实际上是分布式存储在一个一个小的 segment 中的,每次文件操作也是直接操作的 segment。为了进一步的查询优化(O(1)),Kafka 又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件

  20. Kafka如何保证Exactly Once语义
    生产者:ack=-1并且开启幂等性,enable.idempotence属性设置为true(该选项会自动将ack设置为-1)
    消费者:保证消费和提交offset是原子性的

  21. 如何提升吞吐量?

  • 提升生产吞吐量
    (1)buffer.memory:发送消息的缓冲区大小,默认值是 32m,可以增加到 64m。
    (2)batch.size:默认是 16k。如果 batch 设置太小,会导致频繁网络请求,吞吐量下降;
    如果 batch 太大,会导致一条消息需要等待很久才能被发送出去,增加网络延时。
    (3)linger.ms,这个值默认是 0,意思就是消息必须立即被发送。一般设置一个 5-100
    毫秒。如果 linger.ms 设置的太小,会导致频繁网络请求,吞吐量下降;如果 linger.ms 太长,
    会导致一条消息需要等待很久才能被发送出去,增加网络延时。
    (4)compression.type:默认是 none,不压缩,但是也可以使用 lz4 压缩,效率还是不
    错的,压缩之后可以减小数据量,提升吞吐量,但是会加大 producer 端的 CPU 开销。

  • 增加分区

  • 消费者提高吞吐量
    (1)增大 fetch.max.bytes 大小(默认是 50m) – 批量消费每次最多消费多少条消息
    (2)增大 max.partition.fetch.bytes(默认是 1m) – 一次fetch请求中,从一个partition中取得的records最大大小
    (3)增大 max.poll.records 大小(默认是 500 条) – 一次fetch请求中从一个broker中取得records的最大大小

  • 增加下游消费者处理能力

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值