Kafka

1 概述

1.1 定义

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

优势:kafka可以做到,使用非常普通的硬件,也可以支持每秒数百万的消息读写。

MQ 代表消息队列 kafka只是众多mq中一款产品,mq的其他产品:active MQ、Rabbit MQ、Rocket MQ

1.2 消息队列MQ

MQ (Message Queue) 在传统开发中的应用场景 : 发短信、秒杀等等

特点:削峰 解耦 异步
在这里插入图片描述

1.3 消息队列两种模式

  1. 点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
    消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。
    消息被消费以后,Queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
    在这里插入图片描述
  2. 发布/订阅模式(一对多,消费者消费数据之后不会清除消息)
    消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
    在这里插入图片描述

1.4 Kafka基础架构

在这里插入图片描述
分区数并不是越多越好,一般分区数不要超过集群机器数量。
分区数越多占用内存越大(ISR等),一个节点集中的分区也就越多,当它宕机的时候,对系统的影响也就越大。
分区数一般设置为:3-10个
在这里插入图片描述

  • Producer :消息生产者,就是向kafka broker发消息的客户端;
  • Consumer :消息消费者,从kafka broker拉取消息的客户端;
  • Consumer Group(CG):消费者组,由多个consumer组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
  • Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。
  • Topic:可以理解为一个队列,生产者和消费者面向的都是一个topic;
  • Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列;
  • Replication:副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。
  • leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader。
  • follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的follower。

2 安装

集群规划

hadoop11hadoop12hadoop13
zkzkzk
kafkakafkakafka

http://kafka.apache.org/downloads.html
安装步骤
0. 准备工作

安装JDK,搭建Zookeeper集群环境
1) 解压zk安装
[root@hadoop11 modules]# tar -zxf zookeeper-3.4.6.tar.gz -C /opt/installs
[root@hadoop11 installs]# cd zookeeper-3.4.6/conf
2) 修改conf下的zoo_sample.cfg文件名为zoo.cfg
[root@hadoop11 conf]# mv zoo_sample.cfg zoo.cfg
3) 编辑zoo.cfg文件
[root@hadoop11 conf]# vi zoo.cfg
#内容如下
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/opt/installs/zookeeper-3.4.6/data
# the port at which the clients will connect
clientPort=2181

server.11=hadoop11:2888:3888
server.12=hadoop12:2888:3888
server.13=hadoop13:2888:3888
#------------------------------------------------
[root@hadoop11 conf]# cd ..
4) 在zookeeper下创建data目录,在data目录下创建myid文件
[root@hadoop11 zookeeper-3.4.6]# mkdir data
[root@hadoop11 zookeeper-3.4.6]# cd data
[root@hadoop11 data]# touch myid
5) 在文件中写入数字和zoo.cfg配置的server.x这个x数字对应
[root@hadoop11 data]# echo 11 >> myid
[root@hadoop11 data]# cat myid
11

6) 回到zk根目录下,启动zookeeper
[root@hadoop11 zookeeper-3.4.6]# bin/zkServer.sh start
[root@hadoop11 zookeeper-3.4.6]# jps
1233 Jps
1215 QuorumPeerMain
  1. 解压
[root@hadoop11 modules]# tar -zxvf kafka_2.11-0.11.0.0.tgz -C /opt/installs
  1. 修改文件名
[root@hadoop11 installs]# mv kafka_2.11-0.11.0.0  kafka
  1. 在/opt/kafka目录下创建logs文件夹
[root@hadoop11 installs]# cd kafka
[root@hadoop11 kafka]# mkdir logs
  1. 修改配置文件
[root@hadoop11 kafka]# cd config
[root@hadoop11 config]# vi server.properties
# 每个kafka节点,broker.id必须不一样
broker.id=11
# 允许topic可以删除
delete.topic.enable=true
# kafka运行日志(数据)存放的路径
log.dirs=/opt/installs/kafka/logs
# 配置连接Zookeeper集群地址
zookeeper.connect=hadoop11:2181,hadoop12:2181,hadoop13:2181
  1. 集群同步安装包
