Kafka消息队列学习笔记3——Kafka高级

1、分区和副本机制

1.1、生产者分区写入策略

生产者写入消息到topic,Kafka将依据不同的策略将数据分配到不同的分区中。

  • 轮询分区策略;
  • 随机分区策略;
  • 按key分区策略;
  • 自定义分区策略。

1.1.1、轮序分区策略

默认的分配策略,也是使用最多的策略,可以最大限度保证所有消息平均分配到一个分区;

如果在生产消息时,key为null,则使用轮序算法均衡地分配分区。

1.1.2、随机分区策略(不用)

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

1.1.3、按key分区策略

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

1.1.4、乱序问题

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

1.1.5、自定义分区策略

实现步骤:

1、创建自定义分区器:

public class KeyWithRandomPartitioner implements Partitioner {
    private Random r;
    
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        return r.nextInt(1000) % cluster.partitionCountForTopic(topic)
    }

    @Override
    public void close() {

    }

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

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

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

1.2、消费者组Rebalance机制

1.2.1、Rebalance再均衡

Kafka中的Rebalance称为再均衡,是Kafka中确保Consumer group下所有的consumer如何达成一致,分配订阅的topic的每个分区的机制。

Rebalance触发的时机有:

  • 消费者组中consumer的个数发生变化。例如:有新的consumer加入到消费者组,或者是某个consumer停止了;
  • 订阅的topic个数发生变化。消费者可以订阅多个主题,假设当前的消费者组订阅的了三个主题但有一个主题突然被删除了,此时也需要发生再均衡。
  • 订阅的分区数量发生变化。

1.2.2、Rebalance的不良影响

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

1.3、消费者分区分配策略

分区分配策略:保障每个消费者尽量能够均衡地消费分区的数据,不能出现某个消费者消费分区的数量特别多,某个消费者消费的分区特别少。

1.3.1、Range范围分配策略

Range范围分配策略就是Kafka默认的分配策略,它可以确保每个消费者消费的分区数量是均衡的。注意:Range范围分配策略是针对每个Topic的。

配置:

- 配置消费者的partition.assignment.strategy为
org.apache.kafka.clients.consumer.RangeAssignor。

算法公式:

n = 分区数量 / 消费者数量
m = 分区数量 % 消费者数量

前m个消费者消费n+1个,剩余消费者消费n个

1.3.2、RoundRobin轮询策略

RoundRobin轮询策略是将消费者组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序(topic和分区的hashcode进行排序),然后通过轮询的方式逐个将分区分配给消费者。

发生rebalance时,轮询分配策略会重新走一遍轮序分配的过程。

1.3.3、Striky粘性分配策略

从Kafka 0.11.x开始,引入此类分配策略,主要目的:

  • 分区分配尽可能均匀;
  • 在发生rebalance的时候,分区的分配尽可能与上一次分配保持相同。

没有发生rebalance时,Striky粘性分配策略和RoundRobin分配策略类似。

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

1.4、副本机制

副本的目的就是冗余备份,当某个Broker上的分区数据丢失时,依然可以保障数据可用,因为在其他的Broker上的副本是可用的。

producer是不断地往Kafka中写入数据,写入数据会有一个返回结果,表示是否写入成功。这里对应有一个ACKs的配置。

1.4.1、producer的ACKs参数

对副本关系较大的就是,producer配置的acks参数了。acks参数表示当生产者生产消息的时候,写入到副本的要求严格程度,它决定了生产者如何在性能和可靠性之间做取舍。

配置:

props.put("bootstrap.servers", "node1.itcast.cn:9092");
props.put("acks", "all");
  • ACKs=0:不等待broker确认,直接发送下一条数据,性能最高,但可能会存在数据丢失的情况。
  • ACKs=1:等待leader副本确认接收后,才会发送下一条数据,性能中等。
  • ACKs=-1 / all:确保消息写入到leader分区,还确保消息写入到对应副本都成功后,接着发送下一条,性能是最差的。

根据业务情况来选择ack机制,是要求性能最高,一部分数据丢失影响不大,可以选择0/1,如果要求数据一定不能丢失,就得配置为-1/all。

分区中是有leader和follower的概念,为了确保消费者消费的数据是一致的,只能从分区leader去读写消息,follower做的事情就是同步数据,Backup。

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

2.1、高级API

高级API:就是直接让Kafka帮助管理、处理分配、数据。

/**
 * 消费者程序
 */
