Kafka核心原理

Kafka架构设计

安装Kafka

zkServer.sh start
tar -zxf kafka_2.11-2.0.0.tgz
mv kafka_2.11-2.0.0 soft/kafka211
cd soft/kafka211/config/
vi server.properties
如果是分布式环境则需要修改broker.id的编号不能相同
log.dir是存储数据的位置需要指定(不是日志)
Zookeeper.connect=你的zookeeper的IP:2182(多个用逗号隔开)
vi /etc/profile
source /etc/profile
	export KAFKA_HOME=/opt/soft/kafka211
	export PATH=$PATH:$KAFKA_HOME/bin
// 启动kafka
kafka-server-start.sh /opt/soft/kafka211/config/server.properties
kafka-topics.sh --zookeeper 192.168.56.100:2181 --list
kafka-topics.sh --create --zookeeper 192.168.56.100:2181 --replication-factor 1 --partitions 1 --topic demo
Created topic "demo"
kafka-topics.sh --zookeeper 192.168.56.100:2181 --list    // demo
kafka-console-consumer.sh --bootstrap-server 192.168.56.100:9092 --from-beginning --topic demo
kafka-console-producer.sh --broker-list 192.168.56.100:9092 --topic demo
先启动consumer,后启动producer,producer输入,consumer输出
kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list 192.168.56.100:9092 --topic testPartition2

Kafka核心概念

术语解释

如下图所示,Kafka集群由不同的Broker节点组成,每个Broker都有唯一的id(如从0开始编号),应该在节点安装时指定。Broker在本地文件系统存储了所有Topic数据,不依赖外部数据库。
在这里插入图片描述
Topic类似于一个数据库表,里面存储了一条条的key-value消息。每条消息的Key及Value都有相应的序列化方式,通常是在Producer生产消息是指定相应的序列化器,而Consumer消费时使用相应的反序列化器,在实际应用时生产者与消费者应该约定统一的序列化方式。Topic又分为多个不同的分区(Partition),每一条消息都由分区策略控制该消息应该存储在哪个分区之中,并由分区相应的偏移量(Offset)唯一标识。
Producer负责向Kafka主题发布(生产)消息,一个Topic可以有多个Producer实例,其相互之间没有协作关系。Producer的send()方法用于发送消息,参数ProducerRecord封装了消息的内容:Topic、Partition、key、value等信息。如果发生成功,返回的RecordMetadata中记录了消息的偏移量,如果发送失败就会重试或者抛出异常,如下图所示。
在这里插入图片描述
Producer可以以同步和异步的方式发送消息
Producer以Batch的方式推送数据可以极大地提高处理效率,Kafka Producer可以将消息在内存中累计到一定数量后作为一个Batch发送请求。Batch的数量大小可以通过Producer的参数控制,参数值可以设置为累计的数据大小。通过增加Batch的大小,可以减少网络请求和磁盘IO的次数,当然具体参数设置需要在效率和时效性方面做一个权衡
Consumer 负责订阅(消费)主题并处理消息。Consumer 负责维护到Broker的 TCP 连接以便获取数据,在一个 Partition 中每一个记录的 Offset 是该记录的唯一标识,即每一个 Offset 唯一标识当前 Partition 中的一条记录,同时 Offset 也可以标识 Consumer 在 Partition中的位置(Position)。对 Consumer 来讲,这个位置有两种含义:Current Offset 和 Committed Offset。
1.Current Offset保存在Consumer客户端中,它表示Consumer希望收到的下一条消息的序号。它仅仅在poll()方法中使用,例如,Consumer第一次调用poll()方法后收到了20条消息(offset:0-19),那么Current Offset就被设置为20。这样Consumer下一次调用poll()方法时,Kafka就知道应该从序号为20的消息开始读取。这样就能够保证每次Consumer poll消息时,都能够收到不重复的消息。
2.Committed Offset保存在Broker上,它表示Consumer已经确认消费过的消息的序号,主要通过commitSync和commitAsync API来操作。
例如,如果Committed Offset为0,Consumer通过poll()方法收到20条消息后,此时Current Offset就是20,经过一系列的逻辑处理后,并没有调用consumer.commitAsync()或consumer.commitSync()来提交Committed Offset,那么此时Committed Offset依旧是0,下一次Consumer重启后调用poll()继续从0开始消费。
又如,如果一个Consumer消费了5条消息(poll并且成功commitSync)之后宕机了,重新启动之后它仍然能够从第6条消息开始消费,因为Committed Offset已经被Kafka记录为5。
可以将多个Consumer设置为同一个Consumer Group,组内的所有Consumer协调在一起来消费订阅主题的所有分区。但是每个分区只能由一个消费组内的一个Consumer来消费。很明显,Consumer Group的作用是为了实现多个Consumer并行消费一个Topic。

