Kafka(数据缓存监控)

文章目录

  • 一、Kafka的简介
    • 1.定义
    • 2.特点及应用
    • 3.消息队列
      • 3.1 传统消息队列
      • 3.2 点对点模式
      • 3.3 发布/订阅模式
    • 4.核心概念
      • 4.1 Broker
      • 4.2 Topic
      • 4.3 Partition
      • 4.4 Offset
      • 4.5 持久化
      • 4.6 副本机制
      • 4.7 Producer
      • 4.8 Consumer
      • 4.9 Consumer Group
    • 5.Kafka基础架构
    • 2.安装
      • 2.1环境的配置
      • 2.2 配置
      • 2.3启动和停止
  • 二、Kafka常用操作
    • 1.主题操作
    • 2.生产数据
    • 3.消费数据
  • 三、生产者
    • 1.副本相关概念
    • 2.如何判断副本属于OSR
    • 3.生产者如何保证消息的安全性
    • 4.副本数据同步策略
    • 5.分布式系统对消费数据语义的支持
    • 6.故障处理细节
  • 四、消费者
    • 1.消费方式
    • 2.独立消费者
    • 3.消费者的分区
      • 3.1 range(默认)
      • 3.2 round_rabin(轮询分区)
  • 五、kafka高效的原因
    • 1.顺序写磁盘
    • 2.磁盘页缓存技术
    • 3.零拷贝技术
  • 六、JavaAPI
    • 1.引入依赖
    • 2.Producer API
      • 2.1简单的生产者
      • 2.2 带回调的异步发送
      • 2.3 同步发送
    • 3.自定义分区器
    • 4.自定义拦截器
    • 5.Consumer API
      • 5.1 自动提交的consumer
      • 5.2 手动提交offset
      • 5.3 手动提交的问题
      • 5.4 独立消费者
  • 七、KafkaSink
    • 1.介绍
    • 2.案例

一、Kafka的简介

1.定义

Kafka是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域

在流式计算中,Kafka一般用来缓存数据,Spark通过消费Kafka的数据进行计算

Kafka对消息保存时根据Topic进行归类,发送消息者称为Producer,消息接受者称为Consumer,此外kafka集群有多个kafka实例组成,每个实例(server)称为broker。

2.特点及应用

作为一个数据流式传输平台,kafka有以下特点:

  • 类似于消息队列和商业的消息系统,kafka提供对流式数据的发布和订阅
  • kafka提供一种持久的容错的方式存储流式数据
  • kafka拥有良好的性能,可以及时地处理流式数据
  • Kafka作为一个集群运行在一个或多个可跨多个数据中心的服务器上
  • Kafka集群将数据按照类别记录存储,这种类别在kafka中称为主题
  • 每条记录由一个键,一个值和一个时间戳组成

主要应用于:

  • 需要在多个应用和系统间提供高可靠的实时数据通道
  • 一些需要实时传输数据及及时计算的应用

3.消息队列

3.1 传统消息队列

Kafka(数据缓存监控)_第1张图片
Kafka(数据缓存监控)_第2张图片

3.2 点对点模式

(一对一,消费者主动拉取数据,消息收到后消息清除)

消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。
消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费
Kafka(数据缓存监控)_第3张图片

3.3 发布/订阅模式

(一对多,消费者消费数据之后不会清除消息)

消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费

Kafka(数据缓存监控)_第4张图片

4.核心概念

4.1 Broker

一台kafka服务器就是一个broker。一个集群由多个broker组成

4.2 Topic

Topic 就是数据主题,只是逻辑上的分类,实际上数据在存储时必须存储在某个主题的分区下

Topic可以类比为数据库中的库

4.3 Partition

分区是数据真正的物理上数据存储的路径!分区在磁盘上就是一个目录!目录名由主题名-分区名组成!

消费者在消费主题中的数据时,一个分区只能被同一个组中的一个消费者线程所消费!

分区可以类比为数据库中的表

4.4 Offset

每一个消费者端,会唯一保存的元数据是offset(偏移量),即消费在log中的位置
Kafka(数据缓存监控)_第5张图片
Kafka(数据缓存监控)_第6张图片

4.5 持久化