public class KafkaConsumerTest {
    public static void main(String[] args) {
        // 1、创建Kafka消费者配置
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "127.0.0.1:9092");
        // 消费者组(可以使用消费者组,将若干个消费者组织到一起,共同消费Kafka中topic的数据)
        // 每一个消费者需要指定一个消费者组,如果消费者的组名一样,就表明这几个消费者是一个组的
        properties.setProperty("group.id", "test");
        // 自动提交offset
        properties.setProperty("enable.auto.commit", "true");
        // 自动提交offset的时间间隔
        properties.setProperty("auto.commit.interval.ms", "1000");
        // 拉取的key、value数据的反序列化方式
        properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        properties.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        // 2、创建Kafka的消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 3、订阅要消费的主题
        // 指定消费者从哪个topic中拉取数据
        kafkaConsumer.subscribe(Arrays.asList("test-topic"));

        // 4、使用一个while循环,不断从Kafka的topic中拉取消息
        while(true) {
            // Kafka的消费者一次拉取一批数据
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
            // 5、将记录(ConsumerRecords)的offset、key、value都打印出来
            for(ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                // 主题名字
                String topic = consumerRecord.topic();
                // offset
                long offset = consumerRecord.offset();
                // key / value
                String key = consumerRecord.key();
                String value = consumerRecord.value();

                System.out.println("topic:" + topic + " offset:" + offset + " key:" + key + " value" + value);
            }
        }
    }
}

上面是之前编写的代码,消费Kafka的消息很容易实现,写起来比较简单;

  • 不需要执行去管理offset,直接通过ZK管理,也不需要管理分区、副本,由Kafka统一管理;
  • 消费者会自动根据上一次在ZK中保存的offset去接着获取数据;
  • 在ZK中,不同的消费者组(group)同一个topic记录不同的offset,这样不同程序读取同一个topic,不会受offset的影响。

高级API的缺点:

  • 不能控制offset,例如:想从指定的位置读取;
  • 不能细化控制分区、副本、ZK等。

2.2、低级API

低级API:由编写的程序自己控制逻辑。

  • 自己来管理offset,可以将offset存储在ZK、MySQL、Redis、HBase、FLink的状态存储;
  • 指定消费者拉取某个分区的数据;
  • 可以做到细粒度的控制;
  • 原有的Kafka的策略会失效,需要我们自己来实现消费机制。

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

2.3、手动消费分区数据

之前的代码,我们让Kafka根据消费者组中的消费者动态地为topic分配要消费的分区。但在某些时候,我们需要执行要消费的分区,例如:

  • 如果某个程序将某个指定分区的数据offset保存到外部存储中,例如:Redis、MySQL,那么保存数据的时候,只需要消费该指定的分区数据即可;
  • 如果某个程序是高可用的,在程序出现故障时将自动重启(例如:Flink、Spark程序)。这种情况下,程序将从指定的分区重新开始消费数据。

如何进行手动消费分区中的数据呢?

  • 不再使用之前的subscribe方法订阅主题,而是使用【assign】方法指定想要消费的信息;
    String topic = "test";
    TopicPartition partition0 = new TopicPartition(topic, 0);
    TopicPartition partition1 = new TopicPartition(topic, 1);
    consumer.assign(Arrays.asList(partition0, partition1));
    
  • 一旦指定了分区,就可以向前面的示例一样,在循环中调用poll()方法消费消息。

3、监控工具Kafka-eagle介绍

3.1、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/

3.2、安装Kafka-Eagle

3.2.1、开启Kafka JMX端口

1、JMX接口

JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。很多的一些软件都提供了JMX接口,来实现一些管理、监控功能。

2、开启Kafka JMX

在启动Kafka的脚本前,添加:

cd ${KAFKA_HOME}
export JMX_PORT=9988
nohup bin/kafka-server-start.sh config/server.properties &

3.2.2、安装Kafka-Eagle

1、安装JDK,并配置好JAVA_HOME
2、将kafka-eagle上传,并解压到/export/server目录中
    cd /export.software
    tar -xvzf kafka-eagle-bin-1.4.6.tar.gz -C ../server/
    cd /export/server/kafka-eagle-bin-1.4.6/
    tar -xvzf kafka-eagle-web-1.4.6-bin.tar.gz
    cd /export/server/kafka-eagle-bin-1.4.6/kafka-eagle-web-1.4.6
3、配置kafka_eagle环境变量
    vim /etc/profile
        export KE_HOME=/export/server/kafka-eagle-bin-1.4.6/kafka-eagle-web-1.4.6
        export PATH=$PATH:$KE_HOME/bin
    source /etc/profile
