Kafka基础(二)

本文详细介绍了Kafka的生产者和消费者的工作原理,包括数据分发策略、幂等性和事务特性。同时,探讨了Kafka的日志存储机制,如Log的组织结构、索引原理以及清理策略。此外,还讨论了Kafka如何保证消息不丢失,涉及生产者和消费者的配置以及ACK机制。最后,提到了消费者组的Rebalance机制和不同分区分配策略。
摘要由CSDN通过智能技术生成

接上篇

1、kafka原理

生产者

生产者是一个向kafka Cluster发布记录的客户端;生产者是线程安全的,跨线程共享单个生产者实例通常比具有多个实例更快。

必要条件

生产者要进行生产数据到kafka Cluster中,必要条件有以下三个:

#1、地址
bootstrap.servers=node01:9092
#2、序列化 key.serializer=org.apache.kafka.common.serialization.StringSerializer value.serializer=org.apache.kafka.common.serialization.StringSerializer
#3、主题(topic) 需要制定具体的某个topic(order)即可。

流程描述

Producer连接任意活着的Broker,请求指定Topic,Partion的Leader元数据信息,然后直接与对应的Broker直接连接,发布数据

开放分区接口(生产者数据分发策略)

2.1、用户可以指定分区函数,使得消息可以根据key,发送到指定的Partition中。
2.2、kafka在数据生产的时候,有一个数据分发策略。默认的情况使用DefaultPartitioner.class类。 这个类中就定义数据分发的策略。
2.3、如果是用户制定了partition,生产就不会调用DefaultPartitioner.partition()方法
2.4、当用户指定key,使用hash算法。如果key一直不变,同一个key算出来的hash值是个固定值。如果是固定 值,这种hash取模就没有意义。

Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions

2.5、 当用既没有指定partition也没有key。

/**
The default partitioning strategy:
<ul>
<li>If a partition is specified in the record, use it
<li>If no partition is specified but a key is present choose a partition based on a hash of the key
<li>If no partition or key is present choose a partition in a round-robin fashion
*/

2.6、数据分发策略的时候,可以指定数据发往哪个partition。当ProducerRecord 的构造参数中有partition的时 候,就可以发送到对应partition上。
生产者数据分发策略有如下四种:(总的来说就是调用了一个方法,参数不同而已)

//可根据主题和内容发送
public ProducerRecord(String topic, V value)
//根据主题,key、内容发送
public ProducerRecord(String topic, K key, V value)
//根据主题、分区、key、内容发送
public ProducerRecord(String topic, Integer partition, K key, V value)
//根据主题、分区、时间戳、key,内容发送
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value)

消费者

消费者是一个从kafka Cluster中消费数据的一个客户端;该客户端可以处理kafka brokers中的故障问题,并且可以适应在集群内的迁移的topic分区;该客户端还允许消费者组使用消费者组来进行负载均衡。

消费者维持一个TCP的长连接来获取数据,使用后未能正常关闭这些消费者问题会出现,因此消费者不是线程安全 的。

必要条件

消费者要从kafka Cluster进行消费数据,必要条件有以下四个

#1、地址
bootstrap.servers=node01:9092
#2、序列化 key.serializer=org.apache.kafka.common.serialization.StringSerializer value.serializer=org.apache.kafka.common.serialization.StringSerializer
#3、主题(topic) 需要制定具体的某个topic(order)即可。
#4、消费者组 group.id=test

2、kafka的log-存储机制

kafka中log日志目录及组成
kafka在我们指定的log.dir目录下,会创建一些文件夹;名字是【主题名字-分区名】所组成的文件夹。 在【主题名字-分区名】的目录下,会有两个文件存在,如下所示:

#索引文件
00000000000000000000.index
#日志内容
00000000000000000000.log

在kafka的设计中,将offset值作为了文件名的一部分
比如:topic的名字为:test,有三个分区,生成的目录如下如下所示:

test-0
test-1
test-2

kafka日志的组成
segment file组成:由两个部分组成,分别为index file和data file,此两个文件一一对应且成对出现; 后缀.index和.log分别表示为segment的索引文件、数据文件。

segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个全局 partion的最大offset(偏移message数)。数值最大为64位long大小,19位数字字符长度,没有数字就用0 填充。
通过索引信息可以快速定位到message。通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作;
通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。 稀疏索引:为了数据创建索引,但范围并不是为每一条创建,而是为某一个区间创建;
好处:就是可以减少索引值的数量。
不好的地方:找到索引区间之后,要得进行第二次处理。

kafka的offset查找过程