Kafka 集群保留所有发布的记录—无论他们是否已被消费—并通过一个可配置的参数(server.properties–>log.retention.hours)——保留期限来控制。举个例子, 如果保留策略设置为2天,一条记录发布后两天内,可以随时被消费,两天过后这条记录会被清除并释放磁盘空间。
Kafka的性能和数据大小无关,所以长时间存储数据没有什么问题

4.6 副本机制

  • 容错性:每个服务器在处理数据和请求时,共享分区。每一个分区都会在已配置的服务器上进行备份,确保容错性。
  • 高可用:每个分区都有一台 server 作为 “leader”,零台或者多台server作为 follwers 。leader 处理一切对 partition (分区)的读写请求,而follwers 只需被动的同步leader上的数据。当leader宕机了,followers 中的一台服务器会自动成为新的 leader。通过这种机制,既可以保证数据有多个副本,也实现了一个高可用的机制!
  • 基于安全考虑,每个分区的Leader和follower一般会错落在不同的broker

4.7 Producer

消息生产者,就是向kafka broker发消息的客户端。生产者负责将记录分配到topic的指定 partition(分区)中

4.8 Consumer

消息消费者,向kafka broker取消息的客户端。每个消费者都要维护自己读取数据的offset。低版本0.9之前将offset保存在Zookeeper中,0.9及之后保存在Kafka的“__consumer_offsets”主题中。

4.9 Consumer Group

消费者组!多个消费者可以分配到同一个组中!同一个组的不同消费者,在消费指定的主题时,可以合理分配分区,达到负载均衡,提升消费的速度!

  • 单播:如果所有的消费者实例在同一消费组中,消息记录会负载平衡到每一个消费者实例。即每个消费者可以同时读取一个topic的不同分区
  • 广播:如果所有的消费者实例在不同的消费组中,每条消息记录会广播到所有的消费者进程
  • 一个topic可以有多个consumer group。topic的消息会复制(不是真的复制,是概念上的)到所有的consumer group,但每个partion只会把消息发给该consumer group中的一个consumer

5.Kafka基础架构

Kafka(数据缓存监控)_第7张图片

2.安装

2.1环境的配置

kafka使用scala语言编写,scala也需要运行在JVM上!要求必须有JAVA_HOME!

2.2 配置

编辑 config/server.properties

#21行,每台cluster中的broker都需要有唯一的id号,必须为整数
broker.id=103
#24行,打开注释,此行代表允许手动删除kafka中的主题
delete.topic.enable=true
#63行,配置kafka存储的数据文件的存放目录
log.dirs=/opt/module/kafka/datas
#126行,配置连接的zk集群的地址
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181

分发整个kafka到集群:xsync kafka/

修改其他机器broker的broker.id,不得重复

2.3启动和停止

①启动zk集群

  • 执行zkServer.sh start来启动服务端,jps查看进程
  • 执行zkCli.sh来启动客户端

②启动broker

/opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties

③停止集群

/opt/module/kafka/bin/kafka-server-stop.sh

二、Kafka常用操作

1.主题操作

①查看集群中的所有主题:

bin/kafka-topics.sh --zookeeper hadoop103:2181 --list

②创建主题:

bin/kafka-topics.sh --zookeeper hadoop103:2181 --create --topic hello --partitions 3 --replication-factor 2
  • topic 定义topic名
  • replication-factor 定义副本数
  • partitions 定义分区数

创建主题是必须指定分区个数和副本数!

以上方式新建的主题是采用赋值均衡算法,将主题的多个分区均衡地分配到多个broker!

  • replication-factor不能超过当前集群可用的broker的数量!

在新建主题时,明确地告诉kafka,我的每个分区要分配到哪个机器:

bin/kafka-topics.sh --zookeeper hadoop103:2181 --create --topic hello1  --replica-assignment 102:103,102:104

③查看主题的描述信息:

bin/kafka-topics.sh --zookeeper hadoop103:2181 --describe --topic hello

④扩展主题的分区:

bin/kafka-topics.sh --zookeeper hadoop103:2181 --alter --topic hello --partitions6

⑤删除topic

bin/kafka-topics.sh --zookeeper hadoop103:2181 --delete --topic hello

2.生产数据