[root@hadoop11 installs]# scp -r /opt/kafka root@hadoop12:/opt/installs
[root@hadoop11 installs]# scp -r /opt/kafka root@hadoop13:/opt/installs
  1. 分别在hadoop12和hadoop13上修改配置文件/opt/installs/kafka/config/server.properties中的broker.id
broker.id=12
broker.id=13
注:broker.id不得重复
  1. 启动集群
# 在三台节点分别执行命令启动kafka
[root@hadoop11 kafka]# bin/kafka-server-start.sh -daemon config/server.properties
[root@hadoop12 kafka]# bin/kafka-server-start.sh -daemon config/server.properties 
[root@hadoop13 kafka]# bin/kafka-server-start.sh -daemon config/server.properties
  1. 验证集群是否启动成功
[root@hadoop11 kafka]# jps
1571 Kafka
1622 Jps
1215 QuorumPeerMain
  1. 关闭集群
[root@hadoop11 kafka]$ bin/kafka-server-stop.sh stop
[root@hadoop12 kafka]$ bin/kafka-server-stop.sh stop
[root@hadoop13 kafka]$ bin/kafka-server-stop.sh stop

3 常用命令

  1. 创建topic

    注意:一般在系统设计的时候,先把topic规划好,业务含义相同的数据,放在一个topic中。

    [root@hadoop11 kafka]# bin/kafka-topics.sh  --zookeeper hadoop11:2181  --create   --topic topica --partitions 3 --replication-factor 2
    
    ## ---------------参数说明------------------ ##
    --zookeeper 连接的zookeeper
    --create 表示要创建一个topic
    --topic 指定topic的名字(不同业务的数据,放在不同的topic中)
    --partitions 指定分区数
    --replication-factor 指定副本数
    
  2. 查看所有topic
    [root@hadoop11 kafka]# bin/kafka-topics.sh  --zookeeper hadoop11:2181  --list
    
  3. 查看某个topic详情
    [root@hadoop11 kafka]# bin/kafka-topics.sh  --zookeeper hadoop11:2181  --describe --topic topica
    

在这里插入图片描述
4. 删除topic

[root@hadoop11 kafka]# bin/kafka-topics.sh  --zookeeper hadoop11:2181  --delete   --topic topica
  1. 接收消息
[root@hadoop11 kafka]# bin/kafka-console-consumer.sh --bootstrap-server hadoop11:9092 --topic topica
  1. 发送消息
[root@hadoop11 kafka]# bin/kafka-console-producer.sh --broker-list      hadoop11:9092 --topic topica

4 Kafka Java API

4.1 Producer API

① 添加依赖

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

② 相关API
KafkaProducer:需要创建一个生产者对象,用来发送数据
ProducerConfig:获取所需的一系列配置参数
ProducerRecord:每条数据都要封装成一个ProducerRecord对象
③ 异步发送:不带回调函数的Producer

public class CustomProducer {
   public static void main(String[] args) throws Exception {
        //1. 初始化参数信息
        Map<String, Object> configs = new HashMap<>();
        configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop11:9092");
        configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
		//2. 创建生产者
        KafkaProducer<String,String> producer = new KafkaProducer<>(configs);
        //3. 发送数据
        for (int i=0;i<10;i++){
            producer.send(new ProducerRecord<>("topica","hello"+i));
        }
        producer.close();
    }
}

④ 异步发送:带回调函数的Producer
回调函数会在Producer收到ack时调用,为异步调用,该方法有两个参数,分别是RecordMetadata和Exception,如果Exception为null,说明消息发送成功,如果Exception不为null,说明消息发送失败。

注意:消息发送失败会自动重试,不需要在回调函数中手动重试。

