http://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202005%2F08%2F20200508102713_L8aCB.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1649077062&t=40b7b14053ef546a57de4934b9442cbe",

【All】Kafka从抬脚到入门

一、Kafka简介

1.1、定义

  • 旧定义
    Kafka 是一个分布式的基于发布/订阅模式的消息队列。
  • 新定义
    Kafka 是一个开源的分布式事件流平台,用于数据管道、流分析、数据集成和关键任务的应用。

1.2、使用场景

主要用于大数据实时处理领域。

  • 缓冲: 有助于控制和优化数据流经过系统的速度。
  • 消峰: 在访问量剧增的情况下,保证应用不会因突发的超负荷请求而崩溃。
  • 解耦: 保证程序的可扩展性。
  • 异步通信:

1.3、使用模式

  1. 点对点: 消费者主动拉取消息,消息收到后清除消息。
  2. 发布/订阅模式: 可以有多个topic主题;消息被消费者消费后不会删除;每个消费者相互独立,都可以消费到数据。

1.4、基础架构及概念

最简单的流程:生产者 -> 消息主题 -> 消费者。

考虑到一台主机可能存不下 海量的消息 数据,引入分区,将topic进行 分区
考虑到分区数据的高可用,引入副本,可为每个分区创建副本;
考虑到一个消费者消费信息的瓶颈,引入 消费者组

  1. Producer: 消息生产者,向 Kafka broker 发送消息。
  2. Consumer: 消息消费者,从 Kafka broker 消费消息。
  3. Consumer Group(CG):消费者组,由多个Consumer组成。
    消费者组与消费者组之间对消息的消费互不影响;
    消费者组内的消费者之间,不能消费同一个Topic的同一分区数据,即一个Topic的一个分区只能由同一消费者组内的一个消费者消费。
  4. Broker: 一台Kafka服务器。一个broker可容纳多个topic。一个集群由多个broker组成。
  5. Topic: 消息主题,Kafka将一组消息抽象归纳为一个主题。主题就是对消息的分类,生产者将消息发送到特定的主题,消费者订阅主题或是主题的某些分区进行消费。
  6. Parition: 一个topic可分为多个Parition,每个partition是一个有序队列。
  7. Replica: 分区副本。每个Parition可设置若干副本。 每个分区的所有副本有且只有一个Leader,其他为Follower。
  8. Leader: 分区Leader副本,生产者发送数据的对象,以及消费者消费数据的对象都是Leader。
  9. Follower:分区Leader副本,实时从Leader中同步数据,当Leader故障时,某个follower将成为新的Leader。
  10. Message:消息。消息是Kafka通信的基本单位,由固定长度的消息头和可变长度的消息体构成。在java重新实现的客户端中,Message被称之为Record。

二、Kafka安装

2.1、准备环境

  1. Linux操作系统
  2. Java运行环境(1.8或以上)
  3. zookeeper 集群环境,可参照 Zookeeper集群部署
  4. 服务器列表:

2.2、准备安装介质

分别登录server1、server2、server3执行,操作、配置相同:

##更新或安装wget命令
yum -y install wget
##创建安装目录
mkdir -p /usr/local/services/kafka
##获取安装包kafka_2.12-3.0.0.tgz
wget http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/3.0.0/kafka_2.12-3.0.0.tgz
##解压缩kafka_2.12-3.0.0.tgz
tar -zxvf kafka_2.12-3.0.0.tgz

2.3、修改环境配置

  1. 分别登录server1、server2、server3添加主机名,操作、配置相同:
vi /etc/hosts

##添加如下内容
168.5.7.75 server1
168.5.7.76 server2
168.5.7.77 server3

:wq
  1. 分别登录server1、server2、server3添加kafka环境变量,操作、配置相同
vi ~/.bash_profile

##添加如下内容
export KAFKA_HOME=/usr/local/services/kafka/kafka_2.12-3.0.0
export PATH=$PATH:$KAFKA_HOME/bin

:wq

source /etc/profile

说明:再任一路径下输入 kafka 按 Tab 键后会补全 Kafka 相关脚本.sh,即Kafka 环境变量配置成功。 因 Kafka 脚本运行时会加载 /config 路径下的相关配置文件,故当不在 Kafka 安装目录 bin 下执行相关脚本时, 需要指定配置文件绝对路径

2.4、修改Kafka配置

登录server1执行操作:

cd $KAFKA_HOME/config
cp server.properties server.properties.$(date +%Y%m%d)
vi server.properties

