KafKa

一、Kafka 概述

1.Kafka传统定义:Kafka是一个分布式的基于发布/订阅模式消息队列,主要应用于大数据实时处理领域

发布/订阅:消息的发布者不会将消息直接发送给特定的订阅者,而是将发布的消息分为不同的类别,订阅者只接受感兴趣的信息

2.Kafka最新定义:Kafka是一个开源的分布式事件流平台,被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用

3.传统消息队列应用场景缓存/消峰解耦异步通信

1)缓冲/消峰:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况
在这里插入图片描述
2)解耦:允许你独立的扩展或修改两边的处理过程,只要确保他们遵守同样的接口约束
在这里插入图片描述
3)异步通信:允许用户把一个消息放入队列,但不立即处理它,然后在需要的时候再去处理它们
在这里插入图片描述

4.消息队列的两种模式

(1)点对点模式:消费者主动拉取数据,消息收到后清除消息,消息生产者生产消息发送到Queue中,然后消费者从Queue中取出消息并消费,消息消费后,Queue中不会再存储,所以消费者不可能消费到已经被消费的消息,Queue支持存在多个消费者,但是对于一个消息而言,只会有一个消费者可以消费
在这里插入图片描述
(2)发布/订阅模式:消息生产者将消息发布到Topic(主题)中,同时有多个消息消费者(订阅)消费该消息,消费者消费消息后,消息数据不会被删除,其他消费者也可以消费这些消息,每个消费者是独立的,都可以消费到数据
在这里插入图片描述在这里插入图片描述

5.Kafka基础架构

1) 在生产者生产大量信息数据的情况下,单个Kafka服务器存储不下大量数据,这时需要使用Kafka集群将一个Topic(主题)的信息数据分解为多个partition(部分)存储在不同Kafka服务器中

2)在有大量消息数据的情况下,提出消费者组的概念,一个消费者组由多个消费者构成,组内的每个消费者并行消费,每个消费者消费不同分区的信息,即一个分区只能由一个组内的一个消费者消费,消费者组之间不互相影响,即消费者组是逻辑上的一个订阅者

3)Replica:副本,为保证奇群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍能继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower

4)leader / follower : Kafka的副本分为leader和Follower,副本之间是没有差别的,在角色上分为leader 和 follower,在生产和消费消息时,只针对于Leader进行操作,而Follower的工作只是实时同步数据,当leader出现故障挂掉后,follower有条件可以成为leader,进行数据操作

6.KafKa的安装

下载kafka 的压缩包,在linux中解压并配置相关配置信息

7.Shell 脚本 服务器的同步分发

1) 同步分发:使用shell脚本对服务器中的文件同步上传到其他服务器中,也可以同步执行命令
2) 如果想要在任意位置下执行Shell脚本,则需要创建不同的环境变量,root用户的环境变量为 /usr/bin,将脚本复制到此位置下即可执行
3)同步分发脚本

#!/bin/bash

#1. 判断参数个数
if [ $# -lt 1 ]
then
    echo Not Enough Arguement!
    exit;
fi

#2. 遍历集群所有机器
for host in 192.168.114.100 192.168.114.101 192.168.114.102
do
    echo ====================  $host  ====================
    #3. 遍历所有目录,挨个发送

    for file in $@
    do
        #4. 判断文件是否存在
        if [ -e $file ]
            then
                #5. 获取父目录
                pdir=$(cd -P $(dirname $file); pwd)

                #6. 获取当前文件的名称
                fname=$(basename $file)
                ssh $host "mkdir -p $pdir"
                rsync -av $pdir/$fname $host:$pdir
            else
                echo $file does not exists!
        fi
    done
done

4)在普通用户创建脚本:

	1.添加环境变量  /用户/bin
	2.添加脚本文件
	3.测试脚本文件

	注意 环境变量文件不能同步分发 因为没有权限

5)在root用户创建脚本

	1.在  /usr/目录中创建 myShell 文件夹 ,将脚本文件复制到文件夹中
	2.添加环境变量 /usr/myShell/bin 

6)免密登入:查看Hadoop视频 p29 目前只配置了root用户对其他服务器进行免密登入 其他用户需要重新配置

7)Java_Home 报错:由于使用的是root用户登入,所以在脚本中使用ssh命令时会寻找在 .bashrc中的配置 ,所以我们需要将Java_HOME配置在这个文件中,这个文件是隐藏文件,如果我们使用的是其他用户,则会寻找用户独立建立的 /etc/profile.d/myenv.sh文件中的环境变量 所以找不到

