kafka技术

kafka概述

1.1 定义

kafka最新定义:​ kafka是一个多分区、多副本且基于zookeeper协调的分布式消息系统。也是一个分布式流式处理平台,它以高吞吐、可持久化、可水平扩展、支持流数据处理等多种特性而被广泛使用。

1.2 消息队列

目 前企 业中比 较常 见的 消息 队列产 品主 要有 Kafka、ActiveMQ 、RabbitMQ 、
RocketMQ 等。
在大数据场景主要采用 Kafka 作为消息队列。在 JavaEE 开发中主要采用 ActiveMQ、
RabbitMQ、RocketMQ。可以关注尚硅谷教育公众号回复 java,免费获取相关资料。

1.2.1 传统消息队列的应用场景

传统的消息队列主要应用场景包括:缓存/削峰、解耦和异步通信

  1. 消息队列的应用场景—— 缓存/削峰
    缓存/削峰:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
    在这里插入图片描述

  2. 消息队列的应用场景——解耦
    解耦:允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
    在这里插入图片描述

  3. 消息队列的应用场景——异步通信
    异步通信
    :允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们。
    在这里插入图片描述

1.2.2 消息队列的两种模式

消息队列的两种模式
1)点对点模式
消费者主动拉取数据,消息收到后清除消息
在这里插入图片描述
2)发布/订阅模式
可以有多个topic主题(浏览、点赞、收藏、评论等)
消费者消费数据之后,不删除数据
每个消费者相互独立,都可以消费到数据
在这里插入图片描述

1.3 Kafka 基础架构

Kafka 基础架构
在这里插入图片描述
(1)Producer:消息生产者,就是向 Kafka broker 发消息的客户端。
(2)Consumer:消息消费者,向 Kafka broker 取消息的客户端。
(3)Consumer Group(CG):消费者组,由多个 consumer 组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
(4)Broker:一台 Kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
(5)Topic:可以理解为一个队列,生产者和消费者面向的都是一个 topic。
(6)Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列。
(7)Replica:副本。一个 topic 的每个分区都有若干个副本,一个 Leader 和若干个Follower。
(8)Leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 Leader。
(9)Follower:每个分区多个副本中的“从”,实时从 Leader 中同步数据,保持和Leader 数据的同步。Leader 发生故障时,某个 Follower 会成为新的 Leader。

Kafka 快速入门

2.1 kafka命令行操作

kafka 基础架构
在这里插入图片描述

Kafka 生产者

3.1 生产者消息发送流程

发送原理

在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator,Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka Broker。

发送流程
在这里插入图片描述

3.1.2 生产者重要参数列表

参数名称描述
bootstrap.servers生产者连接集群所需的 broker 地 址 清 单 。例 如hadoop102:9092,hadoop103:9092,hadoop104:9092,可以设置 1 个或者多,中间用逗号隔开。注意这里并非需要所有的 broker 地址,因为生产者从给定的 broker里查找到其他 broker 信息。
key.serializer 和 value.serializer指定发送消息的 key 和 value 的序列化类型。一定要写全类名。
buffer.memoryRecordAccumulator 缓冲区总大小,默认 32m。
batch.size缓冲区一批数据最大值,默认 16k。适当增加该值,可以提高吞吐量,但是如果该值设置太大,会导致数据传输延迟增加。
linger.ms如果数据迟迟未达到 batch.size,sender 等待 linger.time之后就会发送数据。单位 ms,默认值是 0ms,表示没有延迟。生产环境建议该值大小为 5-100ms 之间。
acks0:生产者发送过来的数据,不需要等数据落盘应答。 1:生产者发送过来的数据,Leader 收到数据后应答。 -1(all):生产者发送过来的数据,Leader+和 isr 队列里面的所有节点收齐数据后应答。默认值是-1,-1 和all 是等价的。
retries当消息发送出现错误的时候,系统会重发消息。retries表示重试次数。默认是 int 最大值,2147483647。 如果设置了重试,还想保证消息的有序性,需要设置 MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1否则在重试此失败消息的时候,其他的消息可能发送成功了。
retry.backoff.ms两次重试之间的时间间隔,默认是 100ms。
enable.idempotence是否开启幂等性,默认 true,开启幂等性。
compression.type生产者发送的所有数据的压缩方式。默认是 none,也就是不压缩。 支持压缩类型:none、gzip、snappy、lz4 和 zstd。