## 修改文件内配置如下
## broker 的全局唯一编号,不能重复,只能是数字
broker.id=1
## kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/data/kafka-logs
port=9093
## 配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=server1:2181,server2:2181,server3:2181/kafka

:wq

登录server2执行操作:

cd $KAFKA_HOME/config
cp server.properties server.properties.$(date +%Y%m%d)
vi server.properties

## 修改文件内配置如下
## broker 的全局唯一编号,不能重复,只能是数字
broker.id=2
## kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/data/kafka-logs
port=9093
## 配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=server1:2181,server2:2181,server3:2181/kafka

:wq

登录server3执行操作:

cd $KAFKA_HOME/config
cp server.properties server.properties.$(date +%Y%m%d)
vi server.properties

## 修改文件内配置如下
## broker 的全局唯一编号,不能重复,只能是数字
broker.id=3
## kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/data/kafka-logs
port=9093
## 配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=server1:2181,server2:2181,server3:2181/kafka

:wq

2.5、Kafka服务启停

## 默认 zookeeper 集群启动成功,查看命令:./zkServer.sh status

## 启动命令
sh  ${KAFKA_HOME}/bin/kafka-server-start.sh -daemon ${KAFKA_HOME}/config/server.properties

## 停止命令
sh ${KAFKA_HOME}/bin/kafka-server-stop.sh 

2.6、Kafka集群启停脚本

说明:本脚本基于SSH服务器免密登录,如集群未配置SSH,参照:《SSH安装配置》 。

  • 启动脚本:start-kafka-cluster.sh
#!/bin/bash
brokers="server1 server2 server3"
KAFKA_HOME="/usr/local/services/kafka/kafka_2.11-2.3.0"
KAFKA_NAME="kafka_2.11-2.3.0"

echo "INFO : Begin to start kafka cluster ..."

for broker in $brokers
do
  echo "INFO : Starting ${KAFKA_NAME} on ${broker} ..."
  ssh ${broker} -C "source /etc/profile; sh ${KAFKA_HOME}/bin/kafka-server-start.sh -daemon ${KAFKA_HOME}/config/server.properties"
  if [[ $? -eq 0 ]]; then
      echo "INFO:[${broker}] Start successfully"
  fi
done
echo "INFO:Kafka cluster starts successfully !"

为脚本添加执行权限:

chmod a+x start-kafka-cluster.sh
  • 停止脚本:stop-kafka-cluster.sh
#!/bin/bash
brokers="server1 server2 server3"
KAFKA_HOME="/usr/local/services/kafka/kafka_2.11-2.3.0"
KAFKA_NAME="kafka_2.11-2.3.0"

echo "INFO : Begin to stop kafka cluster ..."
for broker in $brokers
do
  echo "INFO : Shut down ${KAFKA_NAME} on ${broker} ..."
  ssh ${broker} "source /etc/profile;bash ${KAFKA_HOME}/bin/kafka-server-stop.sh"
  if [[ $? -ne 0 ]]; then
      echo "INFO : Shut down ${KAFKA_NAME} on ${broker} is down"
  fi
done

echo "INFO : kafka cluster shut down completed!"

为脚本添加执行权限:

chmod a+x stop-kafka-cluster.sh

三、Kafka-Shell API

3.1、主题相关

${KAFKA_HOME}/bin/kafka-topics.sh
## 参数详解
## --bootstrap-server <io:port>  指定连接 kafka-broker 节点
## --topic <topic_name>          指定操作的 主题
## --create                      指定对主题的操作:新增
## --delete                      指定对主题的操作:删除
## --alter                       指定对主题的操作:编辑
## --list                        指定对主题的操作:查看列表
## --describe                    指定对主题的操作:查看详情
## --partitions <num>            设置主题分区数。
## -- replication-factor <num>   设置分区副本数。 
## --config <key-value>          更改系统默认设置

## 案例:查看集群中所有 topic
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --list
## 案例:创建一个名为 hello 的主题,其分区数为:1,副本数为:3。
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --create --partitions 1 --replication-factor 3 --topic hello
## 案例:查看主题 hello 详情
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --describe  --topic hello
## 案例:修改主题 hello 分区数(注意:通过alter命令只能增加分区数,不能减少)
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --alter --topic hello --partitions 3
##  案例:删除主题 hello
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --delete --topic hello

3.2、生产者相关

${KAFKA_HOME}/bin/kafka-console-producer.sh
## 参数详解
## --bootstrap-server <io:port>  指定连接 kafka-broker 节点
## --topic <topic_name>          指定操作的 主题