8.Topic 入门命令

在这里插入图片描述
1)使用命令创建 topic 创建 分区 1,副本 3 并测试

kafka-topics.sh --bootstrap-server 192.168.114.100:9092 --topic first --create --partitions 1 --replication-factor 3

2)注意:

  1. 在连接时总会出现超时问题,需要在server.propertis 中配置 advertised.listeners=PLAINTEXT://192.168.114.101:9092 否则会超时,也有可能是zookeeper的连接过慢 可以延长一下配置中zookeeper连接的超时时间
  2. 目前使用的是新版本的Kafka 有些Kafka的命令与参数 和旧版本的不相同,这里可以去参照 UncleHang的博客去看https://hang.domcer.com/
  3. 在修改分区时 ,修改后的分区数量的大小不能小于原先的数量,也不能大于Kafka集群服务器的数量,否则报错,这里是因为其他消费者在消费数据的时候,如果合并了分区,那么消费者的就会不知道去哪里消费信息了
  4. 命令行不能修改Kafka topic副本的数量

生产者

9.入门_命令行操作

1)开启Kafka集群,创建Topic后 使用命令 kafka-console-producer.sh --bootstrap-server 192.168.114.100:9092 --topic first 发送数据
2)另开客户端 ,使用命令 kafka-console-consumer.sh --bootstrap-server 192.168.114.100:9092 --topic first 接收数据,注意:开启消费者后,消费者只能接收到开启之后发送的数据,不能消费到之前的数据
3)使用 kafka-console-consumer.sh --bootstrap-server 192.168.114.100:9092 --topic first --from-beginning 接收查看历史数据

10.生产者发送消息流程

在这里插入图片描述
在这里插入图片描述

11.JavaAPI生产消息 同步/异步发送数据

package com.atguigu.kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;

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

/**
 *  Kafka  同步/异步发送数据
 * @author 昭浅
 */

public class CustomProducer {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 0.配置
        Properties properties =new Properties();

        // 连接集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.101:9092");
        // 指定对应keu和value的序列化类型 key.serializer
//        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.serialization.StringSerializer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

//        "" hello
        // 1.创建Kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        // 2.发送数据
        for (int i = 0; i < 5; i++) {
            // 同步发送数据
            RecordMetadata first = producer.send(new ProducerRecord<>("first", "hello" + i)).get();
            // 异步发送数据
//            producer.send(new ProducerRecord<>("first","hello"+i));
        }
        // 3.关闭资源
        producer.close();
    }
}

12.异步发送回调函数

package com.atguigu.kafka.producer;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author 昭浅
 */

public class CustomProducerCallBack {
    public static void main(String[] args) {

        // 0.配置
        Properties properties =new Properties();

        // 连接集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.101:9092");
        // 指定对应keu和value的序列化类型 key.serializer
//        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.serialization.StringSerializer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

//        "" hello
        // 1.创建Kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        // 2.发送数据
        for (int i = 0; i < 5; i++) {
            producer.send(new ProducerRecord<>("first", "hello" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    // Exception 返回的错误信息    metadate 元数据
                    if(exception==null){
                        System.out.println("主题:"+metadata.topic()+"  分区:"+metadata.partition());
                    }
                }
            });
        }
        // 3.关闭资源
        producer.close();
    }
}

13.生产者分区

Kafka 分区好处
(1)便于合理使用存储资源,每个Partition在一个Broker上存储,可以把海量的数据按照分区切割成一
块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。
(2)提高并行度,生产者可以以分区为单位发送数据;消费者可以以分区为单位进行消费数据。
Kafka分区策略
在执行 send() 方法时,需要new一个ProducerRecord() 在其中配置分区策略
在这里插入图片描述
自定义分区策略

/**
 *  自定义分区策略
 * @author 昭浅
 */

