大数据Spark实时处理--实时数据交换2(Kafka)

  • Kafka Producer API编程
  • 1)工作当中,使用Kafka的场景:和流处理进行关联/对接。也就是通过流处理系统(Spark Streaming\Flink\Storm流处理引擎)对接Kafka的数据,然后获取topic里的数据,进行消费和统计分析。这种场景一般是使用API的方式进行交互的。接下来,讲解使用API的方式来操作Kafka。
  • 2)按照之前的传统----->spark-log4j----->new----->Module----->Maven----->next----->Artifactld:log-kafka-api----->Modele name:log-kafka-api----->Finish
  • 在项目spark-log4j中,多了一个log-kafka-api的子工程。
  • 在主工程的pom.xml中,多了log-kafka-api的配置。
  • 3)使用Api进行开发,要将kafka的包导入
  • 主工程的pom.xml中,添加kafka版本号:
<properties>
    <kafka.version>2.5.0</kafka.version>
</properties>
  • 主工程的pom.xml中,导入kafka包:
<dependency>
     <groupId>org.apache.kafka</groupId>
     <artifactId>kafka-clients</artifactId>
     <version>${kafka.version}</version>
</dependency>
  • 子工程log-kafka-api的pom.xml中,添加dependency:
    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
        </dependency>
    </dependencies>
  • 更新Maven,导入依赖包:右键----->Maven----->Reload Project
  •  4)创建package及生产者class
  • 路径:C:\Users\jieqiong\IdeaProjects\spark-log4j\log-kafka-api\src\main\java
  • new package:com.imooc.bigdata.kafka----->new java class:ImoocKafkaProducer
  • 记住:程序不需要死记硬背,只需记住入口点KafkaProducer(可以看源码)
package com.imooc.bigdata.kafka;

/*
 * 此API的作用:是一个Kafka客户端向Kafka集群上的一个topic发送记录的
 */
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class ImoocKafkaProducer {

    public static void main(String args[]){

        // ***第2步:new一个properties
        // 输入new Properties().var
        Properties props = new Properties();
        // ***第7步:放信息put(key,value)
        // 目前Properties()里还是空的
        // 往Properties()放哪些信息呢?
        // 源码里无示例:KafkaProducer<String, String> producer

        // 作用:使用producer发送记录record,是使用strings类型和key\value模式。
        // ***第8步:kafka服务器地址
        props.put("bootstrap.servers","spark000:9092");
        // ***第9步:后续讲
        props.put("acks","all");
        // ***第10步:
        // key的序列化
        props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
        // ***第11步:
        // value的序列化
        props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");

        // 入口点:KafkaProducer<>()
        // ***第1步:new KafkaProducer<>()
        // 看源码:构造器KafkaProducer(Properties properties)
        // ***第3步:将上面new的properties的props放入()
        // ***第4步:new KafkaProducer<>(properties).var选择producer
        // 看源码:KafkaProducer<String, String>的KafkaProducer类,很重要:KafkaProducer<K, V> implements Producer<K, V>
        // KafkaProducer<K, V> implements Producer<K, V>实现了一个Producer接口,有事务的初始化/开始、发送偏移量offset、提交事务等。
        // KafkaProducer<K, V> implements Producer<K, V>,是一个Kafka客户端向Kafka集群上的一个topic发送记录的。
        // ***第5步:将KafkaProducer<Object, Object>中改为KafkaProducer<String, String>
        // ***第6步:KafkaProducer<>(props)改为:KafkaProducer<String, String>(props)
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        // ***第12步
        //开始发数据
        for (int i = 0; i < 100; i++){
            //构建一条记录,通过producer发过去,
            //send选择record;
            //在send()中new ProducerRecord<String, String>()
            //看ProducerRecord源码:在这里选择一个比较简单的ProducerRecord(String topic, K key, V value)
            producer.send(new ProducerRecord<String, String>("zhang-replicated-topic",i+"",i+""));
        }

        // ***第14步
        // 控制台输出信息
        System.out.println("消息发送完毕...");

        // ***第13步
        //关闭producer
        producer.close();
    }

}
  • 5)虚拟机开启producer和consumer