public class CustomProducer_CallBack {
    public static void main(String[] args) throws Exception {
        Map<String, Object> configs = new HashMap<>();
        configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop11:9092");
        configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        KafkaProducer<String,String> producer = new KafkaProducer<>(configs);
        for (int i=0;i<10;i++){
            producer.send(new ProducerRecord<>("topica", "hello" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if(exception == null){
                         System.out.println("发送成功:"+metadata.partition());//数据所在分区
                         System.out.println("发送成功:"+metadata.topic());//数据所对应的topic
                         System.out.println("发送成功:"+metadata.offset());//数据的offset
                    }
                }
            });
        }
        producer.close();
    }
}

4.2 Consumer API

① 相关API
KafkaConsumer:需要创建一个消费者对象,用来消费数据
ConsumerConfig:获取所需的一系列配置参数
ConsuemrRecord:每条数据都要封装成一个ConsumerRecord对象
② Consumer接收数据

public class CustomConsumer {
    public static void main(String[] args) {
        //1. 初始化配置信息
        Map<String,Object> map = new HashMap<>();
        map.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop11:9092");
        map.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        map.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class);
        map.put(ConsumerConfig.GROUP_ID_CONFIG,"g1");
	    //2. 创建Consumer
        KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer(map);
        //订阅 topic-user的数据 
        kafkaConsumer.subscribe(Arrays.asList("topica"));
        while (true){
            //3. 消费数据
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(100);
            Iterator<ConsumerRecord<String, String>> iterator = consumerRecords.iterator();
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

5 架构深入

5.1 Kafka流程及存储机制

在这里插入图片描述
在这里插入图片描述
Kafka中消息是以topic进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。

topic是逻辑上的概念,而partition是物理上的概念,每个partition对应于一个log文件,该log文件中存储的就是producer生产的数据。

Producer生产的数据会被不断追加到该log文件末端,且每条数据都有自己的offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个offset,以便出错恢复时,从上次的位置继续消费。

在这里插入图片描述

生产者生产的消息会不断追加到log文件末尾,为防止log文件过大导致数据定位效率低下,Kafka采取了分片索引机制,将每个partition分为多个segment。

每个segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic名称+分区序号。例如,first这个topic有三个分区,则其对应的文件夹为first-0,first-1,first-2。

00000000000000000000.index
00000000000000000000.log
00000000000028532124.index
00000000000028532124.log

index和log文件以当前segment的第一条消息的offset命名。下图为index文件和log文件的结构示意图。

在这里插入图片描述
“.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中message的物理偏移地址。

5.2 生产者 之 分区策略

生产者发送消息到哪个分区 称之为 生产者分区策略

1)分区的原因

  • 方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;
  • 可以提高并发,因为可以以Partition为单位读写了。

2)分区的原则
需要将producer发送的数据封装成一个ProducerRecord对象。

在这里插入图片描述
在这里插入图片描述
(1)指明 partition 的情况下,直接将指明的值作为 partition 值;

(2)没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;

(3)既没有 partition 值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin 算法。

3)自定义分区

  • 自定义分区器实现Partitioner接口,重写partition方法

    public class MyPartition implements Partitioner {
        @Override
        public int partition(String topic, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
            int i = Integer.parseInt(value.toString().substring(0, 1));
            //获取分区总数
            Integer count = cluster.partitionCountForTopic(topic);
            if (i%2==0){
                return 1;
            }else {
                return 2;
            }
        }
        @Override
        public void close() { }
        @Override
        public void configure(Map<String, ?> map) { }
    }
    
  • 生产者端配置自定义分区器

    map.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"MyPartitioner全类名");
    

5.3 生产者 之 数据可靠性