public class MyPartitioner implements Partitioner {
    /**
     *
     * @param topic
     * @param key
     * @param keyBytes 序列化后的key
     * @param value
     * @param valueBytes 序列化后的value
     * @param cluster
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // 获取数据
        String msgValue = value.toString();

        int partition;
        if(msgValue.contains("atguigu")){
            partition=0;
        }else{
            partition=1;
        }

        return partition;
    }

    @Override
    public void close() {

    }

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

    }
}


在 properties 中添加配置项
 // 关联自定义分区器    
 properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.atguigu.kafka.producer.MyPartitioner");

生产者如何提高吞吐量

在配置中添加配置项:
        //增加吞吐量 的操作
        // (1)  调整缓冲区大小  默认缓冲区 32M
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);

        // (2) 修改批次大小  默认每个队列 16k
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);

        // (3) linger.ms   默认是0  每0秒发送一次数据
        properties.put(ProducerConfig.LINGER_MS_CONFIG,1);

        // (4) 压缩 压缩数据的类型  gzip snappy lz4 zstd
        properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");

14.ACK应答级别:数据可靠性

1)各个应答级别可能会发生的问题:
在这里插入图片描述
在这里插入图片描述
2)可靠性总结:
在这里插入图片描述
3)数据重复分析:
当Leader和所有Follower完成落盘后,Leader即将应答成功时,Leader宕机了,则会选举为其他Follower为Leader,由于宕机前的Leader没有应答成功,则send线程会重新发送数据,新选举的Leader会接收数据,造成数据重复
在这里插入图片描述

15.生产者_数据重复问题

在这里插入图片描述
幂等性
开启参数: enable.Idempotence 默认为ture false为关闭
在这里插入图片描述

Kafka 事务原理
在这里插入图片描述

开启事务 

public class CustomProducerTranactions {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 0.配置
        Properties properties = new Properties();

        // 连接集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.114.100:9092,192.168.114.101:9092");
        // 指定对应keu和value的序列化类型 key.serializer
//        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.serialization.StringSerializer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 配置 事务 ID
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transcational_id_0");

        // 1.创建Kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);


        // 开启Kafka 事务
        producer.initTransactions();
        producer.beginTransaction();

        try {
            // 2.发送数据
            for (int i = 0; i < 5; i++) {
                producer.send(new ProducerRecord<>("first", "hello" + i));
            }

            // 模拟 出现异常 事务失败
            int j = 10/0;

            // 提交事务
            producer.commitTransaction();
        } catch (Exception e) {
            // 回滚事务
            producer.abortTransaction();
        } finally {
            // 3.关闭资源
            producer.close();
        }
    }
}

Broker

16.生产经验=数据的有序

在这里插入图片描述
1) 解决数据乱序

  1. Producer 的send线程会缓存producer已经发出但没有收到服务端响应的请求, 每个客户端与broker的网络连接的最多缓存请求数,默认是5,超过这个值,客户端不再向这个连接发送更多的请求,可以设置请求缓存数为1,使得请求未处理之前,不能发送其他请求
  2. 在Kafka1.x之后的版本,由于幂等性的出现,Kafka 服务端会缓存producer发来的最近5个request的元数据,开启了幂等性之后,会存在SeqNumber(自增序列化号),服务端会根据它来进行排序

在这里插入图片描述

17.Zookeeper中存储的Kafka信息

在这里插入图片描述

18.Kafka Broker总体工作流程

在这里插入图片描述

19.Broker节点操作

1) 服役新节点

1.克隆虚拟机,修改ip等配置文件,开启Kafka服务端,连接至Kafka集群
2. vim topics-to-move.json   )
		{
			 "topics": [
			 {"topic": "first"}
			 ],
			 "version": 1
	 }
3. 	执行命令  bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2,3" --generate生成负载均衡计划,使得副本们负载均衡在所有服务器中,而不是集中
4. 将生成的计划复制在 vim increase-replication-factor.json 文件中
5. 根据命令执行计划 bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
6. 执行命令bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --verify 验证计划是否应用成功

2) 退役旧节点

1.修改命令生成负载均衡计划 减少需要退役的节点并生成计划
bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2,3" --generate
2.将计划复制在increase-replication-factor.json 文件中,执行命令bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute执行新计划 退役旧节点
3.当副本数只有一个时,退役节点会造成数据丢失,所以副本数最少有两个

20.Kafka副本

(1)Kafka 副本作用:提高数据可靠性。
(2)Kafka 默认副本 1 个,生产环境一般配置为 2 个,保证数据可靠性;太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率。
(3)Kafka 中副本分为:Leader 和 Follower。Kafka 生产者只会把数据发往 Leader,然后 Follower 找 Leader 进行同步数据。
(4)Kafka 分区中的所有副本统称为 AR(Assigned Repllicas)。
AR = ISR + OSR
ISR:表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出ISR。 该时间阈值由 replica.lag.time.max.ms
参数设定,默认 30s。Leader 发生故障之后,就会从 ISR 中选举新的 Leader。
OSR:表示 Follower 与 Leader 副本同步时,延迟过多的副本。

21.Leader选举流程

1)Controller辅助选举Leader流程

1.不同Broker中的Controller会先抢先在Zookeeper中注册信息,由最先注册的Controller来进行辅助选举Leader,Controller只是辅助选举,而并非决定谁是Leader,Controller将节点的信息上传到ZK
2.当参与辅助选举Leader的Controller所在的Broker宕机以后,其他Broker的Controller会同步Zookeeper中的节点信息并重新进行抢先注册,参与辅助选举Leader

在这里插入图片描述
2)Leader选举流程

  1. 当有Broker节点宕机时,会出现分区数大于目前所存活的Broker节点,kafka的分区数量可以大于broker节点数量,当分区数量大于broker节点数量时,在broker节点的data目录下会有同一个topic的两个分区的数据,如:topicName-0,topicName-1。所以会出现不同分区存在相同Leader的情况

  2. Broker宕机后,各分区重新选举Leader的流程,会根据AR(所有副本,包括宕机的副本)的顺序来进行选举,而不是ISR(目前存活的副本),例如 Replicas(AR): 1,0,2,3 , 如果 此时 副本1宕机,则会选举0为此分区的Leader,如果此时 副本 0宕机,那么就会从头开始 选举副本1为当前分区的Leader,当遇到宕机的副本时,会自动跳过选举,继续选择下一位

  3. 服务器宕机重启后,在ISR中顺序会排在末尾

22.Follower故障处理细节

offset是什么?

对于每一个topic, Kafka集群都会维持一个分区日志
每个分区都是有序且顺序不可变的记录集,并且不断地追加到结构化的log文件。分区中的每一个记录都会分配一个id号来表示顺序,我们称之为offset,offset用来唯一的标识分区中每一条记录。

LEO (Log End Offset ):每个副本的最后一个offset LEO就是最新的offset + 1
HW ( High Watermark ):所有副本中最小的LEO,当设置ACK为-1(all)时,Follower还没有同步完成,Leader不会返回成功信息,所以用户只能查询到Leader之前的数据,也就是Follower最小offset同步的信息

  1. LogStartOffset:表示一个Partition的起始位移,初始为0,虽然消息的增加以及日志清除策略的影响,这个值会阶段性的增大。
  2. ConsumerOffset:消费位移,表示Partition的某个消费者消费到的位移位置。
  3. HighWatermark:简称HW,代表消费端所能“观察”到的Partition的最高日志位移,HW大于等于ConsumerOffset的值。
  4. LogEndOffset:简称LEO, 代表Partition的最高日志位移,其值对消费者不可见。

1)当Follower发生故障,Follower会被踢出ISR队列,期间,Leader和其他Follower会继续接受数据,当此Follower恢复后,会先将log文件中始于宕机前所记录的HW的所有数据截取掉,从HW开始向Leader重新开始同步,当同步到目前存活的副本中的HW时,此Follower才会重新返回ISR队列
在这里插入图片描述

23.Leader故障处理细节

在这里插入图片描述

24.分区副本分配规则

在这里插入图片描述

25.生产环境中,指定分区副本存储情况

编辑 执行计划修改分区副本存储情况 vim increase-replication-factor.json

{
"version":1,
"partitions":[{"topic":"three","partition":0,"replicas":[0,1]},
{"topic":"three","partition":1,"replicas":[0,1]},
{"topic":"three","partition":2,"replicas":[1,0]},
{"topic":"three","partition":3,"replicas":[1,0]}]
}

执行命令 修改副本bin/kafka-reassign-partitions.sh – bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute

26.Partition自动平衡

原则上不建议开启自动平衡,因为浪费集群性能
在这里插入图片描述

27.增加副本因子

修改并执行新计划,添加副本,不能使用命令行来直接修改副本数量

{“version”:1,
“partitions”:[{“topic”:“four”,“partition”:0,“replicas”:[0,1,2]},{“topic”:“four”,“partition”:1,“replicas”:[0,1,2]},{“topic”:“four”,“partition”:2,“replicas”:[0,1,2]}]
}

28.Kafka文件存储机制

在这里插入图片描述
1)Kafka 存储文件中的索引index为稀疏索引,大约每往log文件写入4kb数据,会往index文件写入一条索引,参数log.index.interval.bytes默认为4kb
2) 当 .log文件存储大约4kb文件内容时才会向 .index文件中存储一条索引
在这里插入图片描述

29.Kafka文件清除策略

Kafka 中默认的日志保存时间为 7 天,可以通过调整如下参数修改保存时间。

log.retention.hours,最低优先级小时,默认 7 天。
log.retention.minutes,分钟。
log.retention.ms,最高优先级毫秒。
log.retention.check.interval.ms,负责设置检查周期,默认 5 分钟。

那么日志一旦超过了设置的时间,怎么处理呢?
Kafka 中提供的日志清理策略有 delete 和 compact 两种。
1)delete 日志删(默认)除:将过期数据删除

log.cleanup.policy = delete 所有数据启用删除策略

(1)基于时间:默认打开。以 segment 中所有记录中的最大时间戳作为该文件时间戳。
(2)基于大小:默认关闭。超过设置的所有日志总大小,删除最早的 segment。

log.retention.bytes,默认等于-1,表示无穷大。

思考:如果一个 segment 中有一部分数据过期,一部分没有过期,怎么处理?
Kafka会以segment中最大时间戳作为该文件的时间戳,该文件的时间戳是未过期文件的时间戳,该文件不会过期,就不会被删除
2)compact 日志压缩
在这里插入图片描述

30.Kafka 高效读写数据

1)Kafka本身是分布式集群,可采用分区技术,消费者并行消费数据
2)读数据采用稀疏索引,可以快速定位要消费的位置
3)顺序写磁盘:

Kafka的producer生产数据,要写入到log文件中,写的过程是一致追加到文件末端,为顺序写,而随机写由于需要需要磁头寻找会浪费大量时间
在这里插入图片描述

4)零拷贝+页缓存

页缓存(PageCache):Kafka会依赖Linux底层操作系统中提供的PageCache功能,将数据信息缓存在Linux内核的内存中,当需要查询数据时会优先查询PageCache,如果缓存中没有则会去磁盘中查询到并刷写到PageCache中
零拷贝:当消费者消费数据时,Kafka通过PageCache查询到数据后,会直接数据复制到Socket Cache中在通过网卡发送数据,而不需要在将数据返回到Kafka的应用层,减少了一次IO操作
内核态:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。
用户态:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
通俗来说用户态就是Kafka应用层,有受限的访问权限,内核态是Linux的内核,具有访问网卡,读取磁盘等权限等特级权限

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

消费者

31.Kafka消费数据的两种方式

在这里插入图片描述

32.消费者工作流程

1)一个独立的消费者消费多个分区的数据
2)一个分区的数据只能有消费者组中的一个消费者进行消费,其他消费者不能消费此分区的数据
3)消费者消费到分区数据的位置即为offset,新版本Kafka的offset由消费者提交保存到Kafka系统主题中,由Kafka来进行保存,旧版Kafka会将offset将数据保存到Zookeeper中,由于在查询位置时会和Zookeeper进行大量网络通信,造成资源浪费,所以被弃用,有Kafka自行保存
在这里插入图片描述

33.消费者组

由多个consumer组成,形成一个消费者组的条件,是所有消费者的groupId相同

消费者组内每个消费者负责消费不同分区的数据,一个分区只能有一个组内消费者消费
消费者组之间互不影响,所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者
当消费者组中的消费者数量大于分区数量时,则会与一部分消费者闲置,不会接受任何消息

在这里插入图片描述
在这里插入图片描述

34.消费者组初始化流程

1)coordinator:辅助实现消费者组的初始化和分区的分配,每一个broker都有coordinator
coordinator节点选择 = groupid的hashcode值 % 50 (_consumer_offsets的分区数量)

例如:groupId的hashcode值=1 ,1%50 =1,那么_consumer_offsets主题的1号分区,在那个broker上,就选择这个节点的coordinator作为这个消费者组的老大,消费者组下的所有消费者提交offset的时候,就往这个分区区提交offset

2)消费者组分配流程

  1. 每个消费者都会发送JoinGroup请求到coordinatore中,Kafka通过计算后决定将这些请求发送到某个broker节点中的coordinator用来分配消费者组的辅助组件
  2. coorainator会随机选取消费者组中的一个consumer作为 Leader Consumer,并将要消费的Topic情况发送给Leader Consumer
  3. Leader Consumer会负责制定出消费方案,来决定消费者组中的消费者对应消费哪个分区,并将消费方案发还给coordinator
  4. coordinator会将消费方案发送给其他消费者组中的消费者
  5. 每个消费者都会和coorinator保持心跳(默认3s),一旦超过(session.timeout.ms=45s),该消费者会被移除,并触发再平衡,其他消费者会完成该消费者的任务,或者消费者处理消息的时间过长(max.poll.interval.ms 默认5分钟)也会触发再平衡
    在这里插入图片描述

35.消费者组消费流程

1)消费者组发送sendFetches请求,创建网络连接客户端(ConsumerNetworkClient),当以下参数任意条件满足,都会拉取数据

Fetch.min bytes 每批次最小抓取大小 默认1字节
fetch.max.wait.ms 一批数据最小值未达到的超时时间 默认500ms
Fetch.max.bytes 每批次最大抓取大小 默认50M

2)ConsumerNetworkClient通过回调函数将数据拉取过来,存储到消息队列中
3)FetchedRecords从队列中抓取数据,一次拉取数据返回消息的最大条数默认为500条
4)数据会先经过反序列化–>拦截器–>处理数据
在这里插入图片描述

36.消费者组消费API

独立消费者

/**
 * 独立消费者 消费信息数据
 * @author 昭浅
 */

public class CustomConsumer {
    public static void main(String[] args) {

        // 0 配置
        Properties properties = new Properties();

        // 连接bootstrap-server
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.1168.114.101:9092");
        // 反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());

        // 配置 groupId 单个独立消费者也必须配置
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");


        // 1.创建一个消费者和
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 2.定义消费的主题 first
        ArrayList<String> topics = new ArrayList<>();
        topics.add("first");
        kafkaConsumer.subscribe(topics);

        // 3.消费数据
        while (true){
            ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
            records.forEach(record->{
                System.out.println(record);
            });
        }
    }
}

独立消费者消费指定分区

/**
 * 独立消费者 消费指定分区的数据
 * @author 昭浅
 */

public class CustomConsumerPartition {
    public static void main(String[] args) {

        // 0 配置
        Properties properties = new Properties();

        // 连接bootstrap-server
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.1168.114.101:9092");
        // 反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());

        // 配置 groupId 单个独立消费者也必须配置
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");


        // 1.创建一个消费者和
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 2. 指定主题 以及消费的分区
        ArrayList<TopicPartition> topicPartitions = new ArrayList<>();
        topicPartitions.add(new TopicPartition("first",0));
        kafkaConsumer.assign(topicPartitions);

        // 3.消费数据
        while (true){
            ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
            records.forEach(record->{
                System.out.println(record);
            });
        }
    }
}

消费者组案例
创建三个相同groupId的消费者,接收数据,会发现不同消费者消费不同分区的数据

37.消费者分区分配策略

1)Kafka有四种主流的分区分配策略:RangeRoundRobinSticky(黏性)、CooperativeSticky(合作者黏性)
可以通过修改配置参数 partition.assignment.strategy,修改分区的分配策略,默认策略是Range + CooperativeSticky,Kafka可以同时使用多个分区分配策略
2)Range :Range是对每个topic而言的,首先对同一个topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序,通过partitions数/consumer数来决定每个消费者应该消费几个分区,如果除不尽,那么前面几个消费者将会多消费一个分区