4、配置kafka_eagle,使用vi打开conf目录下的system-config.properties
    vim conf/system-config.properties
        # 修改第4行,配置kafka集群别名
        kafka.eagle.zk.cluster.alias=cluster1
        # 修改第5行,配置ZK集群地址
        cluster.zk.list=node1.itcast.cn:2181,node2.itcast.cn:2181,node3.itcast.cn:2181
        # 注释第6行
        # cluster2.zk.list=xdn20:2181,xdn11:2181,xdn12:2181
        # 修改第32行,打开图标统计
        kafka.eagle.metrics.charts=true
        kafka.eagle.metrics.retain=30
        
        # 注释第69行,取消sqlite数据库连接配置
        # kafka.eagle.driver=org.sqlite.JDBC
        # kafka.eagle.url=jdbc:sqlite:/hadoop/kafka-eagle/db/ke.db
        # kafka.eagle.username=root
        # kafka.eagle.password=www.kafka-eagle.org

        # 修改第77行,开启mys
        kafka.eagle.driver=com.mysql.jdbc.Driver
        kafka.eagle.url=jdbc:mysql://node1.itcast.cn:3306/ke?useUnicode=true&
characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
        kafka.eagle.username=root
        kafka.eagle.password=12345
5、配置JAVA_HOME
    cd /export/server/kafka-eagle-bin-1.4.6/kafka-eagle-web-1.4.6/bin
    vim ke.sh
        # 在第24行添加JAVA_HOME环境配置
        export JAVA_HOME=/export/server/jdk1.8.0_241
6、配置Kafka eagle可执行权限
    cd /export/server/kafka-eagle-bin-1.4.6/kafka-eagle-web-1.4.6/bin
    chmod +x ke.sh
7、启动 kafka_eagle
    ./ke.sh start
8、访问Kafka eagle,默认用户为admin,密码为:123456
    http://node1.itcast.cn:8048/ke/

3.3、Kafka度量指标

3.3.1、topic list

点击Topic下的List菜单,就可以展示当前Kafka集群中的所有topic。

指标 意义
Brokers Spread broker使用率
Brokers Skew 分区是否倾斜
Brokers Leader Skew leader partition 是否存在倾斜

4、Kafka原理

4.1、分区的leader与follower

4.1.1、Leader与Follower

Kafka中leader和follower是相对分区有意义,不是相对broker。

在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对应分区的数据,并保存到日志数据文件中。

注意和Zookeeper的区分:

  • ZK的leader负责读、写,follower可以读取;
  • Kafka的leader负责读写,follower不能读写数据(确保每个消费者消费的数据是一致的),Kafka一个topic有多个分区leader,一样可以实现数据操作的负载均衡。

4.1.2、查看某个partition的leader

使用Kafka-eagle查看某个topic的partition的leader在哪个服务器中。

4.1.3、AR、ISR、OSR

在实际环境中,leader有可能会出现一些故障,所以Kafka一定会选举出新的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集合为空;

4.1.4、查看分区的ISR

使用Kafka Eagle查看某个Topic的partition的ISR有哪几个节点;尝试关闭id为0的broker(杀掉该broker的进程),参看topic的ISR情况。

4.1.5、Leader选举

Controller:是Kafka集群的老大,是针对Broker的一个角色。Controller是高可用的,通过ZK来进行选举。

Leader:是针对partition的一个角色。Leader是通过ISR来进行快速选举。

leader对于消息的写入以及读取是非常关键的,此时有两个疑问:

  • Kafka如何确定某个partition是leader,哪个partition是follower呢?
  • 某个leader崩溃了,如何快速确定另外一个leader呢?因为Kafka的吞吐量很高,所以选取leader必须非常快。

1、如果leader崩溃,Kafka会如何?

使用Kafka Eagle找到某个partition的leader,再找到leader所在的broker。在Linux中强制杀掉该Kafka的进程,然后观察leader的情况。

通过观察,我们发现,leader在崩溃后,Kafka又从其他的follower中快速选举出来了leader。

2、Controller介绍

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

3、Controller的选举

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

4、找到当前Kafka集群的controller

  • 点击Kafka Tools的【Tools】菜单,找到【ZooKeeper Browser】;
  • 点击左侧树形结构的controller节点,就可以查看到哪个broker是controller了。

5、测试controller选举

通过Kafka tools找到controller所在的broker对应的kafka进程,杀掉该进程,重新打开ZooKeeper browser,观察kafka是否能够选举出来新的Controller。

6、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宕机,就会出现很多的partition都需要重新选举leader;
  • 如果使用zookeeper选举leader,会给zookeeper带来巨大压力,所以,Kafka中leader的选举不能使用ZK来实现。

4.1.6、leader负载均衡

1、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

2、确保leader在broker中负载均衡