为保证Producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后,都需要向producer发送ack(acknowledgement确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。

ack应答机制

对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的follower全部接收成功。

Kafka为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置

acks参数配置:

  • 0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据

  • 1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据
    注意:ack的默认值就是1,这个默认值是吞吐量与可靠性的一个折中方案,生产上可以根据实际情况进行调整

  • -1:producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复

在这里插入图片描述

5.4 生产者 之 幂等性

幂等:多次操作的结果和一次操作的结果相同,就称为幂等性操作。读操作一定是幂等性操作,写操作不是幂等性操作。

当kafka的producer发送数据给broker,如果在规定的时间没有收到应答,生产者会自动重发数据,这样的操作可能造成重复数据(at least once语义)的产生

对于某些比较重要的消息,我们需要保证exactly once语义,即保证每条消息被发送且仅被发送一次

在0.11版本之后,Kafka引入了幂等性机制(idempotent),配合acks = -1时的at least once语义,实现了producer到broker的exactly once语义。

idempotent + at least once = exactly once

使用时,只需将enable.idempotence属性设置为true,kafka自动将acks属性设为-1。

5.5 消费者 之 分区分配策略

一个Topic中的数据,由一个Consumer group进行消费

一个Topic中的一个partition,由对应Consumer group中的1个consumer进行消费。

注意:1个Partition分区中的数据,只能被1个Consumer分区消费。(1个partition分区不能同时被一个Consumer group中的多个Consumer消费)

问题:一旦partition个数和consumer个数不一致,如何分配?

说明:一般生产者的代码的数据写入由一个团队开发,消费者消费代码,和数据读取可能是由另一个业务团队读取。有时候无法做到统一。

一个consumer group中有多个consumer,一个 topic有多个partition,所以必然会涉及到partition的分配问题,即确定哪个partition由哪个consumer来消费。

Kafka有两种分配策略,一是roundrobin,一是range。

1)roundrobin (轮询法)

前提:同一个Consumer Group里面的所有消费者的num.streams(消费者消费线程数)必须相等;每个消费者订阅的主题必须相同。
在这里插入图片描述
2)range (区间)

默认策略

在这里插入图片描述

5.6 消费者 之 offset维护

由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。

Offset:kafka会保存每个topic数据消费的记录offset,以便记录consumer消费到哪个数据

在这里插入图片描述
在这里插入图片描述

Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets

# HW水位线和offset的关系
LEO(LogEndOffset):表示每个partition的log最后一条Message的位置
HW概念:
	全称 high watermarker 水位线。
	ISR列表中,每个写入对应分区中leader副本的数据,follower会拉取数据期望与leader数据进行同步数据和offset
	此刻因为网络延迟,会导致不同的follower拉取的速度不一样,在高并发场景下follower通常会滞后于leader,那么ISR内部offset最低的那个值就是HW。
	作用:消费者只能消费HW这个offset以下的数据

在这里插入图片描述

5.7 Kafka 高效读写数据

  1. 顺序写磁盘
    内存顺序写 > 硬盘顺序写 = 内存随机写 > 硬盘随机写
    Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。
    同样的磁盘,顺序写能到600M/s,而随机写只有100k/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
  2. Page Cache
    使用了Page Cache的cache预读取:Consumer在读取磁盘文件数据的时候,会将紧随其后的少量数据,读取到pageCache中,接下来因为数据是顺序写入,所以下面consumer读取的数据,就会在page cache直接命中,然后直接读取并消费,与此同时,操作系统会继续将后续数据读取到内存中,并且会扩大缓存数据量。
  3. 零拷贝
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

5.8 Zookeeper作用

Kafka集群中有一个broker会被选举为Controller(启动broker向zk注册,先到先得),负责管理集群broker的上下线、所有topic的分区副本分配和leader选举等工作。

Controller的管理工作都是依赖于Zookeeper的。

  1. 注册所有的broker(kafka节点)

    临时节点

  2. 从broker中选出一个承担KafkaController的职责

    1:防止单点故障,也是临时节点

    2:负责在需要的时候对ISR列表中选出leader

  3. 保存Topic的元数据信息(描述信息)

    zookeeper保存topic的分区和leader信息,并协助KafkaController在需要的时候(启动或者broker故障)选出新的分区的leader

    kafkaController会监听 zookeeper中的borkers下的ids节点的子节点变化
    在这里插入图片描述