注意:当消费者组中的某个消费者宕机后,45s中内,该消费者负责的分区会统一落到某一消费者身上,当超过45s后,消费者组中的消费者会重新进行Range重新分配一次

在这里插入图片描述
3)RoundRobin :针对集群中所有得到Topic而言,RoundRobin轮询分区策略,是将所有topic中的所有partition和consumer都根据字典顺序列出来,然后按照轮询算法来分配partition给各个消费者
注意:当消费者组中的某个消费者宕机后,45s中内,该消费者负责的分区会轮询分发到其他消费者身上,45s后,消费者组会重新轮询进行分区分配

properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,“org.apache.kafka.clients.consumer.RoundRobinAssignor”);

在这里插入图片描述
4)Sticky以及再平衡:黏性(随机)分区,可以理解为分配的结果待带有黏性的,即在执行一次新的分配之前,考虑上一次分配的结果,尽量少的调整分配的变动,即Sticky分区策略是和Range策略相似的策略,只是在平均分配分区时,Sticky策略并不是按照顺序分配分区的,而是随机选择分区,均匀且随机

注意:当消费者组中的某个消费者宕机后,45s中内,该消费者负责的分区会黏性分发到其他消费者身上,45s后,消费者组会重新进行黏性分区分配

在这里插入图片描述