[hadoop@spark000 ~]$ kafka-console-producer.sh --bootstrap-server spark000:9092 --topic zhang-replicated-topic
>
[hadoop@spark000 ~]$ kafka-console-consumer.sh --bootstrap-server spark000:9092 --topic zhang-replicated-topic
  • 6)consumer端成功接收消息
  • 运行ImoocKafkaProducer.java程序,consumer自动接收1-99消息。

 

  • Kafka Consumer API编程
  • 1)创建消费者class
  • 路径:C:\Users\jieqiong\IdeaProjects\spark-log4j\log-kafka-api\src\main\java\com\imooc\bigdata\kafka
  • new java class ----->ImoocKafkaConsumer.java
  • 2)同理,这样的开发只需要记住入口点。
package com.imooc.bigdata.kafka;

/*
 * 此API作用:一个客户端从kafka集群消费/记录数据
 */

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

public class ImoocKafkaConsumer {

    public static void main(String args[]){

        // ***第2步
        // new Properties().var
        Properties properties = new Properties();
        // ***第5步
        // 作用:kafka consumer api 自动提交偏移量offset
        // 源码里无示例:KafkaProducer<String, String> consumer
        // ***第6步:kafka服务器地址
        properties.put("bootstrap.servers","spark000:9092");
        // ***第7步:consumer group.id
        properties.put("group.id","jieqiong-group");
        // ***第8步:自动提交偏移量
        properties.put("enable.auto.commit","true");
        //  ***第9步:key的反序列化
        // 因为在producer有一个序列化,所以在consumer有一个反序列化
        properties.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
        //  ***第10步:value的反序列化
        properties.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");

        // 入口点
        // ***第1步:new KafkaConsumer<>()
        // 看源码:无参构造public KafkaConsumer(Properties properties)
        // ***第3步:将properties放入new KafkaConsumer<>(properties)
        // ***第4步:new KafkaConsumer<>(properties).var选择consumer
        // 看源码:KafkaConsumer<K, V> implements Consumer<K, V>,一个客户端从kafka集群消费/记录数据
        // Consumer要比Producer复杂
        // 第19步:将KafkaConsumer<Object, Object>改为KafkaConsumer<String, String>
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        // ***第11步:开始consumer
        // 具体消费spark000上的哪一个topic
        // 选择consumer.subscribe(Collection<String> topics) void
        // ***第12步:往consumer.subscribe();中放一个集合Arrays.asList()
        // 若有多个集合,使用逗号隔开即可。
        // consumer去订阅zhang-replicated-topic这个topic
        consumer.subscribe(Arrays.asList("zhang-replicated-topic"));

        // ***第13步:
        // 对consumer来说,是源源不断的,只要producer产生数据传递到consumer,都要消费/记录到。
        while (true) {
            // ***第14步:如何记录/消费数据的?
            // consumer.选择poll(Duration)
            // 看源码:ConsumerRecords<K, V> poll(Duration var1); 这是Duration的一个时长。
            // ***第15步:选择毫秒consumer.poll(Duration.ofMillis(1000))
            // 在Duration的一个时长里,不要超时即可。
            // ***第16步:consumer.poll(Duration.ofMillis(1000)).var
            // ***第17步:将poll改为records
            // ***第18步:将ConsumerRecords<Object, Object>改为ConsumerRecords<String, String>
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

            // ***第20步:有了records,做输出
            // 使用ConsumerRecord做增强的for循环
            // ConsumerRecords是一个集合,是一堆的List<ConsumerRecord<K, V>
            // 注意循环里是单数的ConsumerRecord
            for (ConsumerRecord record : records ){
                // ***第21步:记录输出
                // 在控制台输出结果
                // key值为空,是一般我们不关注key值
                // value值,即在producer端输入的值
                // topic值,即默认的zhang-replicated-topic
                // 偏移量值,即从215开始
                // 我们看一下有没有分区信息,我们是单节点的,在这个topic里只有一个分区,分区副本系数也为1,所以这里的值为1.
                System.out.println(record.key() + "\t"
                + record.value() + "\t"
                        + record.topic() + "\t"
                        + record.offset() + "\t"
                        + record.partition()
                );
            }
        }
    }
}
  • 3)在producer处,输入数据,并在控制台接收