生产消息,需要有生产者,生产者需要自己写程序!kafka提供基于测试的生产者!

bin/kafka-console-producer.sh --broker-list hadoop103:9092 --topic  hello

生产数据时,如果一个主题有多个分区!在生产时,只指定主题不指定分区!消息会轮询地分配到不同的分区!

在同一个分区中,消费者在消费数据时,分区内部有序,但是不代表数据整体有序!

希望数据整体有序,只能是一个主题只有一个分区!

3.消费数据

可以启动基于控制台的消费者,用于测试!

bin/kafka-console-consumer.sh --bootstrap-server hadoop104:9092 --topic hello

默认消费者启动后,只会从分区的最后的位置开始消费!

如果是一个新的消费者组,添加–from beginning可以从分区的最新位置消费:

bin/kafka-console-consumer.sh --bootstrap-server hadoop104:9092 --topic hello --from-beginning

可以让多个消费者线程分配到一个组中,同一个组中的消费者线程,会共同消费同一个主题!

bin/kafka-console-consumer.sh --bootstrap-server hadoop104:9092 --topic hello --consumer-property group.id=jaffe --consumer-property client.id=test1

三、生产者

1.副本相关概念

R:replicas(副本)
AR:avaliable replicas(可用副本)
ISR:insync replicas (同步副本)
OSR:out of sync replicas (不同步的副本)

若一个分区有多个副本,那么会从多个副本中选取一个作为leader,其余为follow

Follower只负责从Leader同步数据!如果Follower可以及时地从leader机器同步数据,这台follower就可以进入ISR!否则,属于OSR!

ISR和OSR都由leader进行维护!用户可以设置一个replica.lag.time.max.ms=10(默认),符合这个标准的副本,加入ISR,不符合,就加入OSR!

replica.lag.time.max.ms代表每个副本向leader发送同步数据请求的延迟时间的最大值!

2.如何判断副本属于OSR

在以下情况副本属于OSR:

①当副本(broker)和zookeeper的上一次的通信时间距离现在已经过了zookeeper.connection.timeout.ms=6000,此时集群会判断当前节点已经下线,下线的副本一定属于OSR

②如果副本没有下线,假设副本和leader距离上次发送fetch请求已经超过了replica.lag.time.max.ms,那么当前副本也会认为属于OSR

③如果副本没有下线,假设副本和leader距离上次发送fetch请求没有超过replica.lag.time.max.ms,但是无法同步最新的数据,此时副本也会认为属于OSR

follower和consumer向leader发送的拉取数据的请求都是同一种!follower每次消费的offset也由Leader维护!

如果副本可以及时同步数据,那么也可用从OSR变为ISR!

OSR+ISR=AR<=R

在leader故障时,集群总是从ISR列表中,选举一个称为新的Leader!

3.生产者如何保证消息的安全性

producer在发送数据时,可以设置acks参数,确保消息在何种情况下收到brokder的ack确认!

  • 0:生成者无需等待brokder的ack,效率最快,丢数据的风险最大!

  • 1: leader写完后,就返回ack确认消息,如果leader写完后,在返回ack确认消息之前挂掉,此时,由于follower尚未及时同步,因此选举的新的leader是不具有此条消息,那么可能造成丢数据!

  • -1(all): leader及ISR中所有follower全部写完后,返回确认消息!不会丢数据,但是有可能造成数据的重复!

当Leader挂掉后,ISR中没用可用的副本!但是OSR中有可用的副本,此时是否会选举OSR中的副本作为Leader?

OSR是否可以选举为Leader,取决于unclean.leader.election.enable参数的设置!

clean-elector: 只会从ISR中选

unclean-elector: 也会从OSR中选

如果ISR中只有一个副本可选,即使acks=-1,也可能会丢失数据!如何避免?

​可以设置min.insync.replicas=2,代表当ISR中的副本数满足此条件时,生产者才会向broker发送数据,否则会报错: NoEnoughReplicasExecption!

4.副本数据同步策略

方案 优点 缺点
半数以上完成同步,就发送ack 延迟低 选举新的leader时,容忍n台节点的故障,需要2n+1个副本
全部完成同步,才发送ack 选举新的leader时,容忍n台节点的故障,需要n+1个副本 延迟高