38.offset 位移

1)offset默认维护位置

  1. 自0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为_consumer-offsets
  2. 在0.9版本之前,consumer将offset保存在Zookeeper中
    在这里插入图片描述
    __consumer_offsets :Kafka的内置主题,consumer将消费到的Consumer_Offset存储到这个topic中,该主题使用key和value的方式存储数据,key是group.id+topic+分区号,value就是当前offset的值,每隔一段时间,Kafka内部会对这个topic进行compact(压缩),就是每个key(group.id+topic+分区号)只保留最新数据

想要查看该主题的数据,需要将consumer的配置文件添加配置项,在配置文件 config/consumer.properties 中添加配置 exclude.internal.topics=false,默认是 true,表示不能消费系统主题。为了查看该系统主题数据,所以该参数修改为 false。
在这里插入图片描述

2)自动提交 offset
为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交的功能,我们无需向__consumner_offsets

enable.auto.commit:是否开启自动提交offset功能,默认是true
auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s

3)手动提交 offset
自动提交offset十分方便,但是由于是基于时间提交,开发人员难以把握offset提交的时机,Kafka提供了手动提交的API

commitSync(同步提交):将本次提交的一批数据最高的偏移量提交,同步提交会阻塞当前线程,知道提交成功,如果提交失败,则会自动失败重试
commitAsync(异步提交):将本次提交的一批数据最高的偏移量提交,不会阻塞当前线程,没有自动失败重试机制,故而可能提交失败

   // 配置手动提交 offset
   properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
   
   // 在消费者拉取数据后 手动提交offset
   // 同步提交
   //kafkaConsumer.commitSync();
   // 异步提交 offset
   kafkaConsumer.commitAsync();
   