在这里插入图片描述
比如:要查找绝对offset为7的Message:
上图的左半部分是索引文件,里面存储的是一对一对的key-value,其中key是消息在数据文件(对应的log文件)中的编号,比如“1,3,6,8……”,
分别表示在log文件中的第1条消息、第3条消息、第6条消息、第8条消息……,那么为什么在index文件中这些编号不是连续的呢?
这是因为index文件中并没有为数据文件中的每条消息都建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。
这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。
但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。

其中以索引文件中元数据3,4597为例,其中3代表在右边log数据文件中从上到下第3个消息(在全局partiton表示第4597个消息),
其中4597表示该消息的物理偏移地址(位置)为4597。

kafka Message的物理结构及介绍

在这里插入图片描述

kafka中log CleanUp

kafka中清理日志的方式有两种:delete和compact。
删除的阈值有两种:过期的时间和分区内总日志大小。
在kafka中,因为数据是存储在本地磁盘中,并没有像hdfs的那样的分布式存储,就会产生磁盘空间不足的情 况,可以采用删除或者合并的方式来进行处理
可以通过时间来删除、合并:默认7天 还可以通过字节大小、合并

log.cleanup.policy
log.retention.hours
log.retention.minutes
log.retention.ms

kafka消息不丢失制

生产者生产数据不丢失

可以采用同步或者异步的方式
同步:发送一批数据给kafka后,等待kafka返回结果
1、生产者等待10s,如果broker没有给出ack相应,就认为失败。
2、生产者重试3次,如果还没有相应,就报错

异步:发送一批数据给kafka,只是提供一个回调函数。
1、先将数据保存在生产者端的buffer中。buffer大小是2万条
2、满足数据阈值或者数量阈值其中的一个条件就可以发送数据。
3、发送一批数据的大小是500条
说明:如果broker迟迟不给ack,而buffer又满了,开发者可以设置是否直接清空buffer中的数据。

ack机制(确认机制)

生产者数据,需要服务端返回一个确认码,即ack响应码;ack的响应有三个状态值

0:生产者只负责发送数据,不关心数据是否丢失,响应的状态码为0(丢失的数据,需要再次发送 )
1:partition的leader收到数据,响应的状态码为1
-1:所有的从节点都收到数据,响应的状态码为-1

kafka的broker中数据不丢失

在broker中,保证数据不丢失主要是通过副本因子(冗余),防止数据丢失

消费者消费数据不丢失

在消费者消费数据的时候,只要每个消费者记录好offset值即可,就能保证数据不丢失。

分布式系统当中的CAP理论

分布式系统(distributed system)正变得越来越重要,大型网站几乎都是分布式的。
分布式系统的最大难点,就是各个节点的状态如何同步。
为了解决各个节点之间的状态同步问题,在1998年,由加州大学的计算机科学家 Eric Brewer 提出分布式系统的三个指标,分别是
Consistency:一致性
Availability:可用性
Partition tolerance:分区容错性

Kafka生产者幂等性与事务

幂等性

拿http举例来说,一次或多次请求,得到地响应是一致的(网络超时等问题除外),换句话说,就是执行多次操作与执行一次操作的影响是一样的。

Kafka生产者幂等性

生产者消息重复问题

  • Kafka生产者生产消息到partition,如果直接发送消息,kafka会将消息保存到分区中,但Kafka会返回一个ack给生产者,表示当前操作是否成功,是否已经保存了这条消息。如果ack响应的过程失败了,此时生产者会重试,继续发送没有发送成功的消息,Kafka又会保存一条一模一样的消息

配置幂等性
props.put(“enable.idempotence”,true);

幂等性原理
为了实现生产者的幂等性,Kafka引入了 Producer ID(PID)和 Sequence Number的概念。
PID:每个Producer在初始化时,都会分配一个唯一的PID,这个PID对用户来说,是透明的。
Sequence Number:针对每个生产者(对应PID)发送到指定主题分区的消息都对应一个从0开始递增的Sequence Number。
在这里插入图片描述

Kafka事务

Kafka事务是2017年Kafka 0.11.0.0引入的新特性。类似于数据库的事务。Kafka事务指的是生产者生产消息以及消费者提交offset的操作可以在一个原子操作中,要么都成功,要么都失败。尤其是在生产者、消费者并存时,事务的保障尤其重要。(consumer-transform-producer模式)

事务操作API

Producer接口中定义了以下5个事务相关方法:
1.initTransactions(初始化事务):要使用Kafka事务,必须先进行初始化操作
2.beginTransaction(开始事务):启动一个Kafka事务
3.sendOffsetsToTransaction(提交偏移量):批量地将分区对应的offset发送到事务中,方便后续一块提交
4.commitTransaction(提交事务):提交事务
5.abortTransaction(放弃事务):取消事务