分区策略

一个Topic包含多个Partition,Topic是逻辑概念,而Partition则是物理上的概念。Topic与Partition的关系如下图所示。
在这里插入图片描述
Partition将Topic进行分割,从而更好的将数据均匀分布在Kafka集群的每个Broker中。在server.properties配置文件中可以指定一个全局的分区数设置,这是对每个主题下的分区数的默认设置,默认为1(num.partitions=1)。当然每个主题也可以自己设置分区数量,如果创建主题的时候没有指定分区数量,则会使用server.properties中的设置。例如:

$ bin/kafka-topics.sh --zookeeper localhost:2181
--create
--topic my-topic
--partitions 2
--replication-factor 1

Kafka通过一些算法尽可能将Partition分配到不同的Broker上,这样就带来两个问题:
1.生产数据时,生产者数据发往哪个分区?
如果给定了分区号,直接将数据发往指定的分区。
如果没有给定分区号,视消息的key值,通过key值取hashcode进行分区。
如果既没有给定分区号,也没有key值,则直接轮询分区。
最后,还可以指定自定义分区类。
(1)当分区按每条消息来到顺序轮询依次写入各分区时:kafka会把收到的message进行load balance,均匀的分布在这个topic下的不同的partition上(hash(message)%[broker数量])。
(2)每个partition在内存中对应一个index,记录每个segment中的第一条消息偏移。
segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀“.index”和“.log”分别表示segment索引文件、数据文件。
segment文件命名规则:partition全局的第一个segment从0开始,后续每个segment文件名为上一个全局partition的最大offset(偏移message数)。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
(3)一个partition只能被一个消费者消费(但一个消费者可以同时消费多个partition),因此,如果设置的partition的数量小于consumer的数量,就会有消费者消费不到数据。所以,推荐partition的数量一定要大于同时运行的consumer数量。
2.消费数据时,消费者消费哪个分区的数据?如图
在这里插入图片描述
记住,同一时刻,一个分区只能被消费组中的一个消费者消费。如果消费者组中的消费者大于等于分区数量,则有一些消费者是多余的。但如果消费族中的消费者小于分区数量,则一个消费者将负责多个分区的消费,此时由Consumer的partition.assignment.strategy配置参数决定每个消费者可以消费哪些分区,可选策略包括:range(默认)、roundrobin。
(1)range策略
range策略对应的实现类是org.apache.kafka.clients.consumer.RangeAssignor。具体规则是分区顺序排序,消费者按照字母排序。partition的个数除以消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。
例如:假设有3个消费者11个分区。
C1-0将消费0,1,2,3分区。
C1-2将消费4,5,6,7分区。
C1-3将消费8,9,10分区。
(2)roundrobin策略
roundrobin分配策略的具体实现是org.apache.kafka.clients.consumer.RoundRobinAssignor。

props.put("partition.assignment.strategy","org.apache.kafka.clients.consumer.RoundRobunAssignor");

分区按照hashcode排序,消费者按照字母排序。
例如:假设有3个消费者11个分区。
C1-0将消费0,3,6,9分区。
C1-2将消费1,4,7,10分区。
C1-3将消费2,5,8分区。

副本策略

Kafka是有若干主题,每个主题可进一步划分为若干分区。每个分区配置有若干个副本(Replicas),副本数量不能多于Broker数量。
副本分散保存在不同的Broker上,从而能够解决因为部分Broker宕机带来的数据不可用问题。如下所示,有3台Broker的Kafka集群上的副本分布情况。

在这里插入图片描述
分区的所有副本保存有相同的消息序列,所有副本可分为两类:Leader和Follower。
Leader副本负责所有的读写请求,Follower不对外提供读写服务。当Leader所在Broker宕机时,Kafka依赖Zookeeper感知该故障,并立即开启新一轮的Leader选举,从所有Follower中选择新的Leader副本。当老的Leader重启后,则变成Follower。工作原理如图:
在这里插入图片描述

Zookeeper与Kafka