3.2 异步发送 API

3.2.1 普通异步发送

1)需求:创建 Kafka 生产者,采用异步的方式发送到 Kafka Broker
异步发送流程

在这里插入图片描述
2)代码编写

package com.cjdx.product;

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.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author: 赤脚大仙
 * @date: 2022/9/18 21:01
 * @description:生产者
 */
public class CustomerProducer {
    public static void main(String[] args) {
        // 0配置
        Properties properties = new Properties();
        //连接
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.40.128:9092");
        //指定key和value的序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        //1.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //2发送数据
        for (int i = 0; i < 5; i++) {
            System.out.println("first"+i);
            producer.send(new ProducerRecord<>("first", "atguigu"+i));
            System.out.println(i);
        }
        //3.关闭资源
        producer.close();
    }
}

3.2.2 带回调函数的异步发送

回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是元数据信息(RecordMetadata)和异常信息(Exception),如果 Exception 为 null,说明消息发送成功,如果 Exception 不为 null,说明消息发送失败。

带回调函数的异步发送流程
在这里插入图片描述

package com.cjdx.product;

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

import java.util.Properties;

/**
 * @author: 赤脚大仙
 * @date: 2022/9/18 21:01
 * @description:生产者
 */
public class CustomerProducerCallback {
    public static void main(String[] args) {
        // 0配置
        Properties properties = new Properties();
        //连接
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
        //指定key和value的序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        //1.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //2发送数据
        for (int i = 0; i < 5; i++) {
            producer.send(new ProducerRecord<>( "first", "atguigu" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    System.out.println("分区:"+recordMetadata.partition());

                }
            });
        }
        //3.关闭资源
        producer.close();
    }
}

测试:

①在 hadoop102 上开启 Kafka 消费者。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first 

②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first 
 
atguigu 0 
atguigu 1 
atguigu 2 
atguigu 3 
atguigu 4 

3.4 生产者分区

3.4.1 分区好处

Kafka 分区好处
(1)便于合理使用存储资源,每个Partition在一个Broker上存储,可以把海量的数据按照分区切割成一块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。
(2)提高并行度,生产者可以以分区为单位发送数据;消费者可以以分区为单位进行消费数据。

在这里插入图片描述

3.4.2 生产者发送消息的分区策略

1)默认的分区器 DefaultPartitioner
在 IDEA 中 ctrl +n,全局查找 DefaultPartitioner。

/** 
* 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 the sticky 
partition that changes when the batch is full. 

* 
* See KIP-480 for details about sticky partitioning. 
*/ 
public class DefaultPartitioner implements Partitioner { 

… … 
} 

Kafka 原则
在这里插入图片描述
2)案例一
将数据发往指定 partition 的情况下,例如,将所有数据发往分区 1 中。

package com.cjdx.product;

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

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

/**
 * @author: 赤脚大仙
 * @date: 2022/9/18 21:01
 * @description:生产者 分区测试
 */
public class CustomerProducerPartition {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 0配置
        Properties properties = new Properties();
        //连接
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
        //指定key和value的序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        //关联自定义分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.cjdx.MyPartition");
        //1.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //2发送数据
        for (int i = 0; i < 5; i++) {
           producer.send(
                   new ProducerRecord<>("first","1", "atguigu" + i), new Callback() {
                       @Override
                       public void onCompletion(RecordMetadata metadata, Exception exception) {
                           if (exception == null){
                               System.out.println("主题"+metadata.topic() + "分区:"+metadata.partition());
                           }
                       }
                   });
        }
        //3.关闭资源
        producer.close();
    }
}

测试:
①在 hadoop102 上开启 Kafka 消费者。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first

②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first 
 
atguigu 0 
atguigu 1 
atguigu 2 
atguigu 3 
atguigu 4 