Kafka选择了第二种方案,原因如下:

  1. 同样为了容忍n台节点的故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而Kafka的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。
  2. 虽然第二种方案的网络延迟会比较高,但网络延迟对Kafka的影响较小。
  3. ISR
    采用第二种方案之后,设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?
    Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。Leader发生故障之后,就会从ISR中选举新的leader。

5.分布式系统对消费数据语义的支持

at most once: 每条消息最多存一次! acks=0,1

at least once:每条消息最少存一次! acks=-1

exactly once:每条消息精准一次!enable.idempotence=true

如果要满足exactly once,通常要求系统需要在设计时,提供幂等性功能!

幂等性: 一个数字在执行任意次运算后的结果都是一样的!称为这个数字具有幂等性的特征!

​1 的 N次方 都是1,1具有幂等性!

kafka在0.11之前,无法满足exactly once!在0.11之后,提供了幂等性的设置,通过幂等性的设置,可以满足exactly once!

enable.idempotence=true,kafka首先会让producer自动将 acks=-1,再将producer端的retry次数设置为Long.MaxValue,再在集群上对每条消息进行标记去重!

去重: 在cluster端,对每个生产者线程生成的每条数据,都会添加以下的标识符: (producerid,partition,SequenceId),通过标识符对数据进行去重!

6.故障处理细节

Kafka(数据缓存监控)_第8张图片
LEO:log end offset。 指每个分区副本 log文件最后的offset的值!当前副本分区的最后一条记录的offset!

HW:high watermark。 ISR中LEO最小的值!

HW可以保证consumer在消费时,只有HW之前的数据可以消费的!保证leader故障时,不会由于leader的更换造成消费数据的不一致!

HW还可以在leader发送选举时,使ISR中所有的follower都参照HW,将之后多余的数据截掉,保持和leader的数据同步!

  1. follower故障
    follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始向leader进行同步。等该follower的LEO大于等于该Partition的HW,即follower追上leader之后,就可以重新加入ISR了。
  2. leader故障
    leader发生故障之后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。
    注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复

四、消费者

1.消费方式

consumer采用pull(拉)模式和push(推)模式从broker中读取数据。

  • push(推)模式不足之处:
    很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。
    它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

  • pull(拉)模式不足之处:
    如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout

2.独立消费者

在启动消费者时,如果明确指定了要消费的主题、分区,以及消费的位置!此时启动的消费者,称为独立消费者!

在启动消费者时,只指定了消费的主题,没有指定要消费哪个分区!此时这个消费者称为非独立消费者!

区别: 独立消费者在消费数据时,kafka集群不会帮消费者维护消费的Offset!

3.消费者的分区

再平衡(rebalance): 一个非独立消费者组中如果新加入了消费者或有消费者挂掉,此时都会由系统自动再进行主题分区的分配,这个过程称为再平衡!

当创建的是非独立消费者,此时会由kafka集群自动帮消费者组中的每个消费者分配要消费的分区!

自动分配时,有两种分配的策略:round_rabin(轮询分区) 和 range(范围分区)

3.1 range(默认)

如何分配: 首先会统计一个消费者,一共订阅了哪些主题!以主题为单位,根据主题的分区数 / 当前主题订阅的消费者个数 ,根据结果,进行范围的分配

举例:

有jaffe消费者组,组内有3个消费者[a,b,c]
a 订阅了 hello(0,1,2)
b 订阅了 hello(0,1,2)

c 订阅了 hello1(0,1,2)

此时分配的结果如下:

a 消费了 hello(0,1)
b 消费了 hello(2)
c 消费了 hello1(0,1,2)

问题:①如果一个消费者订阅的主题越多,分配得到的分区越多!

​ ②如果出现同一个主题被多个消费者订阅,那么排名后靠前的消费者容易出现负载不均衡(多分配分区)

极端情况:

a 订阅了 hello(0,1,2),hello1(0,1,2),hello2(0,1,2),hello3(0,1,2)
b 订阅了 hello(0,1,2),hello1(0,1,2),hello2(0,1,2),hello3(0,1,2)

a比b多分配4个分区!

3.2 round_rabin(轮询分区)