Zookeeper在Kafka中的做哟个如下图:
在这里插入图片描述
可以看到,在 Kafka 中,ZooKeeper 主要完成如下工作: 1.Broker 注册并监控状态 znode:/brokers/ids,保存了所有 Broker id,实现对 Broker 的动态监控。
2.Topic 注册 znode:/brokers/topics,保存了所有 Topic。
3.生产者负载均衡 由于同一个 Topic 消息会被分区并将其分布在多个 Broker 上,因此,生产者 需要将消息合理地发送到这些分布式的 Broker 上,那么如何实现生产者的负载 均衡,Kafka 支持传统的四层负载均衡,也支持 ZooKeeper 方式实现负载均衡。
(1) 四层负载均衡,根据生产者的 IP 地址和端口来为其确定一个相关联 的 Broker。通常,一个生产者只会对应单个 Broker,然后该生产者产生的消息都 发往该 Broker。这种方式逻辑简单,每个生产者不需要同其他系统建立额外的 TCP 连接,只需要和 Broker 维护单个 TCP 连接即可。但是,其无法做到真正的 负载均衡,因为实际系统中的每个生产者产生的消息量及每个 Broker 的消息存 储量都是不一样的,如果有些生产者产生的消息远多于其他生产者的话,那么会 导致不同的 Broker 接收到的消息总数差异巨大,同时,生产者也无法实时感知 到 Broker 的新增和删除。
(1) 四层负载均衡,根据生产者的 IP 地址和端口来为其确定一个相关联 的 Broker。通常,一个生产者只会对应单个 Broker,然后该生产者产生的消息都 发往该 Broker。这种方式逻辑简单,每个生产者不需要同其他系统建立额外的 TCP 连接,只需要和 Broker 维护单个 TCP 连接即可。但是,其无法做到真正的 负载均衡,因为实际系统中的每个生产者产生的消息量及每个 Broker 的消息存 储量都是不一样的,如果有些生产者产生的消息远多于其他生产者的话,那么会 导致不同的 Broker 接收到的消息总数差异巨大,同时,生产者也无法实时感知 到 Broker 的新增和删除。
4.offset 维护 Kafka 早期版本使用 ZooKeeper 为每个消费者存储 offset,由于 ZooKeeper 写入性能较差,从 0.10 版本后,Kafka 使用自己的内部主题维护 offset。
另外 ,早期版本的 kafka 用 ZooKeeper 做 meta 信息存储、Consumer 的消费状态、group 的管理以及 offset 的值。考虑到 ZooKeeper 本身的一些因素以及 整个架构较大概率存在单点问题,新版本中确实逐渐弱化了 ZooKeeper 的作用。 新的 Consumer 使用了 Kafka 内部的 Group Coordination 协议,也减少了对 ZooKeeper 的依赖。

副本同步

Kafka 引入了 In-sync Replicas,也就是 ISR 副本集合。ISR 中的副本都是 与 Leader 同步的副本,相反,不在 ISR 中的追随者副本就被认为是与 Leader 不同步的。ISR 不只是追随者副本集合,它必然包括 Leader 副本。甚至在某些 情况下,ISR 只有 Leader 这一个副本。设置 ISR 主要是为了 Broker 宕掉之后, 重新选举 Partition 的 Leader 时从 ISR 列表中选择,也就是说当 Leader 副本发生 故障时,只有在 ISR 集合中的 Follower 副本才有资格被选举为新的 Leader。
ISR 是 一 个 动 态 调 整 的 集 合 , 而 非 静 态 不 变 的 。 通 过 Broker 端 replica.lag.time.max.ms 参数(Follower 副本能够落后 Leader 副本的最长时间间 隔,默认值 10000)值来控制哪个追随者副本与 Leader 同步。只要一个 Follower 副本落后Leader副本的时间不连续超过10秒,那么 Kafka 就认为该Follower 副 本与 Leader 是同步的,即使此时 Follower 副本中保存的消息明显少于 Leader 副 本中的消息。如下图所示。
在这里插入图片描述
上图中,Follower1 与 Follower2 中的消息条数明显少于 Leader,但并不一 定与 Leader 不同步。Follower 副本唯一的工作就是不断地从 Leader 副本拉取 消息,然后写入到自己的提交日志中。如果这个同步过程的速度持续慢于 Leader副本的消息写入速度,那么在 replica.lag.time.max.ms 时间后,此 Follower 副本就会被认为是与 Leader 副本不同步的,因此不能再放入 ISR 中。此时, Kafka 会自动收缩 ISR 集合,将该副本“踢出”ISR。
值得注意的是,倘若该副本后面慢慢地追上了 Leader 的进度,那么它是能 够重新被加回 ISR 的。
下图中的绿色箭头描述了 Kafka 数据流副本同步情况,其中红色分区为 Leader。
在这里插入图片描述