5.9 过期数据清理

Kafka将数据持久化到了硬盘上,允许配置一定的策略对数据清理,清理的策略有两个,删除和压缩。
删除

log.cleanup.policy=delete 启用删除策略,直接删除,删除后的消息不可恢复。

可配置以下两个策略:

  • 超过指定时间清理: log.retention.hours=16
  • 超过指定大小后,删除旧消息:log.retention.bytes=1073741824

为了避免在删除时阻塞读操作,采用copy-on-write形式实现,删除操作进行时,读取操作的二分查找功能实际是在一个静态的快照副本上进行的,这类似于Java的CopyOnWriteArrayList。
压缩
将数据压缩,只保留每个key最后一个版本的数据。

  • 首先在broker的配置中设置log.cleaner.enable=true,启用cleaner,这个默认是关闭的。
  • 在topic的配置中设置log.cleanup.policy=compact,启用压缩策略。

压缩策略细节
在这里插入图片描述
如图,在整个数据流中,每个Key都有可能出现多次,压缩时将根据Key将消息聚合,只保留最后一次出现时的数据。

这样无论什么时候消费消息,都能拿到每个Key的最新版本的数据。

压缩后的offset可能是不连续的,比如上图中没有5和7,因为这些offset的消息被merge了,当从这些offset消费消息时,将会拿到比这个offset大的offset对应的消息

比如,当试图获取offset为5的消息时,实际上会拿到offset为6的消息,并从这个位置开始消费。

这种策略只适合特殊场景,比如消息的key是用户ID,消息体是用户的资料,通过这种压缩策略,整个消息集里就保存了所有用户最新的资料。

压缩策略支持删除,当某个Key的最新版本的消息没有内容时,这个Key将被删除,这也符合以上逻辑。

5.10 Kafka消费能力不足

从两方面考虑:

  1. 可以考虑增加Topic的分区数(一般一个Topic分区数为3-10个),并且同时提升消费组的消费者数量,消费者数==分区数。两者缺一不可
  2. 如果是下游的数据处理不及时:则提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。

5.11 Kafka参数调优

  • buffer.memory
    本质是用来约束KafkaProducer能够使用的内存缓冲的大小的,默认值是32MB
  • batch.size
    决定每个Batch要存放多少数据就可以发送出去,默认值是16KB
  • linger.ms
    含义是一个Batch被创建之后,最多过多久,不管这个Batch有没有写满,都必须发送出去
  • max.request.size
    这个参数决定每次发送给Kafka服务器请求的最大大小,同时也会限制一条消息的最大大小也不能超过这个参数设置的值
  • “retries”和“retries.backoff.ms”
    决定重试机制,如果一个请求失败了可以重试几次,每次重试的间隔是多少毫秒

5.12 leader epoch机制

在 kafka0.11.0.0 版本以后,提供了一个新的解决方案,使用 leader epoch 来解决这个问题,leader epoch 实际上是一对值(epoch,offset), epoch 表示 leader 的版本号,从 0开始,当 leader 变更过 1 次 时 epoch 就会+1,而 offset 则对应于该 epoch 版本的 leader 写入第一条消息的位移。

比如(0,0) ; (1,50); 表示第一个 leader 从 offset=0 开始写消息,一共写了 50 条,第二个 leader 版本号是1,从 50 条处开始写消息。这个信息保存在对应分区的本地磁盘文件中,文件名为 : /tml/kafka-log/topic/leader-epochcheckpoint

leader broker 中会保存这样的一个缓存,并定期地写入到一个 checkpoint 文件中。