##  案例:生产者连接 broker 生产消息
kafka-console-producer.sh --bootstrap-server 168.5.7.75:9092 --topic hello
## 进入命令行后发送消息即可
>

3.3、消费者相关

${KAFKA_HOME}/bin/kafka-console-consumer.sh
## 参数详解
## --bootstrap-server <io:port>  指定连接 kafka-broker 节点
## --topic <topic_name>          指定操作的 主题
## –from-beginning               从头开始消费。
## –group <group_id>             指定消费者组名称。

##  案例:消费者连接 broker 消费消息,默认只消费连接之后生产的新消息
kafka-console-consumer.sh --bootstrap-server 168.5.7.75:9092 --topic hello
##  案例:消费者连接 broker 消费消息,指定从主题中的第一条消息开始消费
kafka-console-consumer.sh --bootstrap-server 168.5.7.75:9092 --from-beginning --topic hello

四、Kafka-Java API

4.1、添加依赖

<dependencies>
     <dependency>
         <groupId>org.apache.kafka</groupId>
         <artifactId>kafka-clients</artifactId>
         <version>3.0.0</version>
     </dependency>
</dependencies>

4.2、生产者生产消息

4.2.1、同步发送

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

        // 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
        Properties properties = new Properties();
        //服务信息
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"47.106.86.64:9092");
        //配置序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        // 2. 创建 kafka 生产者的配置对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);

        // 3. 创建 kafka 生产者对象
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord("first", "one" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e == null) {
                        System.out.println( "分区 : " + recordMetadata.partition() + " 主题: " + recordMetadata.topic() );
                    }

                }
            // .get() 表示同步发送,先把DQueue发送至Broker并ack确认后,在接受新的消息
            }).get();
        }
        kafkaProducer.close();
    }
}

4.2.2、普通异步发送

public class CustomProducer {

    public static void main(String[] args) {

        // 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
        Properties properties = new Properties();
        //服务信息
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"47.106.86.64:9092");
        //配置序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        // 2. 创建 kafka 生产者的配置对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);

        // 3. 创建 kafka 生产者对象
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord("first", "one" + i));
        }
        kafkaProducer.close();
    }
}

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


public class CustomProducer {

    public static void main(String[] args) {

        // 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
        Properties properties = new Properties();
        //服务信息
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"47.106.86.64:9092");
        //配置序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        // 2. 创建 kafka 生产者的配置对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);

        // 3. 创建 kafka 生产者对象
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord("first", "one" + i), new Callback() {
                // 回调函数会在 producer 收到 ack 时调用,为异步调用。
                // 消息发送失败会自动重试,不需要我们在回调函数中手动重试。
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e == null) {
                        System.out.println( "分区 : " + recordMetadata.partition() + " 主题: " + recordMetadata.topic() );
                    }
                }
            });
        }
        kafkaProducer.close();
    }
}

五、Kafka详解

5.1、生产者

5.1.1、生产者 生产消息 流程图

  • 主要模块

    • main线程
      负责将消息发送至 RecordAccumulator。

    • RecordAccumulator(消息累加器)
      消息累加器中的双端队列(DQueue)个数和主题的分区数一一对应;

    • sender线程:负责将消息从 RecordAccumulator 发送至 Broker。
      sender与Broker通信默认缓存五个请求( max.in.flight.requests.per.connection );

  • 主要流程

  1. 生产者调用 send() 方法向 Broker 发送一条消息 msg;
  2. 消息 msg 被 main线程 处理,主要经过:
    -> 过滤器(ProducerInterceptor):
    -> 序列化器:
    -> 分区器:
  3. 经过 main线程 处理后,消息被存放至 消息累加器(多个双端队列);
  4. 队列中数据达到 batch.size 或超过 linger.ms 设置的等待时长,触发sender发送;
  5. sender将DQueue中的数据发送到Broker,并等待Broker的ack,ack超时会触发(retries次)重试;
  6. Broker返回ack成功后,删除累加器中对应的消息批次将被删除。

5.1.2、拦截器

  • 拦截器接口说明
public interface ProducerInterceptor<K, V> extends Configurable {
    // 在消息分区前,修改消息内容、消息主题,或将消息放置延时队列等。
    ProducerRecord<K, V> onSend(ProducerRecord<K, V> record);
    // sender 收到 broker ack 后的回调方法,执行优先级大于 sender 自己的回调。
    void onAcknowledgement(RecordMetadata metadata, Exception exception);
    // 关闭拦截器时,进行资源释放
    void close();
}
  • 添加拦截器流程