③在 IDEA 控制台观察回调信息。

主题:first->分区:1 
主题:first->分区:1 
主题:first->分区:1 
主题:first->分区:1 
主题:first->分区:1 

3)案例二
没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值。

package com.cjdx.product;

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

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

/**
 * @author: 赤脚大仙
 * @date: 2022/9/18 21:01
 * @description:生产者 分区测试
 */
public class CustomerProducerPartition {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 0配置
        Properties properties = new Properties();
        //连接
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
        //指定key和value的序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        //关联自定义分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.cjdx.MyPartition");
        //1.创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //2发送数据
        for (int i = 0; i < 5; i++) {
           producer.send(
                   new ProducerRecord<>("first1", "atguigu" + i), new Callback() {
                       @Override
                       public void onCompletion(RecordMetadata metadata, Exception exception) {
                           if (exception == null){
                               System.out.println("主题"+metadata.topic() + "分区:"+metadata.partition());
                           }
                       }
                   });
        }
        //3.关闭资源
        producer.close();
    }
}

测试:
①key="a"时,在控制台查看结果。

主题:first->分区:1 
主题:first->分区:1 
主题:first->分区:1 
主题:first->分区:1 
主题:first->分区:1 

②key="b"时,在控制台查看结果。

主题:first->分区:2 
主题:first->分区:2 
主题:first->分区:2 
主题:first->分区:2 
主题:first->分区:2 

③key="f"时,在控制台查看结果

主题:first->分区:0 
主题:first->分区:0 
主题:first->分区:0 
主题:first->分区:0 
主题:first->分区:0 

3.4.3 自定义分区器

如果研发人员可以根据企业需求,自己重新实现分区器
1)需求
例如我们实现一个分区器实现,发送过来的数据中如果包含 atguigu,就发往 0 号分区,不包含 atguigu,就发往 1 号分区。
2)实现步骤
(1)定义类实现 Partitioner 接口。
(2)重写 partition()方法。

package com.cjdx;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;

/**
* @author: 赤脚大仙
* @date: 2022/9/19 23:16
* @description:自定义分区器
*/

public class MyPartition implements Partitioner {
   @Override
   public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {

       // 获取数据 atguigu  hello
       String msgValues = value.toString();

       //创建partition
       int partition;

       // 判断消息是否包含atguigu
       if (msgValues.contains("atguigu")) {
           partition = 0;
       } else {
           partition = 1;
       }
       // 返回分区号
       return partition;
   }

   //关闭资源
   @Override
   public void close() {

   }

   //配置方法
   @Override
   public void configure(Map<String, ?> configs) {

   }
}

3)使用分区器的方法,在生产者的配置中添加分区器参数。

package com.cjdx.product;

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

import java.util.Properties;

/**
 * @author: 赤脚大仙
 * @date: 2022/10/8 22:41
 * @description:
 */
public class CustomerProductCallbackPartitions {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        //添加自定义分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.cjdx.MyPartition");
        KafkaProducer kafkaProducer = new KafkaProducer<>(properties);


        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord("first", "atguigu" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null) {
                        System.out.println("主题"+metadata.topic() +"分区"+metadata.partition());
                    }else {
                        exception.printStackTrace();
                    }
                }
            });

        }
        kafkaProducer.close();

    }
}

(4)测试
①在 hadoop102 上开启 Kafka 消费者。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first 

②在 IDEA 控制台观察回调信息。
主题:first->分区:0
主题:first->分区:0
主题:first->分区:0
主题:first->分区:0
主题:first->分区:0

3.5 生产经验——生产者如何提高吞吐量

生产经验——生产者如何提高吞吐量
在这里插入图片描述

package com.atguigu.kafka.producer; 
 