当 leader 写 log 时它会尝试更新整个缓存——如果这个leader 首次写消息,则会在缓存中增加一个条目;否则就不做更新。而每次副本重新成为 leader 时会查询这部分缓存,获取出对应 leader 版本的offset

6 Flume整合Kafka

①. 在flume的job目录下新建exec-memory-kafka.conf文件

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

a1.sources.r1.type = exec
a1.sources.r1.command = tail -F /opt/a.log 

a1.channels.c1.type = memory

a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = topica
a1.sinks.k1.kafka.bootstrap.servers = hadoop11:9092,hadoop12:9092,hadoop13:9092

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

②. 启动kafka的consumer消费者

[root@hadoop11 kafka]# bin/kafka-console-consumer.sh --bootstrap-server hadoop11:9092 --topic topica

③.启动flume

[root@hadoop11 apache-flume-1.9.0-bin]# bin/flume-ng agent --conf conf --name a1 --conf-file job/exec-memory-kafka.conf -Dflume.root.logger=INFO,console

④.向a.log文件中追加数据

[root@hadoop11 opt]# echo hello >> a.log

⑤.查看kafka的consumer消费者的消费情况

[root@hadoop11 kafka]# bin/kafka-console-consumer.sh --bootstrap-server hadoop11:9092 --topic topica
hello

7 kafka执行流程

7.1 Producer发送数据流程

# 流程说明
1. Producer.send的主线程。
	① 数据先经过拦截器。
	② 然后进行网络传输前的序列化
	③ 计算消息所属的分区
	④ 将消息存入对应的本地分区。(本地缓存,为了批量发送)
2. sender线程
	① 当某个分区内的消息数量达到一定值:`batch.size`之后,才会发送数据。(默认值: 16384 (16kb))
		config.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
	② 如果某个分区内消息数量未达到batch.size,sender等待linger.ms之后也会批量发送。(默认值:0ms)
		config.put(ProducerConfig.LINGER_MS_CONFIG,200);
# 流程细节解释
1. 主线程一条条向本地分区中存入数据。
2. Sender线程批量将本地分区的数据,发送到kafka的topic中的对应分区。--提高效率
3. 拦截器:可以对producer发送的数据,做一些通用功能的处理。
4. 序列化:为了保证数据在网络中传输和kafka的broker之间同步,需要数据执行序列化。
5. Partitioner:数据在写入到本地的内存队列(缓冲区)之前,会先计算分区再存放数据。

在这里插入图片描述
在这里插入图片描述

7.2 Consumer消费数据流程

在这里插入图片描述

Consumer从kafka的磁盘中消费数据,所以不用担心数据丢失问题。

但是,Consumer作为一个消费者,是有可能出现宕机等问题的,也就意味着会出现重启后,继续消费的问题,那么就必须要消费者偏移量,消费到哪条数据了。

offset是用来记录Consumer的消费位置的,由Consumer自己负责维护(提交),保存在kafka的broker的内置topic中

  • 相关配置
# consumer重启offset机制,三个可选值,过早的offset记录会被删除。
auto.offset.reset=latest # 默认值,从最新的offset继续消费数据。
# 自动提交offset
1. 默认情况下Consumer的offset自动提交。
# ------------------配置参数-----------------------
# 自动提交开启
enable.auto.commit=true # 默认值
# 自动提交的时间间隔
auto.commit.interval.ms=5000 # 默认值5000 单位毫秒。
// java配置
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");
# 手动提交offset
通过代码的方式手动明确offset提交的方式。
`config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");`
提交方式优点缺点
同步提交有失败重试机制,可以确保每次offset提交成功。会影响消费者的消费速度。
异步提交异步提交offset,不会阻挡继续消费,消费速度快。可能会导致最新的offset没有提交成功,重启consumer之后,消费已经消费过的数据
// 同步提交: consumer提交完毕offset之后,才会继续消费数据。
//3. 消费数据
while (true){
    //JDK1.8 的API 毫秒数,
    ConsumerRecords<String, String> crs = kafkaConsumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> cr : crs) {
        System.out.println("cr = " + cr);
    }
    kafkaConsumer.commitAsync();
}
// 异步提交: consumer只需要发出提交offset的指令之后,就可以继续消费数据,不需要等待本地offset是否提交成功。
while (true){
    //JDK1.8 的API 毫秒数,
    ConsumerRecords<String, String> crs = kafkaConsumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> cr : crs) {
        System.out.println("cr = " + cr);
    }
    kafkaConsumer.commitSync();
}