4)指定 offset 消费
auto.offset.rest = eraliest | latest | none 默认是 latest
当Kafka 中没有初始偏移量(消费者是第一次消费)或服务器上不再存在当前偏移量时(例如当前数据已经被删除)

  1. earliest:自动将偏移量重置为最早的偏移量 --from-beginning,消费者可以获取到最早的数据
  2. latest(默认值):自定将偏移量重置为最新偏移量,消费者只能从最新消息开始获取
  3. none:如果未找到消费者组的先前偏移量,则向消费者抛出异常
/**
 * 指定 offset开始消费
 * @author 昭浅
 */

public class CustorConsumerSeek {
    public static void main(String[] args) {

        // 0 配置信息
        Properties properties = new Properties();

        // 连接 bootstrap-server
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.100:9092");
        // 配置反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

        // 配置 group id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test6");

        // 1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 2.订阅主题
        ArrayList<String> topics = new ArrayList<>();
        topics.add("first");
        kafkaConsumer.subscribe(topics);


        // 指定位置进行消费
        // 遍历所有分区信息 指定分区文件中的offset进行消费
        Set<TopicPartition> assignment = kafkaConsumer.assignment();

        // 保证分区分配方案已经制定完毕
        while(assignment.size()==0){
            kafkaConsumer.poll(Duration.ofSeconds(1));
            assignment = kafkaConsumer.assignment();
        }


        assignment.forEach(assign ->{
            kafkaConsumer.seek(assign,600);
        });

        // 3.消费 拉取数据
        while (true){
            ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));