容灾

Kafka 容灾指当 Broker 宕机时的恢复机制,在 Kafka 集群中会有一个或者多 个 Broker,其中有一个 Broker 会被选举为控制器(Kafka Controller),它负责管 理整个集群中所有分区和副本的状态。
当某个分区的 Leader 副本出现故障时,由控制器负责为该分区选举新的 Leader 副本。  当检测到某个分区的 ISR 集合发生变化时,由控制器负责通知所有 Broker 更新其元数据信息。  当使用 kafka-topics.sh 脚本为某个 topic 增加分区数量时,同样还是由控 制器负责分区的重新分配。
Kafka中的控制器选举的工作依赖于ZooKeeper,成功竞选为控制器的Broker 会在 ZooKeeper 中创建/controller 这个临时(EPHEMERAL)节点,此临时节点 的内容参考如下: {“version”:1,“brokerid”:0,“timestamp”:“1561214469054”} brokerid 表示称为控制器的 Broker 的 id 编号,timestamp 表示竞选称为控制 器时的时间戳。 Broker 容灾流程如下:
1.Controller 在 ZooKeeper 的 /brokers/ids/[brokerId] 节点注册 Watcher,当 Broker 宕机时 ZooKeeper 会监听到。
2.Controller 从/brokers/ids 节点读取可用 Broker。 3.Controller 决定 set_p,该集合包含宕机 Broker 上的所有 Partition。
4.对set_p中的每一个Partition:
(1)从/brokers/topics/[topic]/partitions/[partition]/state 节点读取 ISR;
(2)决定新 Leader;
(3)将新 Leader、ISR、controller_epoch 和 leader_epoch 等信息写入 state 节点。
5.通过 RPC 向相关 Broker 发送 leaderAndISRRequest 命令。

高并发

Kafka 在保存数据时采用顺序写入,仅仅将数据追加到文件的末尾,不是在 文件的随机位置来修改数据。
另外,为了保证数据写入性能,首先 Kafka 是基于操作系统的页缓存来实现 文件写入的。操作系统本身有一层缓存,叫做 Page Cache,是在内存里的缓存, 也可以称之为 OS Cache,意思就是操作系统自己管理的缓存。在写入磁盘文件 的时候,可以直接写入这个 OS Cache 里,也就是仅仅写入内存中,接下来由操 作系统自己决定什么时候把 OS Cache 里的数据真的刷入磁盘文件中。仅仅这一 个步骤,就可以将磁盘文件写性能提升很多了,因为其实这里相当于是在写内存, 不是在写磁盘,如下图所示。
在这里插入图片描述
在消费数据时,实际上就是要从 kafka 的磁盘文件里读取某条数据然后发送 给下游的消费者。Kafak 在读数据时引入零拷贝技术,也就是说,直接让操作系 统的 Cache 中的数据发送到网卡后传输给下游的消费者,中间跳过了两次拷贝数据(OS Cache->应用进程缓存->Socket 缓存->网卡)的步骤,Socket 缓存中仅仅会拷贝一个描述符过去,不会拷贝数据到 Socket 缓存。如下图所示。
基于上述过程,Kafka集群可以做到每秒几十万、上百万的超高并发性能。此外,Kafka消费组可以使多个服务器配置在一个消费组中,保证所有消费者拉取的数据不会重复并且是完整的,也大大提高了消费者的执行效率。

负载均衡

在创建一个Topic时,Kafka尽量将Partition均分在所有的Broker上,并且将Replicas也均分在不同的Broker上,这点如前面分区策略、副本策略中所述。
另外关于Leader的负载均衡也需要注意,当一个Broker停止时,所有本来将它作为Leader的分区将会把Leader转移到其他Broker上去,极端情况下,会导致同一个Leader管理多个分区,导致负载不均衡,同时当这个Broker重启时,如果这个Broker不再是任何分区的Leader,Kafka的Client也不会从这个Broker来读取消息,从而导致资源浪费。
Kafka中有一个被称为优先副本的概念。如果一个分区有3个副本,且这3个副本的优先级分别为0,1,2,根据优先副本的概念,0会作为Leader。当0节点的Broker挂掉时,会启动1这个节点Broker当作Leader。当0节点的Broker再次启动后,会自动恢复为此Partition的Leader。不会导致负载不均衡和资源浪费,这就是Leader的均衡机制。可在配置文件conf/server.properties中配置开启(默认就是开启):