7.3 自定义拦截器

对Producer发送到Kafka的数据,进行前置处理和后置处理。

接口:org.apache.kafka.clients.producer.ProducerInterceptor

public class TimeInterceptor implements ProducerInterceptor<String, String> {
/**
  * @param record  生产者发送出来的ProducerRecord数据
   * @return 		将处理后的数据封装成ProducerRecord继续处理
   */
  @Override
  public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
      // 数据离开producer到达broker之前。准确的是,进入Serializer之前。
      return null;
  }
  /**
  * @param metadata  响应的ack携带的数据描述信息(topic、分区、offset等)
   * @param exception 如果ack响应成功,则异常为null,否则就是真正的异常对象。
   */
  @Override
  public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
      //响应的ack返回给producer之前执行
      String topic = metadata.topic();
      int partition = metadata.partition();
      long offset = metadata.offset();
      System.out.println(topic+" : "+partition+" : "+offset);
  }
  @Override
  public void close() {
     // Producer调用close或者Producer对象回收,调用该close方法。
  }
 @Override
  public void configure(Map<String, ?> configs) {
      //初始化拦截器时候,调用该方法。
  }
}

7.3.1 执行时机

在这里插入图片描述

7.3.2 编码

需求如下:

  1. 对Producer发出的消息,添加一个时间戳:124313782314+message

  2. 对kafka接收到的消息返回ack的时候,统计个数,以统计生产者发送数据的成功数据和失败数,并且打印失败的消息的offset。

# 定义拦截器
public class TimeInterceptor implements ProducerInterceptor<String, String> {
    private Long successNum = 0L;
    private Long errorNum = 0L;
    /**
     * @param record 	生产者发送出来的ProducerRecord数据
     * @return 			将处理后的数据封装成ProducerRecord继续处理
     */
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
        // 数据离开producer到达broker之前。准确的是,进入Serializer之前。
        ProducerRecord<String, String> records = new ProducerRecord<String, String>(record.topic(),record.partition(),record.timestamp(),record.key(),System.currentTimeMillis()+record.value(),record.headers());
        return records;
    }
    /**
     * @param metadata 	响应的ack携带的数据描述信息(topic、分区、offset等)
     * @param exception 如果ack响应成功,则异常为null,否则就是真正的异常对象。
     */
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        //响应的ack返回给producer之前执行
        String topic = metadata.topic();
        int partition = metadata.partition();
        long offset = metadata.offset();
        System.out.println(topic+" : "+partition+" : "+offset);
        if (exception == null){
            successNum++;
        }else{
            errorNum++;
        }
    }
    @Override
    public void close() {
        // Producer调用close或者Producer对象回收,调用该close方法。
        System.out.println("successNum = " + successNum);
        System.out.println("errorNum = " + errorNum);
        System.out.println("-----------close---------");

    }
    @Override
    public void configure(Map<String, ?> configs) {
        //初始化拦截器时候,调用该方法。
        System.out.println("拦截器初始化:configs = " + configs);
    }
}
# 使用拦截器
// 将拦截器的全类名注册到config中,多个拦截器的类名,使用逗号隔开。
config.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, "demo3.TimeInterceptor");

8 Kafka压力测试

8.1 压测Kafka性能瓶颈

使用kafka内置脚本测试kafka的性能瓶颈(CPU 内存 网络IO)