import org.apache.kafka.clients.producer.KafkaProducer; 
import org.apache.kafka.clients.producer.ProducerRecord; 
import java.util.Properties; 
public class CustomProducerParameters { 
 public static void main(String[] args) throws InterruptedException { 
 // 1. 创建 kafka 生产者的配置对象 
 Properties properties = new Properties(); 
 // 2. 给 kafka 配置对象添加配置信息:bootstrap.servers 
 properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092"); 
  // key,value 序列化(必须):key.serializer,value.serializer 
 properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); 
 properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 
"org.apache.kafka.common.serialization.StringSerializer"); 
 // batch.size:批次大小,默认 16K 
 properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); 
  // linger.ms:等待时间,默认 0 
 properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); 
  // RecordAccumulator:缓冲区大小,默认 32M:buffer.memory 
 properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432); 
  // compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4 和 zstd 
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy"); 
  // 3. 创建 kafka 生产者对象 
 KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties); 
  // 4. 调用 send 方法,发送消息 
 for (int i = 0; i < 5; i++) { 
  kafkaProducer.send(new ProducerRecord<>("first","atguigu " + i)); 
  } 
  // 5. 关闭资源 
 kafkaProducer.close(); 
 } 
} 

测试:
①在 hadoop102 上开启 Kafka 消费者。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first 

②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。

bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first 
atguigu 0 
atguigu 1 
atguigu 2 
atguigu 3 
atguigu 4

3.6 生产经验——数据可靠性

0)回顾发送流程
发送流程
在这里插入图片描述
1)ack 应答原理
ACK应答级别
在这里插入图片描述
ACK应答级别
在这里插入图片描述
ACK应答级别
在这里插入图片描述
2)代码配置

package com.atguigu.kafka.producer; 
import org.apache.kafka.clients.producer.KafkaProducer; 
import org.apache.kafka.clients.producer.ProducerRecord; 
import java.util.Properties; 
public class CustomProducerAck { 
 public static void main(String[] args) throws InterruptedException { 
 // 1. 创建 kafka 生产者的配置对象 
 Properties properties = new Properties(); 
 // 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
 properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092"); 
 // key,value 序列化(必须):key.serializer,value.serializer 
 properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 
StringSerializer.class.getName()); 
 // 设置 acks 
 properties.put(ProducerConfig.ACKS_CONFIG, "all"); 
 // 重试次数 retries,默认是 int 最大值,2147483647 
 properties.put(ProducerConfig.RETRIES_CONFIG, 3); 
 // 3. 创建 kafka 生产者对象 
 KafkaProducer<String, String> kafkaProducer = new 
KafkaProducer<String, String>(properties); 
 
 // 4. 调用 send 方法,发送消息 
 for (int i = 0; i < 5; i++) { 
 
 kafkaProducer.send(new 
ProducerRecord<>("first","atguigu " + i)); 
 
 } 
 
 // 5. 关闭资源 
 kafkaProducer.close(); 
 } 
} 

3.7 生产经验——数据去重

3.7.1 数据传递语义

数据传递语义

  • 至少一次(At Least Once)= ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
  • 最多一次(At Most Once)= ACK级别设置为0
  • 总结:
    At Least Once可以保证数据不丢失,但是不能保证数据不重复;
    At Most Once可以保证数据不重复,但是不能保证数据不丢失。
  • 精确一次(Exactly Once):对于一些非常重要的信息,比如和钱相关的数据,要求数据既不能重复也不丢失。
    Kafka 0.11版本以后,引入了一项重大特性:幂等性和事务。

3.7.2 幂等性

1)幂等性原理
幂等性原理

幂等性就是指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证了不重复。
精确一次(Exactly Once) = 幂等性 + 至少一次( ack=-1 + 分区副本数>=2 + ISR最小副本数量>=2) 。

重复数据的判断标准:具有<PID, Partition, SeqNumber>相同主键的消息提交时,Broker只会持久化一条。其
中PID是Kafka每次重启都会分配一个新的;Partition 表示分区号;Sequence Number是单调自增的。
所以幂等性只能保证的是在单分区单会话内不重复。
在这里插入图片描述
2)如何使用幂等性
开启参数 enable.idempotence 默认为 true,false 关闭。

3.7.3 生产者事务

1)Kafka 事务原理
Kafka 事务原理
在这里插入图片描述
2)Kafka 的事务一共有如下 5 个 API

// 1 初始化事务 
void initTransactions(); 
 