如何分配: 首先会统计一个消费者组,一共订阅了哪些主题!
以主题为单位,将主题的分区进行排序,排序后采取轮询的策略,将主题轮流分配到订阅这个主题的消费者上!如果出现组内有消费者没有订阅这个主题,默认轮空(跳过),继续轮询!

举例:

有jaffe消费者组,组内有3个消费者[a,b,c]
a 订阅了 hello(0,1,2)
b 订阅了 hello(0,1,2)

c 订阅了 hello1(0,1,2)

此时分配的结果如下:

a 消费了 hello(0,2)
b 消费了 hello(1)
c 消费了 hello1(0,1,2)

五、kafka高效的原因

1.顺序写磁盘

Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到600M/s,而随机写只有100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。

2.磁盘页缓存技术

在现代操作系统中,可以把磁盘的一片区域当作临时的缓存使用!

3.零拷贝技术

Kafka(数据缓存监控)_第9张图片

六、JavaAPI

1.引入依赖

<dependencies>
        <dependency>
            <groupId>org.apache.kafkagroupId>
            <artifactId>kafka-clientsartifactId>
            <version>0.11.0.0version>
        dependency>
        <dependency>
            <groupId>org.apache.kafkagroupId>
            <artifactId>kafka_2.11artifactId>
            <version>0.11.0.0version>
        dependency>
    dependencies>

2.Producer API

2.1简单的生产者

package com.jaffe.kafka.custom;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;

public class MyProducer {

    public static void main(String[] args) {

        //producer的配置信息
        Properties props = new Properties();
        // 服务器的地址和端口
        props.put("bootstrap.servers", "hadoop102:9092,hadoop103:9092,hadoop104:9092");
        // 接受服务端ack确认消息的参数,0,-1,1
        props.put("acks", "all");
        // 如果接受ack超时,重试的次数
        props.put("retries", 3);
        // sender一次从缓冲区中拿一批的数据量
        props.put("batch.size", 16384);
        // 如果缓冲区中的数据不满足batch.size,只要和上次发送间隔了linger.ms也会执行一次发送
        props.put("linger.ms", 1);
        // 缓存区的大小
        props.put("buffer.memory", 33554432);
        //配置生产者使用的key-value的序列化器
        props.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // ,泛型,必须要和序列化器所匹配
        Producer<Integer, String> producer = new KafkaProducer<>(props);

        for (int i = 0; i < 10; i++){
            
            producer.send(new ProducerRecord<Integer, String>("test1", i, "jaffe"+i));
        }
        producer.close();
    }
}

2.2 带回调的异步发送

package com.jaffe.kafka.custom;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class MyProducer {

    public static void main(String[] args) {

        //producer的配置信息
        Properties props = new Properties();
        // 服务器的地址和端口
        props.put("bootstrap.servers", "hadoop102:9092,hadoop103:9092,hadoop104:9092");
        // 接受服务端ack确认消息的参数,0,-1,1
        props.put("acks", "all");
        // 如果接受ack超时,重试的次数
        props.put("retries", 3);
        // sender一次从缓冲区中拿一批的数据量
        props.put("batch.size", 16384);
        // 如果缓冲区中的数据不满足batch.size,只要和上次发送间隔了linger.ms也会执行一次发送
        props.put("linger.ms", 1);
        // 缓存区的大小
        props.put("buffer.memory", 33554432);
        //配置生产者使用的key-value的序列化器
        props.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // ,泛型,必须要和序列化器所匹配
        Producer<Integer, String> producer = new KafkaProducer<>(props);

        for (int i = 0; i < 10; i++){

            //异步发送
            // producer.send(new ProducerRecord("test1", i, "jaffe"+i));

            //异步带回调的发送
            producer.send(new ProducerRecord<Integer, String>("test2", i, "jaffe" + i), new Callback() {
                //  一旦发送的消息被server通知了ack,此时会执行onCompletion()
                // RecordMetadata: 当前record生产到broker上对应的元数据信息
                // Exception: 如果发送失败,会将异常封装到exception返回 
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {

                    //没有异常
                    if (exception==null){
                        //查看数据的元数据信息
                        System.out.println("partition:"+metadata.topic()+"-"+metadata.partition()+",offset:"+
                                metadata.offset());
                    }

                }
            });
        }
        producer.close();
    }
}