内置脚本:

kafka-producer-perf-test.sh

kafka-consumer-perf-test.sh

# 生产者压力测试
[root@hadoop11 bin]# kafka-producer-perf-test.sh --topic test --record-size 100 --num-records 100000 --throughput 1000 --producer-props bootstrap.servers=hadoop11:9092,hadoop12:9092,hadoop13:9092

# 参数说明
--topic 测试的topic名字
--record-size 每条数据100字节
--num-records 总数据条数10w条
--throughput 每秒1000条数据

在这里插入图片描述
根据实际场景,判断测试结果是否能够应付现有系统的需求。

# 消费者压力测试

注意:在绝大多数情况下保证消费者的速度,要比生产者速度大,防止数据在kafka积压堆积

[root@hadoop11 bin]# kafka-consumer-perf-test.sh --broker-list hadoop11:9092,hadoop12:9092,hadoop13:9092 --topic test --fetch-size 10000 --messages 10000000 --threads 1

# 参数说明
--broker-list 指定zookeeper的ip
--topic 指定消费的topic的名称
--fetch-size 指定每次fetch的数据1w条。
--messages 总共要消费的消息个数 1KW,先确保kafka中有这么多数据可以消费。
--threads 消费者线程数(消费者数量)
start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec
2020-06-02 03:03:32:239, 2020-06-02 03:04:12:270, 1068.1152, 26.6822, 1300000, 32474.8320, 118, 39913, 26.7611, 32570.8416

start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec
2020-06-02 03:31:28:858, 2020-06-02 03:34:35:620, 7744.6509, 41.4680, 2227638, 11927.6834, 46, 186716, 41.4782, 11930.6219

总共六个参数:
第一个是开始时间;
第二个是结束时间;
第三个是本次消费了多少MB数据;
第四个是本次消费的速率MB/S;
第五个是总的消费数据条数;
第六个是每秒消费数据条数。

8.2 估算Kafka节点

① 数据量

每天总数据量100g,每天产生1亿条日志, 10000万/24/60/60=1150条/每秒钟

平均每秒钟:1150条

低谷每秒钟:400条

高峰每秒钟:1150条*(2-20倍)=2300条-23000条

每条日志大小:0.5k-2k

每秒多少数据量:2.3M-20MB

② 硬盘大小

每天的数据量*7天

③ 机器数量

公式:Kafka节点数量=2*(峰值生产速度*副本数/100)+1

案例:如果目前服务器(生产者)产生数据 100MB/s ,数据的副本数为2,
则需要的kafka节点数量为:2 * (100 * 2/100)+1 = 5台。
# 计算kafka集群的最大写入
使用producer压力测试工具,不断增加参数,测试每秒写入的最大数据量,一般实战中,最大数据量每秒写入不少于50MB/s

9 Kafka与RabbitMQ区别

  1. 语言不同
    RabbitMQ是由内在高并发的erlang语言开发,用在实时的对可靠性要求比较高的消息传递上

    Kafka是采用Scala语言开发,它主要用于处理活跃的流式数据,大数据量的数据处理上

  2. 结构不同
    RabbitMQ采用AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递异步消息的网络协议

    RabbitMQ的broker由Exchange,Binding,queue组成

    kafka采用mq结构:broker 有part 分区概念
    在这里插入图片描述

  3. Broker与Consume交互方式不同

    RabbitMQ采用push(推送)的方式

    kafka采用pull(拉取)的方式

  4. 集群负载均衡

    rabbitMQ的负载均衡需要单独的loadbalancer进行支持

    kafka采用zookeeper对集群中的broker、consumer进行管理

  5. 使用场景

    rabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作;基于存储的可靠性要求存储可以采用内存或者硬盘。金融场景中经常使用

    kafka具有高吞吐量,内部采用消息的批量处理,zero-copy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度(与分区上的存储大小无关),消息处理的效率很高。(大数据)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值