杀掉test主题的某个broker,这样kafka会重新分配leader。等到Kafka重新分配leader之后,再次启动Kafka进程。此时,观察test主题各个分区leader的分配情况。

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

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

--partition:指定需要重新分配leader的partition编号。

4.2、Kafka生产、消费数据工作流程

4.2.1、Kafka数据写入流程

  1. 生产者先从zookeeper的"/brokers/topics/主题名/partition/分区名/state"节点找到该partition的leader,根据id找到对应的broker;
  2. broker进程上的leader将消息写入到本地log中;
  3. follower从leader上拉取消息,写入到本地log,并向leader发送ACK;
  4. leader接收到所有的ISR中的Replica的ACK后,并向生产者返回ACK。

4.2.2、Kafka数据消费流程

1、两种消费模式

  • Kafka采用拉取模型,由消费者自己记录消费状态,每个消费者相互独立地顺序拉取每个分区的消息;
  • 消费者可以按照任意的顺序消费消息。比如:消费者可以重置到旧的偏移量,重新处理之前已经消费过的消息;或者直接跳到最近的位置,从当前时刻开始消费。

2、Kafka消费者拉取数据流程

  1. 每个consumer都可以根据分配策略(默认RangeAssignor),获得要消费的分区;
  2. 获取到consumer对应的offset(默认从ZK中获取上一次消费的offset);
  3. 找到该分区的leader,开始从offset往后顺序拉取数据;
  4. 消费者提交offset(自动提交——每隔多少秒提交一次offset、手动提交——放入到事务中提交)。

4.3、Kafka的数据存储形式

文件存储机制:

  • 一个topic由多个分区组成;
  • 一个分区(partition)由多个segment(段)组成;
  • 一个segment(段)由多个文件组成(log、index、timeindex)。

4.3.1、存储日志

接接下来,我们来看一下Kafka中的数据到底是如何在磁盘中存储的。

  • Kafka中的数据是保存在/export/server/kafka_2.12-2.4.1/data中;
  • 消息是保存在以:【主题名-分区ID】的文件夹中的;
  • 数据文件夹中包含:xx.index、xx.log、xx.timeindex、leader-epoch-checkpoint,这些分别对应:
    文件名 说明
    xx.index 索引文件(稀疏索引),根据offset查找数据就是通过该索引文件来操作的
    xx.log 日志数据文件
    xx.timeindex 时间索引
    leader-epoch-checkpoint 持久化每个partition leader对应的LEO(Log end offset、日志文件中下一条待写入消息的offset)
  • 每个日志文件的文件名为起始偏移量,因为每个分区的起始偏移量为0,所以,分区的日志文件都以0000000000000000.log开始;
  • 默认的每个日志文件最大为【log.segment.bytes = 1024 * 1024 * 1024】 1G;
  • 为了简化根据offset查找消息,Kafka日志文件名设计为开始的偏移量。

1、观察测试

为了方便测试观察,新创建一个topic:【test_10m】,该topic每个日志数据文件最大为10M。

bin/kafka-topics.sh --create --zookeeper node1.itcast.cn --topic test_10m --replication-
factor 2 --partitions 3 --config segment.bytes=10485760

2、写入消息

  • 新的消息总是写入到最后的一个日志文件中;
  • 该文件如果到达指定的大小(默认为:1GB)时,将滚动到一个新的文件中。

3、读取消息

  1. 根据【offset】首先需要找到存储数据的segment段(注意:offset指定分区的全局偏移量);
  2. 然后根据这个【全局分区offset】找到相对于文件的【segment段offset】。

  3. 最后再根据【segment段offset】读取消息;
  4. 为了提高查询效率,每个文件都会维护对应的范围内存,查找的时候就是使用简单的二分查找。

4、删除消息

  • 在Kafka中,消息是会被定期清理的。一次删除一个segment段的日志文件;
  • Kafka的日志管理器,会根据Kafka的配置,来决定哪些文件可以被删除。

4.4、消息不丢失机制

4.4.1、broker数据不丢失

生产者通过分区的leader写入数据后,所有在ISR中follower都会从leader中复制数据,这样,可以确保即使leader崩溃了,其他的follower的数据仍然是可用的。

4.4.2、生产者数据不丢失

生产者连接leader写入数据时,可以通过ACK机制来确保数据已经成功写入。ACK机制有三个可选配置:

  • 配置ACK响应要求为-1时——表示所有的节点都收到数据(leader和follower都接收到数据);
  • 配置ACK响应要求为1时——表示leader收到数据;
  • 配置ACK响应要求为0时——生产者只负责发送数据,不关心数据是否丢失(这种情况可能会产生数据丢失,但性能是最好的)。