2.3 同步发送

RecordMetadata result=producer.send(new ProducerRecord()).get()

3.自定义分区器

①编写分区器

public class MyPartitioner implements Partitioner {

    //为每个ProduceRecord计算分区号
    // 根据key的hashCode() % 分区数
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //获取主题的分区数
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);

        int numPartitions = partitions.size();

        return (key.hashCode() & Integer.MAX_VALUE) % numPartitions;
    }

    // Producer执行close()方法时调用
    @Override
    public void close() {

    }

    // 从Producer的配置文件中读取参数,在partition之前调用
    @Override
    public void configure(Map<String, ?> configs) {

        System.out.println(configs.get("welcomeinfo"));

    }
}

②在producer中设置

props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.jaffe.kafka.custom.MyPartitioner");

4.自定义拦截器

①自定义拦截器

public class TimeStampInterceptor implements ProducerInterceptor<Integer,String> {

    //拦截数据
    @Override
    public ProducerRecord<Integer, String> onSend(ProducerRecord<Integer, String> record) {

        String newValue=System.currentTimeMillis()+"|"+record.value();

        return new ProducerRecord<Integer, String>(record.topic(),record.key(),newValue);
    }

    //当拦截器收到此条消息的ack时,会自动调用onAcknowledgement()
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {

    }

    // Producer关闭时,调用拦截器的close()
    @Override
    public void close() {

    }

    //读取Producer中的配置
    @Override
    public void configure(Map<String, ?> configs) {

    }
}

②设置

		//拦截器链
        ArrayList<String> interCeptors = new ArrayList<>();

        // 添加的是全类名,注意顺序,先添加的会先执行
        interCeptors.add("com.jaffe.kafka.custom.TimeStampInterceptor");
        interCeptors.add("com.jaffe.kafka.custom.CounterInterceptor");
         //设置拦截器
        props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interCeptors);

5.Consumer API

5.1 自动提交的consumer

public static void main(String[] args) {

        // consumer的配置
        Properties props = new Properties();
        // 连接的集群地址
        props.put("bootstrap.servers", "hadoop102:9092,hadoop103:9092,hadoop104:9092");
        // 消费者组id
        props.put("group.id", "test");
        // 消费者id
        props.put("client.id", "test01");
        // 允许在消费完数据后,自动提交offset
        props.put("enable.auto.commit", "true");
        // 每次自动提交offset的间隔时间
        props.put("auto.commit.interval.ms", "1000");
        // key-value的反序列化器,必须根据分区存储的数据类型,选择合适的反序列化器
        props.put("key.deserializer", "org.apache.kafka.common.serialization.IntegerDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        //基于配置创建消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        //订阅主题
        consumer.subscribe(Arrays.asList("test1"));
        //消费数据,采取poll的方式主动去集群拉取数据
        while (true) {
            //每次poll,拉取一批数据,如果当前没有可用的数据,就休息timeout单位时间
            ConsumerRecords<String, String> records = consumer.poll(100);
            //遍历数据,执行真正的消费逻辑
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }

    }

5.2 手动提交offset

自动提交Offset存在一定的风险!如果从kafka,pull到数据后,在真正执行消费处理逻辑之前,已经提交了offset!但是如果在执行消费逻辑时,程序出现异常!

​ 期望下次重启程序,可以继续消费异常的数据!但是由于之前已经提交了offset,此部分数据我们是无法再消费到了,因此这就是消费端丢数据!

​ 解决:采取手动提交offset!在消费的逻辑真正运算结束后,手动提交!

关闭自动提交:

 props.put("enable.auto.commit", "false");

5.3 手动提交的问题

手动提交和核心是在执行真正的消费逻辑后,再提交offset!这种情况可能会造成数据的重复消费!

如果pull了一批数据,只有部分数据执行了消费逻辑,此时程序发生异常,将不会运行提交offset!

之后程序重启,会从之前提交的offset继续消费,已经消费成功的部分数据,就存在重复消费的情况!

如何避免重复消费:将消费逻辑和提交Offset放入一个事务中!同时在出现异常时,回滚事务!

//全局变量
 int offset=当前处理的记录的offset
while (true) {
    
    try{
        	//开启事务
        	xxxxx
            //每次poll,拉取一批数据,如果当前没有可用的数据,就休息timeout单位时间
            ConsumerRecords<String, String> records = consumer.poll(100);
            //遍历数据,执行真正的消费逻辑
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
                //记录当前处理的位置
                offset=record.offset();
            }

            //手动同步提交  等offset提交完成后,再继续运行代码
            //consumer.commitSync();
            //手动异步提交
            consumer.commitAsync();
        	
        // 提交事务
        
       }catch(Exception e){
//回滚事务
//在catch中,提交之前已经处理的offset,自己维护提交的offset,例如将Offset存储到mysql中!将offset存储,提交到mysql中
    }
    }
}