事务编程

开启事务的条件

  • 生产者

    // 开启事务必须要配置事务的ID
    props.put("transactional.id", "dwd_user");
    
  • 消费者

    // 配置事务的隔离级别
    props.put("isolation.level","read_committed");
    // 关闭自动提交,一会我们需要手动来提交offset,通过事务来维护offset
    props.setProperty("enable.auto.commit", "false");
    

分区

生产者分区写入策略

生产者写入消息到topic,Kafka将依据不同的策略将数据分配到不同的分区中
1.轮询分区策略
默认的策略,也是使用最多的策略,可以最大限度保证所有消息平均分配到一个分区
如果在生产消息时,key为null,则使用轮询算法均衡地分配分区

2.随机分区策略
随机策略,每次都随机地将消息分配到每个分区。在较早的版本,默认的分区策略就是随机策略,也是为了将消息均衡地写入到每个分区。但后续轮询策略表现更佳,所以基本上很少会使用随机策略。

3.按key分区分配策略
按key分配策略,有可能会出现「数据倾斜」,例如:某个key包含了大量的数据,因为key值一样,所有所有的数据将都分配到一个分区中,造成该分区的消息数量远大于其他的分区。
4.自定义分区策略

public class KeyWithRandomPartitioner implements Partitioner {

    private Random r;

    @Override
    public void configure(Map<String, ?> configs) {
        r = new Random();
    }

    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // cluster.partitionCountForTopic 表示获取指定topic的分区数量
        return r.nextInt(1000) % cluster.partitionCountForTopic(topic);
    }

    @Override
    public void close() {
    }
}

2.在Kafka生产者配置中,自定使用自定义分区器的类名

props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, KeyWithRandomPartitioner.class.getName());

乱序问题

轮询策略、随机策略都会导致一个问题,生产到Kafka中的数据是乱序存储的。而按key分区可以一定程度上实现数据有序存储——也就是局部有序,但这又可能会导致数据倾斜,所以在实际生产环境中要结合实际情况来做取舍。
在这里插入图片描述

消费者组Rebalance机制

Rebalance再均衡

Kafka中的Rebalance称之为再均衡,是Kafka中确保Consumer group下所有的consumer如何达成一致,分配订阅的topic的每个分区的机制。
Rebalance触发的时机有:
1.消费者组中consumer的个数发生变化。例如:有新的consumer加入到消费者组,或者是某个consumer停止了。
2.订阅的topic个数发生变化
消费者可以订阅多个主题,假设当前的消费者组订阅了三个主题,但有一个主题突然被删除了,此时也需要发生再均衡。
3.订阅的topic分区数发生变化

Rebalance的不良影响

发生Rebalance时,consumer group下的所有consumer都会协调在一起共同参与,Kafka使用分配策略尽可能达到最公平的分配
Rebalance过程会对consumer group产生非常严重的影响,Rebalance的过程中所有的消费者都将停止工作,直到Rebalance完成

消费者分区分配策略

Range范围分配策略

Range范围分配策略是Kafka默认的分配策略,它可以确保每个消费者消费的分区数量是均衡的。
注意:Rangle范围分配策略是针对每个Topic的。
配置
配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RangeAssignor。
算法公式
n = 分区数量 / 消费者数量
m = 分区数量 % 消费者数量
前m个消费者消费n+1个
剩余消费者消费n个

在这里插入图片描述

RoundRobin轮询策略

RoundRobinAssignor轮询策略是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序(topic和分区的hashcode进行排序),然后通过轮询方式逐个将分区以此分配给每个消费者。
配置
配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RoundRobinAssignor。

在这里插入图片描述

Stricky粘性分配策略

从Kafka 0.11.x开始,引入此类分配策略。主要目的:
1.分区分配尽可能均匀
2.在发生rebalance的时候,分区的分配尽可能与上一次分配保持相同

没有发生rebalance时,Striky粘性分配策略和RoundRobin分配策略类似。
在这里插入图片描述
上面如果consumer2崩溃了,此时需要进行rebalance。如果是Range分配和轮询分配都会重新进行分配,例如:
在这里插入图片描述

通过上图,我们发现,consumer0和consumer1原来消费的分区大多发生了改变。接下来我们再来看下粘性分配策略。
在这里插入图片描述