4.4.3、消费者数据不丢失

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

消息传递的语义性:

  • A-most once:最多一次(只管把数据消费到,不管有没有成功,可能会有数据丢失);
  • At-least once:最少一次(有可能会出现重复消费);
  • Exactly-Once:仅有一次(事务性的保障,保证消息有且仅被处理一次)。

Flink里面有对应的每种不同机制的保证,提供Exactly-Once保障(二阶段事务提交方式)。

4.5、数据积压

Kafka消费者消费数据的速度是非常快的,但如果由于处理Kafka消息时,由于有一些外部IO,或者是产生网络拥堵,就会造成Kafka中的数据积压(或称为数据堆积)。如果数据一致积压,会导致数据出来的实时性受到较大影响。

4.5.1、使用Kafka-Eagle查看数据积压情况

4.5.2、解决数据积压问题

当Kafka出现数据积压问题时,首先要找到数据积压的原因。以下是在企业中出现数据积压的几类场景。

1、数据写入MySQL失败

问题描述:某日运维人员找到开发人员,说某个topic的一个分区发生数据积压,这个topic非常重要,而且开始有用户投诉。运维非常紧张,赶紧重启了这台机器。重启之后,还是无济于事。

问题分析:消费这个topic的代码比较简单,主要就是消费topic数据,然后进行判断再进行数据库操作。运维通过Kafka-Eagle找到积压的topic,发现该topic的某个分区积压了几十万条的消息。最后,通过查看日志发现,由于数据写入到MySQL中报错,导致消费分区的offset一直没有提交,所以数据积压严重。

2、因为网络延迟消费失败

问题描述:基于Kafka开发的系统平稳运行了两个月,突然某天发现某个topic中的消息出现数据积压,大概有几万条消息没有被消费。

问题分析:通过查看应用程序日志发现,有大量的消费超时失败。后查明原因,因为当天网络抖动,通过查看Kafka的消费者超时配置为50ms,随后,将消费的时间修改为500ms后问题解决。

5、Kafka中数据清理(Log Deletion)

Kafka的消息存储在磁盘中,为了控制磁盘占用空间,Kafka需要不断地对过去的一些消息进行清理工作。Kafka的每个分区都有很多的日志文件,这样也是为了方便进行日志的清理。在Kafka中,提供两种日志清理方式:

  • 日志删除(Log Deletion):按照指定的策略直接删除不符合条件的日志;
  • 日志压缩(Log Compaction):按照消息的key进行整合,有相同key的但有不同value值,只保留最后一个版本。

在Kafka的broker或topic配置中;

配置项 配置值 说明
log.cleaner.enable true(默认) 开启自动清理日志功能
log.cleanup.policy delete(默认) 删除日志
log.cleanup.policy compaction 压缩日志
log.cleanup.policy delete.compact 同时支持删除、压缩

5.1、日志删除

日志删除是以段(segment日志)为单位来进行定期清理的。

5.1.1、定时日志删除任务

Kafka日志管理器中会有一个专门的日志删除任务来定期检测和删除不符合保留条件的日志分段文件,这个周期可以通过broker段参数log.retention.check.interval.ms来配置,默认值为300,000,即5分钟。当前日志分段的保留策略有三种:

  • 基于时间的保留策略;
  • 基于日志大小的保留策略;
  • 基于日志起始偏移量的保留策略。

5.1.2、基于时间的保留策略

以下三种配置可以指定如果Kafka中的消息超过指定的阈值,就会将日志进行自动清理:

  • log.retention.hours;
  • log.retention.minutes;
  • log.retention.ms。

其中,优先级为log.retention.ms > log.retention.minutes > log.retention.hours。默认情况,在broker中,配置如下:

  • log.retention.hours=168

也就是,默认日志的保留时间为168小时,相当于保留7天。

删除日志分段时:

  • 从日志文件对象中所维护日志分段的跳跃表中移除待删除的日志分段,以保证没有线程对这些日志分段进行读取操作;
  • 将日志分段文件添上".deleted"的后缀(也包括日志分段对应的索引文件);
  • Kafka的后台定时任务会定期删除这些".deleted"为后缀的文件,这个任务的延迟执行时间可以通过file.delete.delay.ms参数来设置,默认值为60000,即1分钟。

设置topic 5秒删除一次:

为了方便观察,设置段文件的大小为1M;尝试往topic中添加一些数据,观察日志的删除情况,我们发现,日志会定期被标记为删除,然后被删除。

猜你喜欢

转载自blog.csdn.net/weixin_44623055/article/details/125263759