[hadoop@spark000 ~]$ kafka-console-producer.sh --bootstrap-server spark000:9092 --topic zhang-replicated-topic
>zhang
>jie
>qiong
>1
>2
>3

 

  • Kafka对接Flume收集的数据
  • 1)即将Kafka和Flume,这一链路打通。
  • 也就是说,在整个流处理的过程中,数据产生之后,通过Flume收集数据,将数据交给Kafka的。再由后续的流处理对接Kafka。

 

  • 2)Flume----->Kafka sink:Flume 1.9.0 User Guide — Apache Flume
  • 作用是,将flume中的数据输出至Kafka的topic里。
  • sink.type = org.apache.flume.sink.kafka.KafkaSink
  • kafka.bootstrap.servers = spark000:9020 若是多个,则用comma分隔即可。
  • kafka.topic = zhang-replicated-topic
  • kafka.producer.acks = 1 确保数据是否能够接收,是否有反馈。
  • 3)配置Flume的agent
[hadoop@spark000 config]$ pwd
/home/hadoop/app/apache-flume-1.6.0-cdh5.16.2-bin/config
[hadoop@spark000 config]$ vi flume-kafka.conf
# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = 0.0.0.0
a1.sources.r1.port = 44444

# Use a channel which buffers events in memory
a1.channels.c1.type = memory

# Describe the sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = zhang-replicated-topic
a1.sinks.k1.kafka.bootstrap.servers = spark000:9092
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
  • 4)执行flume脚本
  • 在路径下:/home/hadoop/app/apache-flume-1.6.0-cdh5.16.2-bin/config
  • agent的名字是a1
  • 默认FLUME_HOME/conf
  • 写入配置文件所在路径:$FLUME_HOME/config/flume-kafka.conf
  • 在控制台输出数据:Dflume.root.logger=INFO,console
flume-ng agent \
--name a1 \
--conf $FLUME_HOME/conf \
--conf-file $FLUME_HOME/config/flume-kafka.conf \
-Dflume.root.logger=INFO,console
  • 5)连接
[hadoop@spark000 ~]$ telnet spark000 44444
Trying 192.168.131.66...
Connected to spark000.
Escape character is '^]'.
testflume01
OK
tes
OK
testflume02
OK
  • 6)在consumer端查看结果
[hadoop@spark000 ~]$ kafka-console-consumer.sh --bootstrap-server spark000:9092 --topic zhang-replicated-topic
testflume01
tes
testflume02

 

  • 对接项目数据到Kafka
  • 1)Flume对应Kafka的producer,然后数据sink到Kafka的topic中,最后consumer到topic取数据并消费掉。
  • 2)配置Flume的agent。
[hadoop@spark000 config]$ pwd
/home/hadoop/app/apache-flume-1.6.0-cdh5.16.2-bin/config
[hadoop@spark000 config]$ vi access-kafka.conf
a1.sources = r1
a1.sinks = k1
a1.channels = c1

a1.sources.r1.type = TAILDIR
a1.sources.r1.channels = c1
a1.sources.r1.positionFile = /home/hadoop/tmp/position/taildir_position.json
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /home/hadoop/logs/access.log
a1.sources.r1.headers.f1.headerKey1 = zhang
a1.sources.r1.fileHeader = true

# Describe the sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = zhang-replicated-topic
a1.sinks.k1.kafka.bootstrap.servers = spark000:9092,spark000:9093,spark000:9094
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1

# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
  • 3)开始对接项目数据
  • 4)一定要记得开启zookeeper、Kafka服务器