5.4 独立消费者

独立消费者需要自己维护offset

public static void main(String[] args) {

        // consumer的配置
        Properties props = new Properties();
        // 连接的集群地址
        props.put("bootstrap.servers", "hadoop102:9092,hadoop103:9092,hadoop104:9092");
        // 消费者组id
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "test2");

        // 消费者id
        props.put("client.id", "test01");
        // 允许在消费完数据后,自动提交offset,独立消费者的offset不由kafka维护
        props.put("enable.auto.commit", "true");
        // 每次自动提交offset的间隔时间
        props.put("auto.commit.interval.ms", "1000");
        // key-value的反序列化器,必须根据分区存储的数据类型,选择合适的反序列化器
        props.put("key.deserializer", "org.apache.kafka.common.serialization.IntegerDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        //基于配置创建消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        //要订阅的分区
        List<TopicPartition> partitions=new ArrayList<>();

        TopicPartition tp1 = new TopicPartition("test1", 0);
        TopicPartition tp2 = new TopicPartition("test1", 1);

        partitions.add(tp1);
        partitions.add(tp2);

        //分配主题和分区
        consumer.assign(partitions);

        //指定offset
        consumer.seek(tp1,20);
        consumer.seek(tp2,30);

        //消费数据,采取poll的方式主动去集群拉取数据
        while (true) {
            //每次poll,拉取一批数据,如果当前没有可用的数据,就休息timeout单位时间
            ConsumerRecords<String, String> records = consumer.poll(100);
            //遍历数据,执行真正的消费逻辑
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }

        }

    }

七、KafkaSink

1.介绍

kafkasink本质就是一个生产者,负责将flume channel中的数据,生产到kafka集群的topic中!

必须属性:

type org.apache.flume.sink.kafka.KafkaSink
kafka.bootstrap.servers 集群地址
kafka.topic default-flume-topic 向哪个主题生成
flumeBatchSize 100 一批数据量
kafka.producer.acks 1 acks
useFlumeEventFormat false By default events are put as bytes onto the Kafka topic directly from the event body. Set to true to store events as the Flume Avro binary format. Used in conjunction with the same property on the KafkaSource or with the parseAsFlumeEvent property on the Kafka Channel this will preserve any Flume headers for the producing side.

useFlumeEventFormat=false时,将flume 每个event中的body的内容直接写到kafka!

useFlumeEventFormat=true,此时存储的event就默认为flume的avro格式!在生成时,会将flume的event的header内容也存入kafka!

  • 何时用true?
    需要和KafkaSource 及 Kafka Channel的 parseAsFlumeEvent一起使用!配套为true或者false!

2.案例

netcatsource–memerychannel–kafkasink

a1.sources = r1
a1.sinks = k1
a1.channels = c1

# 配置source
a1.sources.r1.type = netcat
a1.sources.r1.bind = hadoop103
a1.sources.r1.port = 44444
a1.sources.r1.interceptors = i1 i2
a1.sources.r1.interceptors.i1.type = static
a1.sources.r1.interceptors.i1.key = topic
a1.sources.r1.interceptors.i1.value = hello
a1.sources.r1.interceptors.i2.type = static
a1.sources.r1.interceptors.i2.key = key
a1.sources.r1.interceptors.i2.value = 1

# 配置sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.bootstrap.servers=hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sinks.k1.kafka.topic=test3
a1.sinks.k1.useFlumeEventFormat=false

# 配置channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000

# 绑定和连接组件
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

你可能感兴趣的:(bigdata)