            records.forEach(record ->{
                System.out.println(record);
            });


        }

    }
}

5)指定 时间 进行消费
需求:在生产环境中,会遇到最近消费的几个小时数据异常,想要重新按照时间消费,例如要求按照时间消费前一天的数据,需要怎样处理

/**
 * 指定 offset开始消费
 * @author 昭浅
 */

public class CustorConsumerSeekByTime {
    public static void main(String[] args) {

        // 0 配置信息
        Properties properties = new Properties();

        // 连接 bootstrap-server
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.100:9092");
        // 配置反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

        // 配置 group id
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test8");

        // 1.创建消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 2.订阅主题
        ArrayList<String> topics = new ArrayList<>();
        topics.add("first");
        kafkaConsumer.subscribe(topics);


        // 指定位置进行消费
        // 遍历所有分区信息 指定分区文件中的offset进行消费
        Set<TopicPartition> assignment = kafkaConsumer.assignment();

        // 保证分区分配方案已经制定完毕
        while(assignment.size()==0){
            kafkaConsumer.poll(Duration.ofSeconds(1));
            assignment = kafkaConsumer.assignment();
        }

        // 把时间转换为对用的offset
        HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();

        // 封装对应集合 分区-》offset
        assignment.forEach(topicPartition -> {
            topicPartitionLongHashMap.put(topicPartition,System.currentTimeMillis()-1 *24 * 3600 * 1000);
        });

        // 通过时间转换获取分区对应时间的offset集合
        Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);