我们发现,Striky粘性分配策略,保留rebalance之前的分配结果。这样,只是将原先consumer2负责的两个分区再均匀分配给consumer0、consumer1。这样可以明显减少系统资源的浪费,例如:之前consumer0、consumer1之前正在消费某几个分区,但由于rebalance发生,导致consumer0、consumer1需要重新消费之前正在处理的分区,导致不必要的系统开销。(例如:某个事务正在进行就必须要取消了)

副本机制

副本的目的就是冗余备份,当某个Broker上的分区数据丢失时,依然可以保障数据可用。因为在其他的Broker上的副本是可用的。
对副本关系较大的就是,producer配置的acks参数了,acks参数表示当生产者生产消息的时候,写入到副本的要求严格程度。它决定了生产者如何在性能和可靠性之间做取舍。

Properties props = new Properties();
props.put("bootstrap.servers", "node1.itcast.cn:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

acks配置为0

在这里插入图片描述

acks配置为1

在这里插入图片描述

acks配置为-1或者all

在这里插入图片描述

高级(High Level)API与低级(Low Level)API

高级API

**
 * 消费者程序:从test主题中消费数据
 */
public class _2ConsumerTest {
    public static void main(String[] args) {
        // 1. 创建Kafka消费者配置
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.88.100:9092");
        props.setProperty("group.id", "test");
        props.setProperty("enable.auto.commit", "true");
        props.setProperty("auto.commit.interval.ms", "1000");
        props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        // 2. 创建Kafka消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        // 3. 订阅要消费的主题
        consumer.subscribe(Arrays.asList("test"));

        // 4. 使用一个while循环,不断从Kafka的topic中拉取消息
        while (true) {
            // 定义100毫秒超时
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
        }
    }
}

上面是之前编写的代码,消费Kafka的消息很容易实现,写起来比较简单
不需要执行去管理offset,直接通过ZK管理;也不需要管理分区、副本,由Kafka统一管理
消费者会自动根据上一次在ZK中保存的offset去接着获取数据
在ZK中,不同的消费者组(group)同一个topic记录不同的offset,这样不同程序读取同一个topic,不会受offset的影响
高级API的缺点
不能控制offset,例如:想从指定的位置读取
不能细化控制分区、副本、ZK等

低级API

通过使用低级API,我们可以自己来控制offset,想从哪儿读,就可以从哪儿读。而且,可以自己控制连接分区,对分区自定义负载均衡。而且,之前offset是自动保存在ZK中,使用低级API,我们可以将offset不一定要使用ZK存储,我们可以自己来存储offset。例如:存储在文件、MySQL、或者内存中。但是低级API,比较复杂,需要执行控制offset,连接到哪个分区,并找到分区的leader。

手动消费分区数据

之前的代码,我们让Kafka根据消费组中的消费者动态地为topic分配要消费的分区。但在某些时候,我们需要指定要消费的分区,例如:
 如果某个程序将某个指定分区的数据保存到外部存储中,例如:Redis、MySQL,那么保存数据的时候,只需要消费该指定的分区数据即可
 如果某个程序是高可用的,在程序出现故障时将自动重启(例如:后面我们将学习的Flink、Spark程序)。这种情况下,程序将从指定的分区重新开始消费数据。

如何进行手动消费分区中的数据呢?
1.不再使用之前的 subscribe 方法订阅主题,而使用 「assign」方法指定想要消费的消息

	String topic = "test";
     TopicPartition partition0 = new TopicPartition(topic, 0);
     TopicPartition partition1 = new TopicPartition(topic, 1);
     consumer.assign(Arrays.asList(partition0, partition1));

2.一旦指定了分区,就可以就像前面的示例一样,在循环中调用「poll」方法消费消息

注意
1.当手动管理消费分区时,即使GroupID是一样的,Kafka的组协调器都将不再起作用
2.如果消费者失败,也将不再自动进行分区重新分配

监控工具Kafka-eagle介绍

在开发工作中,当业务前提不复杂时,可以使用Kafka命令来进行一些集群的管理工作。但如果业务变得复杂,例如:我们需要增加group、topic分区,此时,我们再使用命令行就感觉很不方便,此时,如果使用一个可视化的工具帮助我们完成日常的管理工作,将会大大提高对于Kafka集群管理的效率,而且我们使用工具来监控消费者在Kafka中消费情况。

早期,要监控Kafka集群我们可以使用Kafka Monitor以及Kafka Manager,但随着我们对监控的功能要求、性能要求的提高,这些工具已经无法满足。

Kafka Eagle是一款结合了目前大数据Kafka监控工具的特点,重新研发的一块开源免费的Kafka集群优秀的监控工具。它可以非常方便的监控生产环境中的offset、lag变化、partition分布、owner等。

官网地址:https://www.kafka-eagle.org/
安装Kafka-Eagle

Kafka原理

分区的leader与follower

在Kafka中,每个topic都可以配置多个分区以及多个副本。每个分区都有一个leader以及0个或者多个follower,在创建topic时,Kafka会将每个分区的leader均匀地分配在每个broker上。我们正常使用kafka是感觉不到leader、follower的存在的。但其实,所有的读写操作都是由leader处理,而所有的follower都复制leader的日志数据文件,如果leader出现故障时,follower就会被选举为leader。所以,可以这样说:
Kafka中的leader负责处理读写操作,而follower只负责副本数据的同步
如果leader出现故障,其他follower会被重新选举为leader
follower像一个consumer一样,拉取leader对应分区的数据,并保存到日志数据文件中

AR、ISR、OSR

在实际环境中,leader有可能会出现一些故障,所以Kafka一定会选举出新的leader。在讲解leader选举之前,我们先要明确几个概念。Kafka中,把follower可以按照不同状态分为三类——AR、ISR、OSR。

分区的所有副本称为 「AR」(Assigned Replicas——已分配的副本)
所有与leader副本保持一定程度同步的副本(包括 leader 副本在内)组成 「ISR」(In-Sync Replicas——在同步中的副本)
由于follower副本同步滞后过多的副本(不包括 leader 副本)组成 「OSR」(Out-of-Sync Replias)
AR = ISR + OSR
正常情况下,所有的follower副本都应该与leader副本保持同步,即AR = ISR,OSR集合为空。

Leader选举

leader对于消息的写入以及读取是非常关键的,此时有两个疑问:
1.Kafka如何确定某个partition是leader、哪个partition是follower呢?
2.某个leader崩溃了,如何快速确定另外一个leader呢?因为Kafka的吞吐量很高、延迟很低,所以选举leader必须非常快

Controller介绍

Kafka启动时,会在所有的broker中选择一个controller
前面leader和follower是针对partition,而controller是针对broker的
创建topic、或者添加分区、修改副本数量之类的管理任务都是由controller完成的
Kafka分区leader的选举,也是由controller决定的

Controller的选举

在Kafka集群启动的时候,每个broker都会尝试去ZooKeeper上注册成为Controller(ZK临时节点)
但只有一个竞争成功,其他的broker会注册该节点的监视器
一旦该临时节点状态发生变化,就可以进行相应的处理
Controller也是高可用的,一旦某个broker崩溃,其他的broker会重新注册为Controller

Controller选举partition leader

所有Partition的leader选举都由controller决定
controller会将leader的改变直接通过RPC的方式通知需为此作出响应的Broker
controller读取到当前分区的ISR,只要有一个Replica还幸存,就选择其中一个作为leader否则,则任意选一个Replica作为leader
如果该partition的所有Replica都已经宕机,则新的leader为-1

为什么不能通过ZK的方式来选举partition的leader?
Kafka集群如果业务很多的情况下,会有很多的partition
假设某个broker宕机,就会出现很多的partiton都需要重新选举leader
如果使用zookeeper选举leader,会给zookeeper带来巨大的压力。所以,kafka中leader的选举不能使用ZK来实现

leader负载均衡

Preferred Replica

Kafka中引入了一个叫做「preferred-replica」的概念,意思就是:优先的Replica
在ISR列表中,第一个replica就是preferred-replica
第一个分区存放的broker,肯定就是preferred-replica
执行以下脚本可以将preferred-replica设置为leader,均匀分配每个分区的leader。

./kafka-leader-election.sh --bootstrap-server node1.itcast.cn:9092 --topic 主题 
--partition=1 --election-type preferred

确保leader在broker中负载均衡

会造成leader分配是不均匀的,所以可以执行以下脚本来重新分配leader:

bin/kafka-leader-election.sh --bootstrap-server node1.itcast.cn:9092 --topic test
 --partition=2 --election-type preferred

Docker搭建Kafka

Docker搭建Kafka
下载Kafka和Zookeeper镜像文件

1、docker pull wurstmeister/kafka
2、docker pull wurstmeister/zookeeper
3、docker pull sheepkiller/kafka-manager

先运行zk,再运行kafka


1,启动zk
docker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper
2,启动kafka
docker run --name kafka01 \
-p 9092:9092 \
-e KAFKA_BROKER_ID=0 \
-e KAFKA_ZOOKEEPER_CONNECT=192.168.91.66:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.91.66:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
-d  wurstmeister/kafka  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值