// 2 开启事务 
void beginTransaction() throws ProducerFencedException; 
 
// 3 在事务内提交已经消费的偏移量(主要用于消费者) 
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets, 
 String consumerGroupId) throws 
ProducerFencedException; 
 
// 4 提交事务 
void commitTransaction() throws ProducerFencedException; 
 
// 5 放弃事务(类似于回滚事务的操作) 
void abortTransaction() throws ProducerFencedException; 

3)单个 Producer,使用事务保证消息的仅一次发送

package com.atguigu.kafka.producer; 
 
import org.apache.kafka.clients.producer.KafkaProducer; 
import org.apache.kafka.clients.producer.ProducerRecord; 
 
import java.util.Properties; 
 
public class CustomProducerTransactions { 
 
 public static void main(String[] args) throws 
InterruptedException { 
 
 // 1. 创建 kafka 生产者的配置对象 
 Properties properties = new Properties(); 
 
 // 2. 给 kafka 配置对象添加配置信息 
 properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092"); 
 
 // key,value 序列化 
 properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, 
StringSerializer.class.getName()); 
 
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 
StringSerializer.class.getName()); 
 
 // 设置事务 id(必须),事务 id 任意起名 
 properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, 
"transaction_id_0"); 

 
 // 3. 创建 kafka 生产者对象 
 KafkaProducer<String, String> kafkaProducer = new 
KafkaProducer<String, String>(properties); 
 
 // 初始化事务 
 kafkaProducer.initTransactions(); 
 // 开启事务 
 kafkaProducer.beginTransaction(); 
 try { 
 // 4. 调用 send 方法,发送消息 
 for (int i = 0; i < 5; i++) { 
 // 发送消息 
 kafkaProducer.send(new ProducerRecord<>("first", 
"atguigu " + i)); 
 } 
 
// int i = 1 / 0; 
 
 // 提交事务 
 kafkaProducer.commitTransaction(); 
 
 } catch (Exception e) { 
 // 终止事务 
 kafkaProducer.abortTransaction(); 
 } finally { 
 // 5. 关闭资源 
 kafkaProducer.close(); 
 } 
 } 
} 

3.8 生产经验——数据有序

生产经验——数据有序
在这里插入图片描述

3.9 生产经验——数据乱序

生产经验——数据乱序
1)kafka在1.x版本之前保证数据单分区有序,条件如下:
max.in.flight.requests.per.connection=1(不需要考虑是否开启幂等性)。
2)kafka在1.x及以后版本保证数据单分区有序,条件如下:
(2)开启幂等性
max.in.flight.requests.per.connection需要设置小于等于5。
(1)未开启幂等性
max.in.flight.requests.per.connection需要设置为1。
原因说明:因为在kafka1.x以后,启用幂等后,kafka服务端会缓存producer发来的最近5个request的元数据,
故无论如何,都可以保证最近5个request的数据都是有序的。
在这里插入图片描述

Kafka Broker

4.1 Kafka Broker 工作流程

4.1.1 Zookeeper 存储的 Kafka 信息

(1)启动 Zookeeper 客户端。

bin/zkCli.sh 

(2)通过 ls 命令可以查看 kafka 相关信息。

ls /kafka 

Zookeeper中存储的Kafka 信息
在这里插入图片描述

4.1.2 Kafka Broker 总体工作流程

Kafka Broker总体工作流程
在这里插入图片描述

4.1.3 Broker 重要参数