        assignment.forEach(topicPartition ->{
            // 获取当前分区对应 时间的offset
            OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(topicPartition);
            kafkaConsumer.seek(topicPartition,offsetAndTimestamp.offset());
        });

        // 3.消费 拉取数据
        while (true){
            ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));

            records.forEach(record ->{
                System.out.println(record);
            });


        }

    }
}

39.漏消费和重复消费

重复消费:已经消费了数据,但是 offset 没提交。
漏消费:先提交 offset 后消费,有可能会造成数据的漏消费
在这里插入图片描述

40.生产经验——消费者事务

在这里插入图片描述

41.生产经验——数据积压(消费者如何提高吞吐量)

在这里插入图片描述

42.Kafka-Eagle 监控

Kafka-Eagle 框架可以监控Kafka集群的整体运行情况,在生产环境中经常使用
1)Mysql环境准备:Kafka-Eagle的安装依赖Mysql,Mysql主要用来存储可视化展示的数据
2)Kafka环境准备

1. vim bin/kafka-server-start.sh  修改如下参数值,改变Kafka启动内存大小:
   if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
	 export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
	fi  为
	if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
	 export KAFKA_HEAP_OPTS="-server -Xms2G -Xmx2G -
	XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -
	XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -
	XX:InitiatingHeapOccupancyPercent=70"
	 export JMX_PORT="9999"
	 #export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
	fi
修改后,将文件分发到其他服务器,

3) Kafka-Eagle 安装
安装后修改配置文件,配置ZK地址,配置Mysql,将offset保存到kafka,运行eagle,登入eagle并查看

43.Kafka-Kraft 模式

左图为 Kafka 现有架构,元数据在 zookeeper 中,运行时动态选举 controller,由
controller 进行 Kafka 集群管理。右图为 kraft 模式架构(实验性),不再依赖 zookeeper 集群,
而是用三台 controller 节点代替 zookeeper,元数据保存在 controller 中,由 controller 直接进
行 Kafka 集群管理。
在这里插入图片描述

整合SpringBoot

44.SpringBoot整合Kafka

1)SpringBoot整合Kafka 生产者

  1. 安装lombok插件 方便使用
  2. 创建SpringBoot项目,修改配置项
# 连接kafka集群配置
spring.kafka.bootstrap-servers=192.168.114.100:9092,192.168.114.101:9092,192.168.114.102:9092

#key  value 的序列化
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
  1. 创建controller 使用KafkaTemplate.send() 发送数据,并在服务器端创建消费者接收数据进行测试
@RestController
public class ProducerController {

    @Autowired
    KafkaTemplate<String,String> kafkaTemplate;

    @RequestMapping("/atguigu")
    public String data(String msg){
        kafkaTemplate.send("first",msg);

        return "ok";
    }
}

1)SpringBoot整合Kafka 消费者

  1. 安装lombok插件 方便使用
  2. 创建SpringBoot项目,修改配置项
# 连接kafka集群配置
spring.kafka.bootstrap-servers=192.168.114.100:9092,192.168.114.101:9092,192.168.114.102:9092

# key value 反序列化
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# 消费者组id groupid
spring.kafka.consumer.group-id=test1
  1. 创建消费者配置类,监听Kafka 发送数据,并在网页发送数据接受测试
// 将消费者定义成配置类,监听会一直生效
@Configuration
public class KafkaConsumer {

    // kafka注解 消费消息
    @KafkaListener(topics="first")
    public void consumerTopic(String msg){
        System.out.println("收到消息:"+msg);
    }
}

45.集群压力测试

1)Kafka压测:使用Kafka官方自带的脚本,对Kafka进行压测
生产者压测:kafka-producer-perf-test.sh
消费者压测:kafka-consumer-perf-test.sh

生产者压力测试
[atguigu@hadoop105 kafka]$ bin/kafka-producer-perf-test.sh --topic test --record-size 1024 --num-records 1000000 --throughput
10000 --producer-props bootstrap.servers=hadoop102:9092,hadoop103:9092,hadoop104:9092 batch.size=16384 linger.ms=0 compression.type=lz4

消费者压力测试
[atguigu@hadoop105 kafka]$ bin/kafka-consumer-perf-test.sh --bootstrap-server hadoop102:9092,hadoop103:9092,hadoop104:9092
–topic test --messages 1000000 --consumer.config config/consumer.properties

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值