[hadoop@spark000 ~]$ jps -m
7186 Kafka /home/hadoop/app/kafka_2.12-2.5.0/config/server-zhang0.properties6823 QuorumPeerMain /home/hadoop/app/zookeeper-3.4.5-cdh5.16.2/bin/../conf/zoo.cfg
7591 Kafka /home/hadoop/app/kafka_2.12-2.5.0/config/server-zhang1.properties
7999 Kafka /home/hadoop/app/kafka_2.12-2.5.0/config/server-zhang2.properties
  • 5)启动consumer
[hadoop@spark000 ~]$ kafka-console-consumer.sh --bootstrap-server spark000:9092,spark000:9093,spark000:9094 --topic zhang-replicated-topic
  • 6)启动Flume脚本
  • 路径:/home/hadoop/app/apache-flume-1.6.0-cdh5.16.2-bin/config
flume-ng agent \
--name a1 \
--conf $FLUME_HOME/conf \
--conf-file $FLUME_HOME/config/access-kafka.conf \
-Dflume.root.logger=INFO,console
  • 7)开始对接数据
  • 先打开consumer终端,目前展示为空
  • run本地IDEA的C:\Users\jieqiong\IdeaProjects\spark-log4j\log-service\src\main\java\com\imooc\bigdata\log\utils\Test.java
  • 然后会发现kafka的consumer终端加载access.log中的数据。
  • 8)即:完成了从数据的落地,然后通过Flume把落地的数据收集过来,其次sink到Kafka.。现在是在Kafka的consumer终端来消费/记录数据的。

 

  • Kafka数据/文件存储是什么样的呢?
  • 1)在单机器部署多个broker时候,我们创建了一个分区(多副本的)。看一下是存储在哪里的:
  • topic下面的数据,会拆分成partition,partition是存储在broker上的。
  • kafka-logs-0是1broker,  kafka-logs-1是2broker,  kafka-logs-2是3broker。
  • zhang-replicated-topic-0:zhang-replicated-topic是topic的名称,0是分区
  • zhang-replicated-topic-0是一个分区:0
  • zhang-replicated-topic-0是三个副本,分别在kafka-logs-0  kafka-logs-1  kafka-logs-2中。
[hadoop@spark000 ~]$ cd app/tmp
[hadoop@spark000 tmp]$ ls
kafka-logs-0  kafka-logs-1  kafka-logs-2
[hadoop@spark000 tmp]$ cd kafka-logs-0
[hadoop@spark000 kafka-logs-0]$ ls
zhang-replicated-topic-0
[hadoop@spark000 tmp]$ cd kafka-logs-1
[hadoop@spark000 kafka-logs-1]$ ls
zhang-replicated-topic-0
[hadoop@spark000 tmp]$ cd kafka-logs-2
[hadoop@spark000 kafka-logs-2]$ ls
zhang-replicated-topic-0
  • 2)总结1:
  • 在kafka文件存储时,同一个topic下会有多个不同的partition
  • 每个partition都是一个文件夹/目录
  • partition的命名规则是:topic的名称-序号(zhang-replicated-topic-0)
  • 第一个partition序号就是0
  • 序号最大值应该是partition数量-1
  • 3)总结2:
  • 每个partition相当于是,一个非常大的文件被平均分配到多个大小(不一定相等)的文件中。
  • 每个partition相当于是,一个非常大的文件分配到segment文件中。
  • 什么是segment文件?