auto.leader.rebalance.enable = true

例如,某个Topic详情如下:

./kafka-topics.sh --zookeeper 127.0.0.1:2181 --describe --topic logdata-es
Topic:logdata-es PartitionCount:2 ReplicationFactor:2 Config:
Topic:logdata-es Partition:0 Leader:0 Replicas:0,1 lsr:0,1
Topic:logdata-es Partition:1 Leader:1 Reclicas:1,0 lst:0,1

Topic logdata-es中的Partition 0 的Replicas为[0,1],则0为Preferred Replica。

Kafka API应用

Kafka包括常用API包括:
Producer API(Kafka Java Clients):允许应用程序将记录流发布到一个或多个Kafka主题
Consumer API(Kafka Java Clients):运训应用程序订阅一个或多个主题
Streams API(Kafka Streams组件):允许应用程序充当流处理器,使用一个或多个主题的输入流,生成一个或多个输出主题的输出流,有效的将输入流转换为输出流
Connector API(Kafka Connect 组件):允许构建和运行可重用的生产者或消费者,将Kafka主题连接到现有的应用程序或数据系统,如下图:
在这里插入图片描述
其中生产者与消费者API是Kafka基础及核心API。
Confulent平台成员如下图:
在这里插入图片描述
Kafka Java Clients组件的pom依赖:

<dependency>
	<groupId>org.apache.kafka</groupId>
	<artifactId>kafka-clients</artifactId>
	<version>2.3.0</version>
</dependency>

Producer API

Producer是Kafka三大组件(Producer、Consumer、Broker)之一,用于发送消息给kafka集群。

Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.56.100:9092");
        prop.put(ProducerConfig.ACKS_CONFIG,"all");   // 应答机制
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        Producer pro = new KafkaProducer(prop);
        for (int i = 0; i < 10; i++) {
            pro.send(new ProducerRecord<String,String>("mytest","test"+i,i+""));
        }
        pro.close();

消息发送流程:
(1)构造Properties对象,“bootstrap.servers、key.serializer、value.serializer”是必须指定的。
(2)使用Properties构造KafkaProducer对象。
(3)构造ProducerRecord指定Topic、Partition、Key、Value。
(4)使用KafkaProducer的send() 方法发送消息。
(5)关闭KafkaProducer。
1、ProducerRecord用于封装发送给Broker的key-value对。内部数据结构包括:Topic、PartitionID(可选)、Key(可选)、Value。可使用三种构造方法创建其实例:

ProducerRecord(topic,partition,key,value)
ProducerRecord(topic,key,value)
ProducerRecord(topic,value)

2、KafkaProducer实现了Producer接口,主要方法包括:
send() :实现消息发送主逻辑
close() :关闭Producer
metrices() :获取producer的实时监控指标数据,比如发送消息的速率。

配置项:
在这里插入图片描述

Producer可以以同步和异步的方式发送消息,便是通过KafkaProducer相应操作实现。
(1)Fire and Forget(发送后不再理会结果)
这是最基本的使用方式,如下面代码,producer.send() 后不关心发送结果。

producer.send(new ProducerRecord<String,String>("topic1",Interger.toString(i),"dd:"+i));

(2)同步发送,以阻塞方式发送。
通过producer.send(record) 返回Future对象,通过调用Future.get() 进行无限等待结果返回。

// 读
        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.56.100:9092");
        prop.put(ConsumerConfig.GROUP_ID_CONFIG,"mytest13");
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");
        prop.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"1000");
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");   // --from-beginning
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        KafkaConsumer<String,String> cons = new KafkaConsumer<>(prop);
        cons.subscribe(Arrays.asList("mytest"));
        while (true) {
            ConsumerRecords<String, String> records = cons.poll(1000);
            for (ConsumerRecord<String, String> record : records) {
                System.out.println(record.partition() + ":" + record.offset() + ":" + record.key() + ":" +record.value());
            }
        }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值