// 1. 实现 ProducerInterceptor 接口,如:MyProducerInterceptor

// 2. 添加至 kafka 配置对象
 Properties properties = new Properties();
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,MyInterceptor.getClass.getName());

5.1.3、序列化器

5.1.4、分区器

5.1.4.1、分区器作用

为了合理使用Broker的资源存储消息,提高消息的并行计算消费,引入了 消息分区;
生产者连接 主题后,会为 主题的每个分区在RecordAccumulator(消息累加器)中创建一个对应的双端队列(DQueue)。
那生产者如何知道消息应该发送到哪个 消息分区?
这就是分区器的作用:分区器根据不同的分区策略,将被发送的消息放至主题的某一个双端队列(DQueue)中,再有 sender 将 DQueue 中的消息发送至 对应的 分区。

5.1.4.2、分区器类别

  1. DefaultPartitioner - 默认分区器
// 该分区器支持三种分区策略
// 1. 创建 ProducerRecord 时指定分区: partition 
public ProducerRecord(String topicName, Integer partition, K key, V value);

// 2. 当创建 ProducerRecord 时指定key,使用 key 的 hash 对分区数取余 作为: partition
public ProducerRecord(String topicName, K key, V value);

// 3. 当创建 ProducerRecord 时, partition 和 key 均未指定,使用随机黏性分区
public ProducerRecord(String topicName, V value);
  1. RoundRobinPartitioner - 轮询策略
  2. UniformStickyPartitioner - 随机黏性分区
    随机选择一个分区,并尽可能一直使用该分区,直到该 批次数据达到 batch.size 获取 linger.ms 被sender 发送后,再随机使用除此分区的其他分区。

5.1.4.3、自定义分区器

// 1. 实现分区器 Partitioner 接口,编写 partition() 分区逻辑。例:MyPartitioner

// 2.  在生产者的配置中添加分区器参数
Properties properties = new Properties();
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,MyPartitioner.class.getName());

5.1.5、消息累加器(RecordAccumulator)

  • 当linger.ms = 0时,sender会在消息累加器接收到一条消息时就将其发送到broker;
  • 当linger.ms > 0时,sender会在到达linger.ms时间或者数据量达到batch.size时将消息批量发送到broker。

通过这种批量机制,减少网络IO。提高了生产者的吞吐量。

累加器的存储形式为ConcurrentMap<TopicPartition, Deque<ProducerBatch>>

其中一个分区对应一个双端队列,队列中存储的是ProducerBatchbatch.size配置,数据量达到batch.size会创建新的ProducerBatch,并触发sender线程进行发送。

通过 buffer.memory 设置消息累加器的缓冲容量(默认32m)。如果生产者的发送速率大于sender发送的速率,消息就会堆满累加器。生产者就会阻塞或报错。报错取决于重试次数(retries,默认:int最大值)和重试间隔(retry.backoff.ms,默认:100ms)。

为例减少高流量时JVM对ProducerBatch的GC回收造成的性能损耗,Kafka引入 BufferPool 内存池来管理 ProducerBatch 的创建和回收。申请一个新的ProducerBatch空间时,调用 free.allocate(size, maxTimeToBlock)找内存池申请空间。

当单条消息大于batch.size时,会为其生成一个更大的ProducerBatch,发送完后由GC回收该内存空间,不再复用内存池

5.1.6、消息发送线程(Sender)

​ Sender线程发送数据失败后,会根据retry.backoff.msretries的配置进行失败重试。最多允许max.in.flight.requests.per.connection条无ack得消息存在,开启幂等性时,其取值范围为1-5

​ Sender 线程发送消息及接收消息都是基于java NIO的Selector

5.1.6、生产者性能调优

5.1.7、生产者性能调优

 // 1. 调整批次大小 batch.size。默认 16K,可设置为 32K。
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

// 2. 调整批次等待时间 linger.ms。默认 0 不等待,batch.size 参数将不生效,可设置为:5-100ms
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

// 3.调整缓冲区RecordAccumulator大小 buffer.memory。默认 32M,可设置为 64M。
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 67554432);

// 4.开启消息压缩 compression.type 。默认 none,可配置值 gzip、snappy、lz4 和 zstd
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");

// 以上为调优的方向,具体的参数设置需要结合实际的业务场景、消息种类进行压测来设置,才能达到好的效果。

5.2、Broker

5.3、消费者

posted @   DeepInThought  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示