参数名称描述
replica.lag.time.max.msISR 中,如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR。该时间阈值,默认 30s
auto.leader.rebalance.enable默认是 true。 自动 Leader Partition 平衡。
leader.imbalance.per.broker.percentage默认是 10%。每个 broker 允许的不平衡的 leader的比率。如果每个 broker 超过了这个值,控制器会触发 leader 的平衡。
leader.imbalance.check.interval.seconds默认值 300 秒。检查 leader 负载是否平衡的间隔时间。
log.segment.bytesKafka 中 log 日志是分成一块块存储的,此配置是指 log 日志划分 成块的大小,默认值 1G。
log.index.interval.bytes默认 4kb,kafka 里面每当写入了 4kb 大小的日志(.log),然后就往 index 文件里面记录一个索引。
log.retention.hoursKafka 中数据保存的时间,默认 7 天。
log.retention.minutesKafka 中数据保存的时间,分钟级别,默认关闭。
log.retention.msKafka 中数据保存的时间,毫秒级别,默认关闭。
log.retention.check.interval.ms检查数据是否保存超时的间隔,默认是 5 分钟。
log.retention.bytes默认等于-1,表示无穷大。超过设置的所有日志总大小,删除最早的 segment。
log.cleanup.policy默认是 delete,表示所有数据启用删除策略; 如果设置值为 compact,表示所有数据启用压缩策略。
num.io.threads默认是 8。负责写磁盘的线程数。整个参数值要占总核数的 50%。
num.replica.fetchers副本拉取线程数,这个参数占总核数的 50%的 1/3
num.network.threads默认是 3。数据传输线程数,这个参数占总核数的50%的 2/3 。
log.flush.interval.messages强制页缓存刷写到磁盘的条数,默认是 long 的最大值,9223372036854775807。一般不建议修改,交给系统自己管理。
log.flush.interval.ms每隔多久,刷数据到磁盘,默认是 null。一般不建议修改,交给系统自己管理。

4.2 kafka副本

4.2.1 副本基本信息

(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 副本同步时,延迟过多的副本。

4.3 高效读写数据

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

Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,
为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这
与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
在这里插入图片描述
4)页缓存 + 零拷贝技术
页缓存 + 零拷贝技术
零拷贝
:Kafka的数据加工处理操作交由Kafka生产者和Kafka消费者处理。Kafka Broker应用层不关心存储的数据,所以就不用走应用层,传输效率高。
PageCache页缓存:Kafka重度依赖底层操作系统提供的PageCache功 能。当上层有写操作时,操作系统只是将数据写入
PageCache。当读操作发生时,先从PageCache中查找,如果找不到,再去磁盘中读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。

kafka消费者

5.1 kafka消费方式

kafka消费方式
在这里插入图片描述

5.2 kafka消费者工作流程

消费者总体工作流程

Kafka 消费者总体工作流程
在这里插入图片描述

5.2.2 消费者组原理

消费者组
Consumer Group(CG):消费者组,由多个consumer组成。形成一个消费者组的条件,是所有消费者的groupid相同。
• 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。
• 消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
在这里插入图片描述
消费者组
在这里插入图片描述
消费者组初始化流程
在这里插入图片描述
消费者组详细消费流程
在这里插入图片描述

5.2.3 消费者重要参数

参数名称描述
bootstrap.servers向 Kafka 集群建立初始连接用到的 host/port 列表。
key.deserializer 和value.deserializer指定接收消息的 key 和 value 的反序列化类型。一定要写全类名。
group.id标记消费者所属的消费者组。
enable.auto.commit默认值为 true,消费者会自动周期性地向服务器提交偏移量。
auto.commit.interval.ms如果设置了 enable.auto.commit 的值为 true, 则该值定义了消费者偏移量向 Kafka 提交的频率,默认 5s。
auto.offset.reset当 Kafka 中没有初始偏移量或当前偏移量在服务器中不存在(如,数据被删除了),该如何处理? earliest:自动重置偏移量到最早的偏移量。 latest:默认,自动重置偏移量为最新的偏移量。 none:如果消费组原来的(previous)偏移量不存在,则向消费者抛异常。 anything:向消费者抛异常。
offsets.topic.num.partitions__consumer_offsets 的分区数,默认是 50 个分区
heartbeat.interval.msKafka 消费者和 coordinator 之间的心跳时间,默认 3s。 该条目的值必须小于 session.timeout.ms ,也不应该高于 session.timeout.ms 的 1/3。
session.timeout.msKafka 消费者和 coordinator 之间连接超时时间,默认 45s。超过该值,该消费者被移除,消费者组执行再平衡。
max.poll.interval.ms消费者处理消息的最大时长,默认是 5 分钟。超过该值,该消费者被移除,消费者组执行再平衡。
fetch.min.bytes默认 1 个字节。消费者获取服务器端一批消息最小的字节数。
fetch.max.wait.ms默认 500ms。如果没有从服务器端获取到一批数据的最小字节数。该时间到,仍然会返回数据。
fetch.max.bytes默认 Default: 52428800(50 m)。消费者获取服务器端一批消息最大的字节数。如果服务器端一批次的数据大于该值(50m)仍然可以拉取回来这批数据,因此,这不是一个绝对最大值。一批次的大小受 message.max.bytes (broker config)or max.message.bytes (topic config)影响。
max.poll.records一次 poll 拉取数据返回消息的最大条数,默认是 500 条。

