【kafka】学习笔记(一)

学习笔记

  • 一、概述
    • 1.1、MQ
    • 1.2、kafka
    • 1.3、消息队列的两种模式
      • 1.3.1、点对点
      • 1.3.2、订阅与发布
    • 1.4、Kafka基础架构
  • 二、安装
    • 2.1、 集群规划
    • 2.2、 下载安装
    • 2.3、 集群启动脚本
  • 三、Kafka命令行操作
    • 3.1、主题命令行操作
    • 3.2、生产者命令行操作
    • 3.3、消费者命令行操作
  • 四、 Kafka 生产者
    • 4.1、生产者消息发送流程
      • 4.1.1 发送原理
    • 4.2、 发送实现
      • 4.2.1、不带回调异步发送函数
      • 4.2.2、带回调函数的异步发送
      • 4.2.3、 同步发送
    • 4.3、 生产者分区
      • 4.3.1、分区的好处
      • 4.3.2、分区的策略
      • 4.3.3、自定义分区
    • 4.4、生产者提高吞吐量
    • 4.5、生产者数据可靠性
    • 4.6、数据去重
      • 4.6.1、幂等性
      • 4.6.2、生产者事务

一、概述

1.1、MQ

MQ(message queue),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了MQ之后,消息发送上游只需要依赖MQ,不用依赖其他服务。

1.2、kafka

在kafka3.x开始可以不使用Zookeeper 自己可以独立使用

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

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

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

异步通信:允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们

1.3、消息队列的两种模式

1.3.1、点对点

【kafka】学习笔记(一)_第1张图片

1.3.2、订阅与发布

【kafka】学习笔记(一)_第2张图片

1.4、Kafka基础架构

【kafka】学习笔记(一)_第3张图片

二、安装

2.1、 集群规划

kafka01 kafka02 kafka03
zk zk zk
kafka kafka kafka
192.168.3.34 192.168.3.35 192.168.3.36

2.2、 下载安装

官方下载地址

下载完上传到服务器(我自己在usr下创建一个叫soft的文件夹上传到这里)

解压

cd /usr/soft
tar -zxvf kafka_2.12-3.0.0.tgz

改解压后文件的名

mv kafka_2.12-3.0.0/ kafka

进入到/opt/module/kafka 目录,修改配置文件server.properties

  1. broker.id
  2. log.dirs
  3. zookeeper.connect
#broker 的全局唯一编号,不能重复,只能是数字。
broker.id=0
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘 IO 的线程数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/usr/soft/kafka/datas
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
# 每个 topic 创建时的副本数,默认时 1 个副本
offsets.topic.replication.factor=1
#segment 文件保留的最长时间,超时将被删除
log.retention.hours=168
#每个 segment 文件的大小,默认最大 1G
log.segment.bytes=107374182
# 检查过期数据的时间,默认 5 分钟检查一次是否数据过期
log.retention.check.interval.ms=300000
#配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=192.168.3.34:2181,192.168.3.35:2181,192.168.3.36:2181/kafka

然后kafka02、kafka02执行上面相同步骤 要改broker.id

配置环境变量/etc/profile

#KAFKA_HOME
export KAFKA_HOME=/usr/soft/kafka
export PATH=$PATH:$KAFKA_HOME/bin
#改完执行
source /etc/profile

启动kafaka前要启动zookeeper
zookeeeper安装教程

zkServer.sh start
zkServer.sh status
#在kafka根目录启动
bin/kafka-server-start.sh -daemon  config/server.properties

输入jps查看启动状态

jps

如果有kafka则启动成功
【kafka】学习笔记(一)_第4张图片

2.3、 集群启动脚本

#! /bin/bash
case $1 in
"start"){
 for i in master slave1 slave2
 do
 echo " --------启动 $i Kafka-------"
 ssh $i "/usr/soft/kafka/bin/kafka-server-start.sh -daemon /usr/soft/kafka/config/server.properties"
 done
};;
"stop"){
 for i in master slave1 slave2
 do
 echo " --------停止 $i Kafka-------"
 ssh $i "/usr/soft/kafka/bin/kafka-server-stop.sh "
 done
};;
esac

三、Kafka命令行操作

3.1、主题命令行操作

查看当前服务器中的所有 topic

bin/kafka-topics.sh --bootstrap-server 192.168.3.34:9092 --list

创建 topic

#选项说明:
#--topic 定义 topic 名
#--replication-factor 定义副本数
#--partitions 定义分区数
bin/kafka-topics.sh --bootstrap-server 192.168.3.34:9092 --create --partitions 1 --replication-factor 3 --topic first

查看主题的详情

bin/kafka-topics.sh --bootstrap-server 192.168.3.34:9092 --describe --topic first

修改分区数(分区数只能增加,不能减少)

bin/kafka-topics.sh --bootstrap-server 192.168.3.34:9092 --alter --topic first --partitions 3

删除 topic

bin/kafka-topics.sh --bootstrap-server 192.168.3.34:9092 --delete --topic first

3.2、生产者命令行操作

发送消息

bin/kafka-console-producer.sh --bootstrap-server 192.168.3.34:9092 --topic first
>hello world

3.3、消费者命令行操作

消费消息

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

把主题中所有的数据都读取出来(包括历史数据)

bin/kafka-console-consumer.sh --bootstrap-server 192.168.3.34:9092 --from-beginning --topic first

四、 Kafka 生产者

4.1、生产者消息发送流程

4.1.1 发送原理