[hadoop@spark000 kafka-logs-2]$ cd zhang-replicated-topic-0
[hadoop@spark000 zhang-replicated-topic-0]$ ls
00000000000000000000.index  //索引文件
00000000000000000000.log //数据文件
00000000000000000000.timeindex
00000000000000000215.snapshot
leader-epoch-checkpoint
  • 假设一个topic分成了三个分区,那我们的数据都会落在这三个分区里面,数据会大批量的进来。所以在这里zhang-replicated-topic-0设计的时候:里面的一个log文件就相当于是一个segment文件。
  • segment file,每个segment file是可以设定大小的。只要数据量超过了segment file大小,就会生成新的segment file。
  • 举例:一个partition是100G的数据大小,然后划分第一个500M的segment file,第二个500M的segment file....
  • 4)总结3:
  • segment file由两部分组成:索引文件index file(00000000000000000000.index)做存储和记录的、数据文件data/log file(00000000000000000000.log)。
  • 00000000000000000000.index和00000000000000000000.log是一组segment文件。名称列位一共是20位。
  • 5)segment的命名规则:
  • partition全局的第一个segment从0开始
  • 后续的每个segment文件名为上一个segment文件最后一条消息的offset值。所以第二组消息是从+1开头开始记录的。
  • 6)需求:offset=368776的消息/数据是什么?
  • 也就是说kafka可以指定一个偏移量,是快速从一个点拉取数据的原因。
  • 以下展示了一个segment里面,index和log的对应关系。
  • 3,497:表示第3个消息的偏移量是497,kafka去找的话会很方便。

 

 

  • Kafka面试题:谈谈对acks的看法(分层次来分析)
  • acks是kafka的一个参数。
  • 1)如何保证宕机时数据的不丢失
    • 首先在kafka里面,数据是分topic来存放的。在这个topic里面,是可以分成多个分区的。那每个分区呢,就是负责存储topic中的一部分数据的。
    • 在kafka cluster集群(由Brokers构成)中:每个broker上存储一些partition的数据。
    • 这样多个机器构成的集群,我们的一个topic的数据,分布式的存储在kafka的集群/(brokers)
  • 2)多副本冗余机制
    • 假设有一个topic是三个partition(1、2、3)的,此时三个broker上各存放一个partition,当一个broker挂掉,则一个partition的数据丢失,则数据不完整了。这种情况是不允许的。
    • 一个partition可以有多个副本
    • leader、follower副本概念
    • 现在假设2个replication副本的机制,则一个broker上存在两个partition(交叉存储),这个就叫冗余的分布式存储。这样就能解决任何一台机器挂掉数据彻底丢失的问题,起码这个副本在别的机器上是有的。
  • 3)多副本如何进行数据同步问题。
    • 因为我们有leader、follower的副本
    • leader:是对外提供读写服务的
    • producer往一个partition中写入数据,一般都是写入leader副本中的。
    • leader副本接收到数据之后,follower副本会不停的发送请求尝试拉取最新的数据,拉取到本地磁盘。(kafka数据写入的流程)
  • 4)Isr
    • 在查看kafka状态时,会显示Isr这一字段值:In-Sync Replication 保持同步的副本
    • 跟leader始终保持同步的follower有哪些副本。
  • 5)ack
    • 在我们ImoocKafkaProducer api接口编程的时候,涉及到了props.put("acks","all");
    • 这个操作的意思:是否需要producer接收leader信息的反馈消息
    • ack有三个值:0、1、all
    • 0:表示producer只负责发送数据,不会等待任何的反馈信息。(默认消息一发送,就落盘成功,即不关心),这种延时性是最低的。(不关心leader、follower是否同步数据ok)
    • 1:意味着leader需要记录本地的日志。而且只负责leader是否成功写入消息,不管follower是否同步成功。(只关心leader是否同步数据ok)
    • all:意味着leader会等待所有的Isr中副本数据写入成功,才是真正的成功。(关心leader和follower是否同步数据ok)
    • 延时性逐层递增。

 

课后问题

1、谈谈Kafka分区分配策略的看法,并举例说明

2、Kafka消息数据积压,Kafka消费能力不足怎处理

3、Kafka为什么有如此搞笑的数据读写能力

4、Kafka有哪些数据是存放在ZK上的?

5、你使用过哪些Kafka的参数调优?

6、Kafka是实时处理中非常受欢迎的一个框架,是一个分布式消息队列,具有很高的吞吐量,谈谈你对Kafka的认识?

(1)Kafka是什么?

(2)Kafka读写数据为什么性能高

(3)Kafka分区分配策略有哪些?各自的特点是什么?

(4)Kafka幂等性

posted @ 2021-12-08 14:43  酱汁怪兽  阅读(265)  评论(0)    收藏  举报