5.3 消费者 API

5.3.1 独立消费者案例(订阅主题)

1)需求:
创建一个独立消费者,消费 first 主题中数据
在这里插入图片描述
注意:在消费者 API 代码中必须配置消费者组 id。命令行启动消费者不填写消费者组id 会被自动填写随机的消费者组 id。
2)实现步骤
(1)创建包名:com.atguigu.kafka.consumer
(2)编写代码

package com.cjdx.consumer;



import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;

/**
* @author: 赤脚大仙
* @date: 2022/10/11 22:02
* @description:
*/
public class  CustomerConsumer {

   public static void main(String[] args) {
       //创建消费者的配置对象
       Properties properties = new Properties();

       //给消费者配置对象添加参数
       properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.40.128:9092");
       //配置序列化
       properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
       properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

       //配置消费者组(组名任意起名)
       properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test5");

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

       // 注册要消费的主题(可以消费多个主题)
       ArrayList<String> topics = new ArrayList<>();
       topics.add("first");
       kafkaConsumer.subscribe(topics);

       // 拉取数据打印
       while (true){
           // 设置 1s 中消费一批数据
           ConsumerRecords<String, String> consumerRecords =
                   kafkaConsumer.poll(Duration.ofSeconds(1));
           // 打印消费到的数据
           for (ConsumerRecord consumerRecord : consumerRecords){
               System.out.println(consumerRecord);
           }
           kafkaConsumer.commitAsync();
       }
   }
}

5.3.2 独立消费者案例(订阅分区)

1)需求:创建一个独立消费者,消费 first 主题 0 号分区的数据。
在这里插入图片描述
2)实现步骤
(1)代码编写。

package com.cjdx.consumer;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.protocol.types.Field;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;

/**
 * @author: 赤脚大仙
 * @date: 2022/10/18 22:05
 * @description:创建一个独立消费者,消费 first 主题 0 号分区的数据。
 */
public class CustomConsumerPartition {
    public static void main(String[] args) {
        Properties properties = new Properties();

        //服务器地址
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.40.128:9092");
        //配置序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

        //配置消费者组  名字自定义
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        //消费某个主题的某个分区数据
        ArrayList<TopicPartition> topicPartitions = new ArrayList<>();

        topicPartitions.add(new TopicPartition("first",0));
        kafkaConsumer.assign(topicPartitions);

        while (true) {
            ConsumerRecords<String, String> consumerRecords =
                    kafkaConsumer. poll(Duration.ofSeconds(1));
            for (ConsumerRecord<String,String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }

    }
}

5.5 offset 位移

5.5.1 offset 的默认维护位置

offset的默认维护位置
在这里插入图片描述
__consumer_offsets 主题里面采用 key 和 value 的方式存储数据。key 是 group.id+topic+分区号,value 就是当前 offset 的值。每隔一段时间,kafka 内部会对这个 topic 进行compact,也就是每个 group.id+topic+分区号就保留最新数据。

5.5.2 自动提交 offset

自动提交offset
为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交offset的功能。
自动提交offset的相关参数:
⚫ enable.auto.commit:是否开启自动提交offset功能,默认是true
⚫ auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s
在这里插入图片描述
在这里插入图片描述

package com.atguigu.kafka.consumer; 
 
import org.apache.kafka.clients.consumer.ConsumerConfig; 
import org.apache.kafka.clients.consumer.ConsumerRecord; 
import org.apache.kafka.clients.consumer.ConsumerRecords; 
import org.apache.kafka.clients.consumer.KafkaConsumer; 
 
import java.util.Arrays; 
import java.util.Properties; 
 
public class CustomConsumerAutoOffset { 
 