在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程。在 main 线程
中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator,
Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka Broker。
【kafka】学习笔记(一)_第5张图片

4.2、 发送实现

pom引入

 <dependency>
    <groupId>org.apache.kafkagroupId>
    <artifactId>kafka-clientsartifactId>
    <version>3.0.0version>
dependency>

4.2.1、不带回调异步发送函数

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;

public class CustomProducer {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建 kafka 生产者的配置对象
        Properties properties = new Properties();
        // 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "master:9092,slave1:9092,slave1: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());
        // 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","hello " + i));
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
}

在这里插入图片描述

4.2.2、带回调函数的异步发送

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

【kafka】学习笔记(一)_第6张图片

消息发送失败会自动重试,不需要我们在回调函数中手动重试

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

import java.util.Properties;
public class CustomProducerCallback {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建 kafka 生产者的配置对象
        Properties properties = new Properties();
        // 2. 给 kafka 配置对象添加配置信息
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"master:9092,slave1:9092,slave1: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());
        // 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", "hello " + i), new Callback() {
                // 该方法在 Producer 收到 ack 时调用,为异步调用
                // 消息发送失败会自动重试,不需要我们在回调函数中手动重试
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null) {
                        // 没有异常,输出信息到控制台
                        System.out.println(" 主题: " + metadata.topic() + "->" + "分区:" + metadata.partition());
                    } else {
                        // 出现异常打印
                        exception.printStackTrace();
                    }
                }
            });
            // 延迟一会会看到数据发往不同分区
            Thread.sleep(2);
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
}

4.2.3、 同步发送

【kafka】学习笔记(一)_第7张图片

4.3、 生产者分区

4.3.1、分区的好处

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

4.3.2、分区的策略

  1. 指明partition的情况下,直接将指明的值作为partition值;例如partition=0,所有数据写入分区0
  2. 没有指明partition值但有key的情况下,将key的hash值与topic的partition数进行取余得到partition值;例如:key1的hash值=5, key2的hash值=6 ,topic的partition数=2,那么key1 对应的value1写入1号分区,key2对应的value2写入0号分区。
  3. 既没有partition值又没有key值的情况下,Kafka采用Sticky Partition(黏性分区器),会随机选择一个分区,并尽可能一直使用该分区,待该分区的batch已满或者已完成,Kafka再随机一个分区进行使用(和上一次的分区不同)。例如:第一次随机选择0号分区,等0号分区当前批次满了(默认16k)或者linger.ms设置的时间到, Kafka再随机一个分区进行使用(如果还是0会继续随机)。

4.3.3、自定义分区

自定义分区文件

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
 * 1. 实现接口 Partitioner
 * 2. 实现 3 个方法:partition,close,configure
 * 3. 编写 partition 方法,返回分区号
 */
public class MyPartitioner implements Partitioner {
    /**
     * 返回信息对应的分区
     * @param topic 主题
     * @param key 消息的 key
     * @param keyBytes 消息的 key 序列化后的字节数组
     * @param value 消息的 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();
        // 创建 partition
        int partition;
        // 判断消息是否包含 atguigu
        if (msgValue.contains("nacl")){
            partition = 0;
        }else {
            partition = 1;
        }
        // 返回分区号
        return partition;
    }
    // 关闭资源
    @Override
    public void close() {

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

    }
}

使用分区:

// 添加自定义分区器      
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"MyPartitioner 的全类名");   

4.4、生产者提高吞吐量

  • batch.size:批次大小,默认16k
  • linger.ms:等待时间,默认0ms
  • compression.type:压缩snappy ,默认没有
  • RecordAccumulator:缓冲区大小,默认32m
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
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, "master:9092,slave1:9092,slave1: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","world " + i));
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
}

4.5、生产者数据可靠性

ack 应答:
0:生产者发送过来的数据,不需要等数据落盘应答。可靠性差,效率高;
1:生产者发送过来的数据,Leader收到数据后应答。可靠性中等,效率中等;
-1(all):生产者发送过来的数据,Leader和ISR队列里面的所有节点收齐数据后应答。可靠性高,效率低;

Leader维护了一个动态的in-sync replica set(ISR),意为和Leader保持同步的Follower+Leader集合(leader:0,isr:0,1,2)。如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值由replica.lag.time.max.ms参数设定,默认30s。例如2超时,(leader:0, isr:0,1)。这样就不用等长期联系不上或者已经故障的节点。

数据完全可靠条件 = ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

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;
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, "master:9092,slave1:9092,slave1: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","nacl " + i));
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
}

4.6、数据去重

重复数据的判断标准:具有相同主键的消息提交时,Broker只会持久化一条。
PID是Kafka每次重启都会分配一个新的;
Partition 表示分区号;
Sequence Number是单调自增的。

4.6.1、幂等性

幂等性就是指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证了不重复。
所以幂等性只能保证的是在单分区单会话内不重复

开启参数 enable.idempotence 默认为 truefalse 关闭。

4.6.2、生产者事务

【kafka】学习笔记(一)_第9张图片

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;
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,"master:9092,slave1:9092,slave1: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", "hello " + i));
            }
            //int i = 1 / 0;
            // 提交事务
            kafkaProducer.commitTransaction();
        } catch (Exception e) {
            // 终止事务
            kafkaProducer.abortTransaction();
        } finally {
            // 5. 关闭资源
            kafkaProducer.close();
        }
    }
}

你可能感兴趣的:(笔记,Kafka,kafka,学习,java)