 public static void main(String[] args) { 
 
 // 1. 创建 kafka 消费者配置类 
 Properties properties = new Properties(); 
 
 // 2. 添加配置参数 
 // 添加连接 
 properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, 
"hadoop102:9092"); 
 
 // 配置序列化 必须 
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, 
"org.apache.kafka.common.serialization.StringDeserializer"); 
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); 
 
 // 是否自动提交 offset 
 properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, 
false); 
 

 //3. 创建 kafka 消费者 
 KafkaConsumer<String, String> consumer = new 
KafkaConsumer<>(properties); 
 
 //4. 设置消费主题 形参是列表 
 consumer.subscribe(Arrays.asList("first")); 
 
 //5. 消费数据 
 while (true){ 
 
 // 读取消息 
 ConsumerRecords<String, String> consumerRecords = 
consumer.poll(Duration.ofSeconds(1)); 
 
 // 输出消息 
 for (ConsumerRecord<String, String> consumerRecord : 
consumerRecords) { 
 System.out.println(consumerRecord.value()); 
 } 
 
 // 同步提交 offset 
 consumer.commitSync(); 

 } 
 } 
} 

2)异步提交 offset
虽然同步提交 offset 更可靠一些,但是由于其会阻塞当前线程,直到提交成功。因此吞吐量会受到很大的影响。因此更多的情况下,会选用异步提交 offset 的方式。
以下为异步提交 offset 的示例:

package com.atguigu.kafka.consumer; 
 
import org.apache.kafka.clients.consumer.*; 
import org.apache.kafka.common.TopicPartition; 
 
import java.util.Arrays; 
import java.util.Map; 
import java.util.Properties; 
 
public class CustomConsumerByHandAsync { 
 
 public static void main(String[] args) { 
 
 // 1. 创建 kafka 消费者配置类 
 Properties properties = new Properties(); 
 
 // 2. 添加配置参数
 // 添加连接 
 properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, 
"hadoop102:9092"); 
 
 // 配置序列化 必须 
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, 
"org.apache.kafka.common.serialization.StringDeserializer"); 
 
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, 
"org.apache.kafka.common.serialization.StringDeserializer"); 
 
 // 配置消费者组 
 properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); 
 
 // 是否自动提交 offset 
 properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, 
"false"); 
 
 //3. 创建 Kafka 消费者 
 KafkaConsumer<String, String> consumer = new 
KafkaConsumer<>(properties); 
 
 //4. 设置消费主题 形参是列表 
 consumer.subscribe(Arrays.asList("first")); 
 
 //5. 消费数据 
 while (true){ 
 
 // 读取消息 
 ConsumerRecords<String, String> consumerRecords = 
consumer.poll(Duration.ofSeconds(1)); 
 
 // 输出消息 
 for (ConsumerRecord<String, String> consumerRecord : 
consumerRecords) { 
 System.out.println(consumerRecord.value()); 
 } 
 
 // 异步提交 offset 

 consumer.commitAsync(); 
 } 
 } 
} 

5.5.6 漏消费和重复消费

重复消费:已经消费了数据,但是 offset 没提交。
漏消费:先提交 offset 后消费,有可能会造成数据的漏消费。

重复消费与漏消费
在这里插入图片描述
思考:怎么能做到既不漏消费也不重复消费呢?详看消费者事务。

5.6 生产经验——消费者事务

生产经验——消费者事务
如果想完成Consumer端的精准一次性消费,那么需要Kafka消费端将消费过程和提交offset过程做原子绑定。此时我们需要将Kafka的offset保存到支持事务的自定义介质(比 如MySQL)。这部分知识会在后续项目部分涉及。
在这里插入图片描述

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

生产经验——数据积压(消费者如何提高吞吐量)
在这里插入图片描述
在这里插入图片描述

  • 21
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值