kafka学习记录

kafka学习记录

优秀文章:https://my.oschina.net/jallenkwong/blog/4449224#

1.Kafka 概述

1.1 定义

kafka是一个用于分布式的基于发布/订阅模式消息队列(Message Queue),主要用于大数据实时处理领域。
本质用于暂存数据;

1.2 消息队列

1.2.1 传统消息队列的应用场景

在这里插入图片描述
使用消息队列的好处

  1. 解耦
    允许你独立扩展或修改两边处理过程,只要确保他们遵守同样的接口约束。
  2. 可恢复性
    系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息进程的挂掉,加入队列中的消息仍然可以再系统恢复后被处理。
  3. 缓冲
    有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
  4. 灵活性&峰值处理能力(削峰)
    在访问量剧增的情况下,应用仍然需要发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷请求而完全崩溃。
  5. 异步通讯
    很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放入多少,然后在需要的时候再去处理他们。

1.2.2 消息队列的两种模式

1. 点对点模式(一对一,消费者主动拉取数据,消息收得到后消息清除)

消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。消息被消费以后,queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费
在这里插入图片描述

2. 发布/订阅模式(一对多,消费者消费消息后不会清除消息)

消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。
缺点:会维护一个长轮询,时刻关注有没有消息
在这里插入图片描述

每种模式下还会分为两种情况:1. 消费者主动拉取消息 2. 生产者主动推送消息

1.3 Kafka 基础架构

在这里插入图片描述

  1. Producer : 消息生产者,就是向 Kafka broker添加消息的客户端 ;
  2. Consumer : 消息消费者,向 Kafka broker 取消息的客户端;
  3. Consumer Group (CG): 消费者组,由多个 consumer 组成。 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费消费者组之间互不影响。 所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。(消费者A,B不能同时消费Topic A Partition 0,因为A,B同属与一个消费者组)
  4. Broker :经纪人 一台 Kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker可以容纳多个 topic。
  5. Topic([ˈtɑːpɪk] n.话题;题目;标题) : 话题,可以理解为一个队列, 生产者和消费者面向的都是一个 topic;
  6. Partition: 为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列;
  7. Replica( [ˈreplɪkə] n.复制品;仿制品): 副本(Replication),为保证集群中的某个节点发生故障时, 该节点上的 partition 数据不丢失,且 Kafka仍然能够继续工作, Kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,一个 leader 和若干个 follower。
  8. Leader: 每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 leader。
  9. Follower: 每个分区多个副本中的“从”,实时从 leader 中同步数据,保持和 leader 数据的同步。 leader 发生故障时,某个 Follower 会成为新的 leader。

0.9版本前offset(偏移量:记录消费位置)存储在了zookeeper中,0.9版本之后offset存储在kafka本地的topic中。因为如果存在zookeeper中,消费者每次拉取消息时候都会和zookeeper打交道,太过频繁。
zookeeper给各大框架提供润滑剂的作用

kafka的消息存储在磁盘当中,默认保留7天。

kafka作用:
主题分区和消费者分组提高读写并发度。
主题分区可以提供某个主题的负载均衡
副本,达到容灾效果

问题思考:
消费者获取消息的策略
生产者生产消息的策略

总结:
主题分区:提高读写并行度,实现负载均衡
副本:实现容灾效果

消费者组中的消费者不能同时消费同意分区的数据

2. Kafka快速入门

2.1 安装部署

2.1.1 集群规划

hadoop102hadoop103hadoop104
zkzkzk
kafkakafkakafka

2.1.2 jar包下载

https://kafka.apache.org/downloads.html

在这里插入图片描述

2.1.3 集群部署

  1. 解压安装包
[atguigu@hadoop102 software]$ tar -zxvf kafka_2.11-0.11.0.0.tgz -C 
/opt/module/
  1. 修改解压后的文件名称
[atguigu@hadoop102 module]$ mv kafka_2.11-0.11.0.0/ kafka
  1. 在/opt/module/kafka 目录下创建 logs 文件夹
[atguigu@hadoop102 kafka]$ mkdir logs
  1. 修改配置文件
[atguigu@hadoop102 kafka]$ cd config/
[atguigu@hadoop102 config]$ vi server.properties

输入以下内容

#broker 的全局唯一编号,不能重复
broker.id=0
#删除 topic 功能使能
delete.topic.enable=true
#处理网络请求的线程数量
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 运行日志存放的路径
log.dirs=/opt/module/kafka/logs
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment 文件保留的最长时间,超时将被删除
log.retention.hours=168
#配置连接 Zookeeper 集群地址
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181
  1. 配置环境变量
[atguigu@hadoop102 module]$ sudo vi /etc/profile
#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka
export PATH=$PATH:$KAFKA_HOME/bin
[atguigu@hadoop102 module]$ source /etc/profile
  1. 分发安装包
[atguigu@hadoop102 module]$ xsync kafka/

注意:分发之后记得配置其他机器的环境变量

  1. 修改配置文件的broker.id
    分别在 hadoop103 和 hadoop104 上修改配置文件/opt/module/kafka/config/server.properties
    中的 broker.id=1、broker.id=2
    注:broker.id 不得重复

  2. 启动集群
    依次在 hadoop102、hadoop103、hadoop104 节点上启动 kafka

[atguigu@hadoop102 kafka]$ bin/kafka-server-start.sh -daemon
config/server.properties
[atguigu@hadoop103 kafka]$ bin/kafka-server-start.sh -daemon
config/server.properties
[atguigu@hadoop104 kafka]$ bin/kafka-server-start.sh -daemon
config/server.properties
  1. 关闭集群
[atguigu@hadoop102 kafka]$ bin/kafka-server-stop.sh stop
[atguigu@hadoop103 kafka]$ bin/kafka-server-stop.sh stop
[atguigu@hadoop104 kafka]$ bin/kafka-server-stop.sh stop
  1. kafka 群起脚本
    kafka 脚本
    在这里插入图片描述
for i in hadoop102 hadoop103 hadoop104
do
echo "========== $i ==========" 
ssh $i '/opt/module/kafka/bin/kafka-server-start.sh -daemon 
/opt/module/kafka/config/server.properties'
done

2.2 Kafka命令行操作

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

[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper 
hadoop102:2181 --list

2.2.2 创建topic

[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper 
hadoop102:2181 --create --replication-factor 3 --partitions 1 --
topic first

选项说明:
–topic 定义 topic 名
–replication-factor 定义副本数
–partitions 定义分区数

2.2.3 删除topic

[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper 
hadoop102:2181 --delete --topic first

需要 server.properties 中设置 delete.topic.enable=true 否则只是标记删除。

2.2.4 发送消息

[atguigu@hadoop102 kafka]$ bin/kafka-console-producer.sh --brokerlist hadoop102:9092 --topic first
>hello world
>atguigu atguigu

2.2.5 消费消息

[atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh \
--zookeeper hadoop102:2181 --topic first

[atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh \
--bootstrap-server hadoop102:9092 --topic first

[atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh \
--bootstrap-server hadoop102:9092 --from-beginning --topic first

–from-beginning:会把主题中以往所有的数据都读取出来。

2.2.6 查看某个topic的详情

[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper 
hadoop102:2181 --describe --topic first

2.2.7 修改分区数

[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper 
hadoop102:2181 --alter --topic first --partitions 6

3.Kafka架构深入

3.1 Kafka工作流程及文件存储机制

在这里插入图片描述

Kafka中消息是以topic进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。

topic是逻辑上的概念,partition是物理层面上的概念。每个partition对应着一个log文件,该log文件存储的就是product生产的数据。(topic = N partition,partition = N segment , segment = N .index and .log)
product生产的数据会不断追加到该log文件末端,且每条数据都有自己的offset(偏移量)。消费者组中的每个消费者,都会实时记录自己消费的哪个offset,以便出错回复时,从上次的位置继续消费。

在这里插入图片描述

由于生产者生产的消息会不断追加到log文件末尾,为防止log文件过大导致数据定位效率低下,kafka采取了分片和索引机制,将每个partition分为多个segment( [ˈseɡmənt , seɡˈment] n.部分;份;片;段)。每个segment对应两个文件“.index”文件和“.log”文件。这些文件在同一个文件夹下,该文件夹命名规则为:topic名称+分区序号。例如,命名为first的topic有三个分区,则对应着文件夹为first-0 ,first-1 ,first-2。

00000000000000000000.index
00000000000000000000.log
00000000000000170410.index
00000000000000170410.log
00000000000000239430.index
00000000000000239430.log

新增的.log 怎么命名?
命名为此log文件开始(最小)的偏移量,例:00000000000000000000.log ->00000000000000170410.log(名字位数相同)
kafka数据量大时是怎么快速定位的?
先拿到偏移量,找到对应.index文件(文件名也包含偏移量信息的),然后进入文件使用二分查找发定位所需要消息在.log中的位置

index 和 log文件以当前segment的第一条消息的offset命名。下图为index和log文件的结构示意图。
在这里插入图片描述
.index文件存储了大量的索引信息,.log文件存储大量的数据,索引文件中元数据指向对应数据文件中message的物理偏移地址。

3.2 Kafka 生产者

3.2.1 分区策略

  1. 分区的原因

    1. 方便在集群中扩展,每个partition可以通过调整以适应它所在的机器,而一个topic又可以有多个partition组成,因此整个集群就可以适应任意大小的数据了。
    2. 可以提高高并发,因为可以以partition为单位进行读写。
  2. 分区的原则
    Producer向kafka发送消息是发送的封装成ProducerRecord对象的数据。请添加图片描述

    1. 在指明partition的情况下,直接讲指明的值直接作为partition值;
    2. 没有指明partition值但是有key的情况下,将key的hash值与topic的partition数进行取余得到partition值;
    3. 既没有partition有没有key的情况下,第一次调用时随机产生一个证书(后面每次调用在这个整数上自增),将这个值与topic可用的partition总数取余得到partition值,也就是常说的round-robin算法。

3.2.2 数据可靠性保证

为了保证producer发送数据,能可靠的发送到指定的topic,topic的每个partition收到product发送的数据后,都需要向producer发送ack(acknowledgement 确认收到 [[əkˈnɑːlɪdʒmənt] n.(对事实、现实、存在的)承认;感谢;谢礼;收件复函])如果producer收到ack,就会进行下一轮的发送,否则就会重新发送数据。

在这里插入图片描述

1.何时发送ack?
确保有follower与leader同步完成,leader再发送ack,这样才能保证leader挂掉后,能在follower中选举出新的leader。

2.多少个follower同步完成后发送ack?
现有方案
1. 半数以上的follower同步完成即可发送ack。
2. 全部的follower同步完成,才可以发送ack。

副本数据同步策略:

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

Kafka选择的是第二种方案,原因如下:
1.同样为了容忍n台节点的故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而Kafka的每个分区都有大量的数据,第一种方案会造成大量数据冗余。
2.虽然第二种方案的网络延迟会比较高,但网络延迟对kafka的影响较小。

ISR

采用第二种方案后,设想以下情景:leader收到数据,所有的follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题要怎么解决呢?

leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据同步之后,就会给leader发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms 参数设定。leader发生故障之后,就会从ISR中选举新的leader。

ISR维护flower时在低版本有两个条件:1.响应时间快的。2.同步消息与leader相差少的。高版本只保留了响应时间亏的,因为生产者可以是批次发送消息到kafka集群的,并且先写到leader中,然后再同步到flower中,但是同步时候是有条数限制的。如果批次发送的数量超过同步的数量时,就会造成ISR中没有符合条数条件的。

replica.lag.time.max.ms
DESCRIPTION: If a follower hasn’t sent any fetch requests or hasn’t consumed up to the leaders log end offset for at least this time, the leader will remove the follower from isr
TYPE: long
DEFAULT: 10000
Source

ack应答机制
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据少量丢失,所以没必要等ISR中的follower全部接收成功。
所以kafka为用户提供了三种可靠性级别,用户根据可靠性和延迟性的要求进行权衡,选择以下的配置。

acks参数配置
acks
0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据。
1:producer等待broker的ack,producer的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么会造成数据丢失。
在这里插入图片描述

-1(all):producer等待broker的ack,partition的leader和ISR的follower全部落盘成功后才返回ack。但如果在follower同步完成后,broker发送之前,leader发生故障,那么会造成数据重复。
在这里插入图片描述
助记:返ACK前,0无落盘,1主落盘,-1全落盘,(落盘:消息存到本地)

故障处理细节

在这里插入图片描述

LEO:指的是每个副本中最大的offset;
HW:指的是消费者能见到的最大的offset,ISR队列中最小的LEO。

follower故障
follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始从leader进行同步。等该follower的LEO大于等于该partition的HW,即follower追上leader之后,就可以重新加入ISR。

leader故障
leader故障后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。

注意:这只能保证副本之间的数据一致性。并不能保证数据不丢失或者不重复。

3.2.3 Exactly Once 语义

将服务器的 ACK 级别设置为-1,可以保证 Producer 到 Server 之间不会丢失数据,即 At Least Once 语义。相对的,将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被发送一次,即 At Most Once 语义

At Least Once 可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once可以保证数据不重复,但是不能保证数据不丢失。但是,对于一些非常重要的信息,比如说交易数据,下游数据消费者要求数据既不重复也不丢失,即 Exactly Once 语义。在 0.11 版本以前的 Kafka,对此是无能为力的,只能保证数据不丢失,再在下游消费者对数据做全局去重。对于多个下游应用的情况,每个都需要单独做全局去重,这就对性能造成了很大影响。

0.11 版本的 Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。幂等性结合 At Least Once 语义,就构成了 Kafka 的 Exactly Once 语义。即:

At Least Once + 幂等性 = Exactly Once

要启用幂等性,只需要将 Producer 的参数中== enable.idompotence 设置为 true== 即可。Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。

但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区跨会话的 Exactly Once。

3.3 Kafka消费者

3.3.1 消费方式

consumer是采用pull(拉)的方式从broker中读取数据的。
push(推)模式很难适应消费速率不同的消费者,因为消息的发送速率是有broker决定的。他的目标尽可能是以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

pull的不足之处在于,当kafka没有数据时,消费者可能会陷入循环之中,一直返回空数据。针对这一点,kafka的消费者在消费数据时候会传入一个时长参数timeout,如果当前没有数据可以消费,consumer会等待一段时间之后返回,这段时长为timeout

3.3.2 分区分配策略

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

kafka有两种分配策略,一是RoundRobin ,二是Range。

在这里插入图片描述
图中,分配策略,如果是RoundRobin,则会以消费者组分partition,即使A没有订阅topic2,A也会拉取到topic2中的消息。
如果是Range分配策略,会按照topic进行分配,topic1被三个消费者,两个消费者组订阅,则A将会分配给T1(0,1)两个partition,B分配T1(2),T2(0,1,2)partition,C分配T1(0,1,2)和T2(0,1,2)partition。

分区策略,RoundRobin按照消费者组分 多个partition轮训分给消费者
Range 按照topic分 每个消费者对应着一个partition 默认分区策略

3.3.3 offset的维护

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

在这里插入图片描述
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka的一个内置topic中,该topic为_consumer_offsets。

offset 消费者消费了10条数据后,消费者组又增加一条一个消费者B,按照分区策略规则,分配给B一个patition后,B获取消息是从第11条数据开始的。

offset是根据消费者组保存的 (由消费者组,分区,话题确定值)

查看
1. 修改配置文件

exclude.internal.topics=false
2. 读取offset
0.11.0.0之前的版本:
bin/kafka-console-consumer.sh --topic __consumer_offsets --
zookeeper hadoop102:2181 --formatter 
"kafka.coordinator.GroupMetadataManager\$OffsetsMessageFormatter" 
--consumer.config config/consumer.properties --from-beginning
0.11.0.0之后的版本:
bin/kafka-console-consumer.sh --topic __consumer_offsets --
zookeeper hadoop102:2181 --formatter 
"kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageForm
atter" --consumer.config config/consumer.properties --frombeginning

3.3.4 消费者组案例

  1. 需求:测试同一个消费者组中的消费者,同一时刻只有一个消费者消费。
  2. 案例实操
    (1)在hadoop102、hadoop103 上修改/opt/module/kafka/config/consumer.properties 配置
    文件中的 group.id 属性为任意组名。
[atguigu@hadoop103 config]$ vi consumer.properties
group.id=atguigu
	(2)在 hadoop102、hadoop103 上分别启动消费者
[atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh \
--zookeeper hadoop102:2181 --topic first --consumer.config 
config/consumer.properties
[atguigu@hadoop103 kafka]$ bin/kafka-console-consumer.sh --
bootstrap-server hadoop102:9092 --topic first --consumer.config 
config/consumer.properties
	(3)在 hadoop104 上启动生产者
[atguigu@hadoop104 kafka]$ bin/kafka-console-producer.sh \
--broker-list hadoop102:9092 --topic first
>hello world
	(4)查看 hadoop102 和 hadoop103 的接收者。

同一时刻只有一个消费者接收消息。

3.4 Kafka高效读写数据

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

2.零复制技术
在文件复制时候,不经过用户空间,直接由系统空间完成,这样可以节省大量时间。
在这里插入图片描述

3.5 Zookeeper在Kafka中的作用

kafka集群中有一个broker会被选举为controller,负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。
controller的管理工作都是依赖于Zookeeper的。

以下是partition的leader选举过程:

在这里插入图片描述

3.6 Kafka事务

Kafka从0.11版本开始引入了事务支持。事务可以保证Kafka在Exactly Once语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。

3.6.1 Producer事务

为了实现跨分区跨会话的事务,需引入一个全局唯一的TransactionID,并将Producer获得的PID和TransactionID绑定。这样当Producer重启后就可以通过正在进行的TransactionID获得原来的PID。

为了管理Transaction,Kafka引入了一个新的组件Transaction coordinator。Producer就是通过Transaction Coordinator 交互获得TransactionID对应的任务状态。Transaction Coordinator 还将负责将事务写入所有Kafka的一个内部Topic,这样即使整个服务重启,由于事务事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。

事务是解决了精准一次性写到kafka集群,加入事务可以保证跨会话,跨分区级别。

3.6.2 Consumer事务

上述事务机制主要是从Productor方面考虑,对于consumer而言,事务的保证机会相对较弱,尤其是无法保证commit的信息被精确消费。这是由于consumer可以通过offset访问任意信息,而且不同的segment File生命周期不同,同一事务的消息可能会出现重启后被删除的情况。

4. Kafka API

4.1 Producer API

4.1.1 消息发送流程

Kafka的Producer发送消息采用的是异步发送(send方法)方式。在消息发送过程中,涉及到了两个线程——main线程和Sender线程,以及一个线程共享变量——RecordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送给Kafka broker。

在这里插入图片描述

其中涉及两个相关参数:
batch.size:只有数据积累到batch.size之后,sender才会发送数据。
linger.ms:如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。

4.1.2 异步发送API

1.导入依赖
<dependency>
	<groupId>org.apache.kafka</groupId>
	<artifactId>kafka-clients</artifactId>
	<version>0.11.0.0</version>
</dependency>
2.编写代码

需要用到的类:
KafkaProducer:需要创建一个生产者对象,用来发送数据
ProducerConfig:获取所需的一系列配置参数
ProducerRecord:每条数据都要封装成一个ProducerRecord对象

不带回调函数API

在这里插入图片描述

package com.erp.platform.controller.workflow;

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;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 10:26
 */
public class kafka {
    public static void main(String[] args) {
        //1.创建kafka生产者的配置信息
        Properties properties = new Properties();

        //2.指定连接的kafka集群
        // properties.put("bootstrap.server", "hadoop102:9092");
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");

        //3.ack应答级别 默认值all
        // properties.put("acks", "all");
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        //4.重试次数 默认值3
        // properties.put("retries", 3);
        properties.put(ProducerConfig.RETRIES_CONFIG, 3);

        //5.批次大小 默认值16384
        // properties.put("batch.size", 16384);
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

        //6.等待时间 默认值1
        // properties.put("linger.ms", 1);
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

        //7.RecordAccumulator缓冲区大小 默认32m
        // properties.put("buffer.memory", 33554432);
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);

        //8.key,value的序列化
        // properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, 
        			"org.apache.kafka.common.serialization" + ".StringSerializer");
        // properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
        			 "org.apache.kafka.common.serialization" + ".StringSerializer");

        //9.创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        //10.发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("topicName", "massage" + i));
        }

        //11.关闭资源
        producer.close();
    }
}

在这里插入图片描述

带回调函数的API

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

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

package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 10:26
 */
public class kafka {
    public static void main(String[] args) {
        //1.创建kafka生产者的配置信息
        Properties properties = new Properties();

        //2.指定连接的kafka集群
        // properties.put("bootstrap.server", "hadoop102:9092");
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");

        //3.ack应答级别 默认值all
        // properties.put("acks", "all");
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        //4.重试次数 默认值3
        // properties.put("retries", 3);
        properties.put(ProducerConfig.RETRIES_CONFIG, 3);

        //5.批次大小 默认值16384
        // properties.put("batch.size", 16384);
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

        //6.等待时间 默认值1
        // properties.put("linger.ms", 1);
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

        //7.RecordAccumulator缓冲区大小 默认32m
        // properties.put("buffer.memory", 33554432);
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);

        //8.key,value的序列化
        // properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
        			 "org.apache.kafka.common.serialization" + ".StringSerializer");
        // properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
        			 "org.apache.kafka.common.serialization" + ".StringSerializer");

        //9.创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        //10.发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("topicName", "massage" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    // 成功返回RecordMetadata数据对象,失败返回错误信息Exception
                    if (e == null) {
                        System.out.println(recordMetadata.partition() +
                        			 "=========" + recordMetadata.offset());
                    } else {
                        e.printStackTrace();
                    }
                }
            });
        }

        //11.关闭资源
        producer.close();
    }
}

自定义分区器

分区器代码:

package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;

/**
 * 自定义分区器
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 13:58
 */
public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
        // 分区器逻辑
        // 与业务相关的代码,此处返回0,表示消息都添加进了0号分区
        return 0;
    }

    @Override
    public void close() {
		//无关
    }

    @Override
    public void configure(Map<String, ?> map) {
		//无关
    }
}

结合生产者使用自定义分区器

  // 添加分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.xxx.xxx.MyPartitioner");
package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 10:26
 */
public class PartitionProducer {
    public static void main(String[] args) {
        //1.创建kafka生产者的配置信息
        Properties properties = new Properties();

        //2.指定连接的kafka集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");

        //3.ack应答级别 默认值all
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        //4.重试次数 默认值3
        properties.put(ProducerConfig.RETRIES_CONFIG, 3);

        //5.批次大小 默认值16384
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

        //6.等待时间 默认值1
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

        //7.RecordAccumulator缓冲区大小 默认32m
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);


        //8.key,value的序列化
        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");

        // 添加分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.xxx.xxx.MyPartitioner");
        
        //9.创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        //10.发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("topicName", "massage" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    // 成功返回RecordMetadata数据对象,失败返回错误信息Exception
                    if (e == null) {
                        System.out.println(recordMetadata.partition() + "=========" 
                        					+ recordMetadata.offset());
                    } else {
                        e.printStackTrace();
                    }
                }
            });
        }

        //11.关闭资源
        producer.close();
    }
}

4.1.3 同步发送API

使用较少,同步发送会阻塞主线程

只需加.get(),多线程相关的知识。需要抛异常。

producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i))).get();

4.2 Consumer API

consumer消费数据时的可靠性是很容易保证的,因为数据在kafka中是持久化的,故不用担心数据丢失问题。

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

所以offs的维护是consumer消费数据必须考虑的问题。

4.2.1 自动提交offset

1.导入依赖
<dependency>
	<groupId>org.apache.kafka</groupId>
	<artifactId>kafka-clients</artifactId>
	<version>0.11.0.0</version>
</dependency>
2.编写代码

需要用到的类:
KafkaConsumer:需要创建一个消费者对象用来消费消费数据。
ConsumerConfig:获取所需的一系列配置参数
ConsumerRecord:每条数据都要封装成一个ConsumerRecord对象

便于专注于业务逻辑,Kafka提供了自动提交offset的功能。
自动提交offset相关参数:
enable.auto.commit:是否开启自动提交offset功能
auto.commit.interval.ms:自动提交offset的时间间隔

自动提交offset的代码:

package com.erp.platform.controller.workflow;

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

import java.util.Arrays;
import java.util.Properties;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 15:00
 */
public class MyConsumer {
    public static void main(String[] args) {
        //1.创建消费者配置信息
        Properties properties = new Properties();

        //2.给配置信息赋值
        //连接的集群
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
        //开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        //自动提交延时
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

        //key, value的反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
        			 "org.apache.kafka.common.serialization" + ".StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, 
        			"org.apache.kafka.common.serialization" + ".StringDeserializer");

        //消费者组
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "groupName");

        //创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        //订阅主题
        consumer.subscribe(Arrays.asList("topicName", "first"));

        //获取数据,长轮询获取数据

        while (true) {
            // timeout:没有拉取到数据下次拉取的时间间隔
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);

            //解析并打印consumerRecords
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key() + "=====" + consumerRecord.value());
            }
        }
        //长轮询不用删除
        //consumer.close();
    }
}

ConsumerRecords和ConsumerRecord是两个不同的类

重置消费者的offset

重置消费者的offset
两个值,一个最早的offset earliest
一个最晚的offset latest(默认)

//2.给配置信息赋值
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");

有两种情况可以使配置生效:
一是更换了消费者组
二是长时间获取数据,消息过了7天的默认保存时间,当前的offset已经不存在

以earliest为例
消费者组更换后,会从当前配置的主题的最早数据进行读取

4.2.2 手动提交offset

虽然自动提交offset十分便利,但由于其是基于时间提交的,开发人员难以把握offset提交时机。因此Kafka还提供了手动提交offset的Api。

手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。两者的相同特点是,都会将本次poll的一批数据最高的偏移量提交;不同点是commitSync阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而commitAsync则没有失败重试机制,因此有可能提交失败。

同步提交offset

由于同步提交offset有失败重试机制,故更加可靠

涉及代码

 		//开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
            //同步提交,当前线程会阻塞知道offset提交成功
            consumer.commitSync();
package com.erp.platform.controller.workflow;

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

import java.util.Arrays;
import java.util.Properties;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 15:00
 */
public class MyConsumer {
    public static void main(String[] args) {
        //1.创建消费者配置信息
        Properties properties = new Properties();

        //2.给配置信息赋值
        //连接的集群
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
        //开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        //自动提交延时
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

        //key, value的反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
	        	 "org.apache.kafka.common.serialization" + ".StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
         		"org.apache.kafka.common.serialization" + ".StringDeserializer");

        //消费者组
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "groupName");

        //创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        //订阅主题
        consumer.subscribe(Arrays.asList("topicName", "first"));

        //获取数据,长轮询获取数据

        while (true) {
            // timeout:没有拉取到数据下次拉取的时间间隔
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);

            //解析并打印consumerRecords
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key() + "=====" + consumerRecord.value());
            }
            //同步提交,当前线程会阻塞知道offset提交成功
            consumer.commitSync();
        }
    }
}

异步提交offset

虽然同步提交 offset 更可靠一些,但是由于其会阻塞当前线程,直到提交成功。因此吞吐量会收到很大的影响。因此更多的情况下,会选用异步提交 offset 的方式。
涉及代码

 		//开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
            //异步提交
            consumer.commitAsync(new OffsetCommitCallback() {
                @Override
                public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
                    if (e != null) {
                        System.err.println("Commit failed for" + map);
                    }
                }
            });
package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

import java.util.Arrays;
import java.util.Map;
import java.util.Properties;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 15:00
 */
public class MyConsumer {
    public static void main(String[] args) {
        //1.创建消费者配置信息
        Properties properties = new Properties();

        //2.给配置信息赋值
        //连接的集群
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
        //开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        //自动提交延时
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

        //key, value的反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, 
        			"org.apache.kafka.common.serialization" + ".StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, 
        			"org.apache.kafka.common.serialization" + ".StringDeserializer");

        //消费者组
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "groupName");

        //创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        //订阅主题
        consumer.subscribe(Arrays.asList("topicName", "first"));

        //获取数据,长轮询获取数据

        while (true) {
            // timeout:没有拉取到数据下次拉取的时间间隔
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);

            //解析并打印consumerRecords
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key() + "=====" + consumerRecord.value());
            }
            //异步提交
            consumer.commitAsync(new OffsetCommitCallback() {
                @Override
                public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
                    if (e != null) {
                        System.err.println("Commit failed for" + map);
                    }
                }
            });
        }
    }
}

数据漏消费和重复消费分析

提交是提交到kafka本地
同步提交:提交成功后才会再次拉取数据。效率低
异步提交
提交时间比较短时可能会丢失数据,例如:处理数据过程中就提交了offset,但是这个时候消费者挂掉,因为已经提交了offset,这些数据就会无法再次获取到。
提交时间比较长时可能会造成数据重复,例如:提交时间为10s,当在5s是数据就已经保存到了mysql,但是此时消费者挂掉,再次启动消费者时候就又会获取到当前数据,造成数据重复。

4.2.3 自定义存储offset

关闭自动提交
当关闭自动提交时候,且没有进行手动提交时候会造成下一次开启kafka时候会从上次获取消息的位置重新获取数据。
例如:当前offset在10位置,生产者又添加了10条数据,现在有了20条,消费者获取了新的10数据,但是没有提交。关闭消费,再重新打开时候,消费者就又从第10条数据开始获取数据。

Kafka 0.9 版本之前,offset 存储在 zookeeper,0.9 版本及之后,默认将 offset 存储在 Kafka的一个内置的 topic 中。除此之外,Kafka 还可以选择自定义存储 offset。

offset 的维护是相当繁琐的,因为需要考虑到消费者的 Rebalace。

当有新的消费者加入消费者组、已有的消费者推出消费者组或者所订阅的主题的分区发生变化,就会触发到分区的重新分配,重新分配的过程叫做 Rebalance。

消费者发生 Rebalance 之后,每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区,并且定位到每个分区最近提交的 offset 位置继续消费。

要实现自定义存储 offset,需要借助 ConsumerRebalanceListener,以下为示例代码,其中提交和获取 offset 的方法,需要根据所选的 offset 存储系统自行实现。

代码实现:

主要涉及代码:
在Rebalance之前要对offset进行自定义存储(可以存储在mysql中,在commitOffset方法中实现)
在Rebalance之后对offset进行获取(同样是从mysql中,可以设计库表为组,主题,分区,offset列进行存储)

		 //订阅主题
        consumer.subscribe(Arrays.asList("topicName", "first"), new ConsumerRebalanceListener() {
            //该方法在Rebalance之前调用
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> collection) {
                commitOffset(currentOffset);
            }

            //该方法在Rebalance之后调用
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> collection) {
                currentOffset.clear();
                for (TopicPartition topicPartition : collection) {
                    //定位到最近提交的offset位置继续消费
                    consumer.seek(topicPartition, getOffset(topicPartition));
                }
            }
        });
	//获取某分区的最新 offset
    private static long getOffset(TopicPartition partition) {
        return 0;
    }

    //提交该消费者所有分区的 offset
    private static void commitOffset(Map<TopicPartition, Long> currentOffset) {

    }
package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

import java.util.*;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 15:00
 */
public class MyConsumer {
    private static Map<TopicPartition, Long> currentOffset = new HashMap<>();

    public static void main(String[] args) {
        //1.创建消费者配置信息
        Properties properties = new Properties();

        //2.给配置信息赋值
        //连接的集群
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
        //开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        //自动提交延时
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

        //key, value的反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, 
        			"org.apache.kafka.common.serialization" +  ".StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, 
        			"org.apache.kafka.common.serialization" + ".StringDeserializer");

        //消费者组
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "groupName");

        //创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        //订阅主题
        consumer.subscribe(Arrays.asList("topicName", "first"), new ConsumerRebalanceListener() {
            //该方法在Rebalance之前调用
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> collection) {
                commitOffset(currentOffset);
            }

            //该方法在Rebalance之后调用
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> collection) {
                currentOffset.clear();
                for (TopicPartition topicPartition : collection) {
                    //定位到最近提交的offset位置继续消费
                    consumer.seek(topicPartition, getOffset(topicPartition));
                }
            }
        });

        //获取数据,长轮询获取数据

        while (true) {
            // timeout:没有拉取到数据下次拉取的时间间隔
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);

            //解析并打印consumerRecords
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key() + "=====" + consumerRecord.value());
            }
            //同步提交,当前线程会阻塞知道offset提交成功
            consumer.commitAsync(new OffsetCommitCallback() {
                @Override
                public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
                    if (e != null) {
                        System.err.println("Commit failed for" + map);
                    }
                }
            });
        }
    }

    //获取某分区的最新 offset
    private static long getOffset(TopicPartition partition) {
        return 0;
    }

    //提交该消费者所有分区的 offset
    private static void commitOffset(Map<TopicPartition, Long> currentOffset) {

    }

}

4.3 自定义 Interceptor

4.3.1 拦截器原理

Producer 拦截器(interceptor)是在 Kafka 0.10 版本被引入的,主要用于实现 clients 端的定制化控制逻辑。

对于 producer 而言,interceptor 使得用户在消息发送前以及 producer 回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer 允许用户指定多个 interceptor按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor 的实现接口是org.apache.kafka.clients.producer.ProducerInterceptor,其定义的方法包括:
(1)configure(configs)
获取配置信息和初始化数据时调用。

(2)onSend(ProducerRecord):
该方法封装进 KafkaProducer.send 方法中,即它运行在用户主线程中。Producer 确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的 topic 和分区,否则会影响目标分区的计算。

(3)onAcknowledgement(RecordMetadata, Exception):
该方法会在消息从 RecordAccumulator 成功发送到 Kafka Broker 之后,或者在发送过程中失败时调用。并且通常都是在 producer 回调逻辑触发之前。onAcknowledgement 运行在producer 的 IO 线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢 producer 的消息发送效率。

(4)close:
关闭 interceptor,主要用于执行一些资源清理工作

如前所述,interceptor 可能被运行在多个线程中,因此在具体实现时用户需要自行确保线程安全。另外倘若指定了多个 interceptor,则 producer 将按照指定顺序调用它们,并仅仅是捕获每个 interceptor 可能抛出的异常记录到错误日志中而非在向上传递。这在使用过程中要特别留意。

4.2.3 拦截器案例

1)需求:
实现一个简单的双 interceptor 组成的拦截链。第一个 interceptor 会在消息发送前将时间戳信息加到消息 value 的最前部;第二个 interceptor 会在消息发送后更新成功发送消息数或失败发送消息数。
在这里插入图片描述

2)案例实操

增加时间戳拦截器

ProducerRecord类不支持单个属性的更改,所以要new一个ProducerRecord赋值后返回

package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/26 11:11
 */
public class TimeInterceptor implements ProducerInterceptor<String, String> {
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> producerRecord) {
        String value = producerRecord.value();

        return new ProducerRecord(producerRecord.topic(), producerRecord.partition(), producerRecord.key(),
                System.currentTimeMillis() + "," + value);
    }

    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {

    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

统计发送消息成功和发送失败消息数,并在 producer 关闭时打印这两个计数器

拦截器类中的onSend方法即使没有用到也要进行更改,如果不进行更改就会返回null,这样所有的被拦截的ProducerRecord都会变成null
拦截器中的close方法,只有当对应的消费者类中的close方法执行之后才会执行

package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/26 11:20
 */
public class CounterInterceptor implements ProducerInterceptor<String, String> {

    private int error;
    private int success;

    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> producerRecord) {
        return producerRecord;
    }

    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {
        if (recordMetadata != null) {
            success++;
        } else {
            error++;
        }
    }

    @Override
    public void close() {
        System.out.println("success" + success);
        System.out.println("error" + error);
    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

producer 主程序

配置拦截器时候,对应的是一个集合,拦截器执行顺序就是集合中拦截器的添加顺序

        // 添加拦截器
        List<String> interceptors = new ArrayList<>();
        interceptors.add("com.erp.platform.controller.workflow.TimeInterceptor");
        interceptors.add("com.erp.platform.controller.workflow.CounterInterceptor");
        properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);

只有当配置此拦截器的生产者类中的close方法执行之后,此拦截器中的close方法才会执行


        //11.一定要关闭 producer,这样才会调用 interceptor 的 close 方法
        producer.close();
package com.erp.platform.controller.workflow;

import org.apache.kafka.clients.producer.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.controller.workflow
 * @date 2022/1/25 10:26
 */
public class PartitionProducer {
    public static void main(String[] args) {
        //1.创建kafka生产者的配置信息
        Properties properties = new Properties();

        //2.指定连接的kafka集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");

        //3.ack应答级别 默认值all
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        //4.重试次数 默认值3
        properties.put(ProducerConfig.RETRIES_CONFIG, 3);

        //5.批次大小 默认值16384
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

        //6.等待时间 默认值1
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

        //7.RecordAccumulator缓冲区大小 默认32m
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);

        //8.key,value的序列化
        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");

        // 添加拦截器
        List<String> interceptors = new ArrayList<>();
        interceptors.add("com.erp.platform.controller.workflow.TimeInterceptor");
        interceptors.add("com.erp.platform.controller.workflow.CounterInterceptor");
        properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);

        //9.创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        //10.发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("topicName", "massage" + i));
        }

        //11.一定要关闭 producer,这样才会调用 interceptor 的 close 方法
        producer.close();
    }
}

5. Kafka Eagle的安装

链接: Kafka Eagle官方主页.
链接: Kafka Eagle下载页面.
链接: Kafka Eagle官方文档.

5.1 什么是Kafka Eagle

Kafka Eagle is open source visualization and management software. It allows you to query, visualize, alert on, and explore your metrics no matter where they are stored. In plain English, it provides you with tools to turn your kafka cluster data into beautiful graphs and visualizations.
Kafka Eagle是开源可视化和管理软件。它允许您查询、可视化、提醒和探索您的指标,无论它们存储在哪里。简单地说,它为您提供了将kafka集群数据转换为漂亮的图形和可视化的工具。

一个运行在Tomcat的Web应用。

5.2 安装与运行

在Windows上安装与运行

  • 安装JDK,设置环境变量时,路径最好不要带空格,否则,后序运行ke.bat抛异常。若路径必须带有空格,可以通过小技巧,让ke.bat成功运行。这个技巧是:若你的JAVA_HOME的变量值为C:\Program Files\Java\jdk1.8.0_161,则将其改成C:\progra~1\Java\jdk1.8.0_161。
  • 到Kafka Eagle下载页面下载安装包。
  • 解压安装包,然后设置环境变量KE_HOME,其值如C:\Kafka\kafka-eagle-web-1.3.7。若想打开cmd输入命令ke.bat运行Kafka Eagle,在PATH环境变量的值头添加%KE_HOME%\bin;
  • 修改配置文件%KE_HOME%\conf\system-config.properties
  • (可选)Kafka Server的JVM调参,用文本编辑器打开%KAFKA_HOME%\bin\windows\kafka-server-start.bat,其中的set KAFKA_HEAP_OPTS=-Xmx1G -Xms1G改为set KAFKA_HEAP_OPTS=-server -Xms2G -Xmx2G -XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70
######################################
# multi zookeeper&kafka cluster list
######################################<---设置ZooKeeper的IP地址
kafka.eagle.zk.cluster.alias=cluster1
cluster1.zk.list=localhost:2181

######################################
# zk client thread limit
######################################
kafka.zk.limit.size=25

######################################
# kafka eagle webui port
######################################
kafka.eagle.webui.port=8048

######################################
# kafka offset storage
######################################
cluster1.kafka.eagle.offset.storage=kafka
#cluster2.kafka.eagle.offset.storage=zk

######################################
# enable kafka metrics
######################################<---metrics.charts从false改成true
kafka.eagle.metrics.charts=true
kafka.eagle.sql.fix.error=false

######################################
# kafka sql topic records max
######################################
kafka.eagle.sql.topic.records.max=5000

######################################
# alarm email configure
######################################
kafka.eagle.mail.enable=false
kafka.eagle.mail.sa=alert_sa@163.com
kafka.eagle.mail.username=alert_sa@163.com
kafka.eagle.mail.password=mqslimczkdqabbbh
kafka.eagle.mail.server.host=smtp.163.com
kafka.eagle.mail.server.port=25

######################################
# alarm im configure
######################################
#kafka.eagle.im.dingding.enable=true
#kafka.eagle.im.dingding.url=https://oapi.dingtalk.com/robot/send?access_token=

#kafka.eagle.im.wechat.enable=true
#kafka.eagle.im.wechat.token=https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=xxx&corpsecret=xxx
#kafka.eagle.im.wechat.url=https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=
#kafka.eagle.im.wechat.touser=
#kafka.eagle.im.wechat.toparty=
#kafka.eagle.im.wechat.totag=
#kafka.eagle.im.wechat.agentid=

######################################
# delete kafka topic token
######################################
kafka.eagle.topic.token=keadmin

######################################
# kafka sasl authenticate
######################################
cluster1.kafka.eagle.sasl.enable=false
cluster1.kafka.eagle.sasl.protocol=SASL_PLAINTEXT
cluster1.kafka.eagle.sasl.mechanism=PLAIN
cluster1.kafka.eagle.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="kafka-eagle";

#cluster2 在此没有用到,将其注释掉
#cluster2.kafka.eagle.sasl.enable=false
#cluster2.kafka.eagle.sasl.protocol=SASL_PLAINTEXT
#cluster2.kafka.eagle.sasl.mechanism=PLAIN
#cluster2.kafka.eagle.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="kafka-eagle";

######################################
# kafka jdbc driver address
######################################
kafka.eagle.driver=org.sqlite.JDBC
#将url设置在本地
kafka.eagle.url=jdbc:sqlite:/C:/Kafka/kafka-eagle-web-1.3.7/db/ke.db
#进入系统需要用到的账号与密码
kafka.eagle.username=root
kafka.eagle.password=123456

运行

  • 运行ZooKeeper
  • 运行Kafka集群,另外运行kafka server前,需设置JMX_PORT,否则Kafka Eagle 后台提示连接失败。执行命令行set JMX_PORT=9999 & start bin\windows\kafka-server-start.bat config\server.properties设置JMX_PORT且运行Kafkaserver。在单节点开启Kafka集群,小心端口号冲突。
  • 点击%KE_HOME%\bin\ke.bat,运行Kafka Eagle。
  • 打开浏览器,在地址栏输入http://localhost:8048/ke/,然后在登录页面,输入在配置文件%KE_HOME%\conf\system-config.properties设置的账号与密码。
  • 登录成功,便可进入Kafka Eagle
    在这里插入图片描述

5.3 Kafka Eagle的使用

链接: QUICK START.

6. Kafka之与Flume对接

Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。

学习Flume时,再补充

7.Kafka面试题

  • Kafka 中的 ISR(InSyncRepli)、 OSR(OutSyncRepli)、 AR(AllRepli)代表什么?
  • Kafka 中的 HW、 LEO 等分别代表什么?
  • Kafka 中是怎么体现消息顺序性的?
  • Kafka 中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序是什么?
  • Kafka 生产者客户端的整体结构是什么样子的?使用了几个线程来处理?分别是什么?
  • “消费组中的消费者个数如果超过 topic 的分区,那么就会有消费者消费不到数据”这句 话是否正确?
  • 消费者提交消费位移时提交的是当前消费到的最新消息的 offset 还是 offset+1?
  • 有哪些情形会造成重复消费?
  • 那些情景会造成消息漏消费?
  • 当你使用 kafka-topics.sh 创建(删除)了一个 topic 之后, Kafka 背后会执行什么逻辑?
    会在 zookeeper 中的/brokers/topics 节点下创建一个新的 topic 节点,如:/brokers/topics/first
    触发 Controller 的监听程序
    kafka Controller 负责 topic 的创建工作,并更新 metadata cache
  • topic 的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
  • topic 的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
  • Kafka 有内部的 topic 吗?如果有是什么?有什么所用?
  • Kafka 分区分配的概念?
  • 简述 Kafka 的日志目录结构?
  • 如果我指定了一个 offset, Kafka Controller 怎么查找到对应的消息?
  • 聊一聊 Kafka Controller 的作用?
  • Kafka 中有那些地方需要选举?这些地方的选举策略又有哪些?
  • 失效副本是指什么?有那些应对措施?
  • Kafka 的哪些设计让它有如此高的性能?

题库
1.Kafka中的ISR(InSyncRepli)、OSR(OutSyncRepli)、AR(AllRepli)代表什么?
ISR : 速率和leader相差低于10秒的follower的集合
OSR : 速率和leader相差大于10秒的follower
AR : 所有分区的follower

2.Kafka中的HW、LEO等分别代表什么?
HW : 又名高水位,根据同一分区中,最低的LEO所决定
LEO : 每个分区的最高offset

3.Kafka的用途有哪些?使用场景如何?
1.用户追踪:根据用户在web或者app上的操作,将这些操作消息记录到各个topic中,然后消费者通过订阅这些消息做实时的分析,或者记录到HDFS,用于离线分析或数据挖掘
2.日志收集:通过kafka对各个服务的日志进行收集,再开放给各个consumer
3.消息系统:缓存消息
4.运营指标:记录运营监控数据,收集操作应用数据的集中反馈,如报错和报告

4.Kafka中是怎么体现消息顺序性的?
每个分区内,每条消息都有offset,所以只能在同一分区内有序,但不同的分区无法做到消息顺序性

5.“消费组中的消费者个数如果超过topic的分区,那么就会有消费者消费不到数据”这句话是否正确?
消费模式有两种情况,一种是以topic分,另一种是以消费者组分。以topic分时会出现这种情况,因为消费者组消费者共同消费topic中的所有的partition的。如果是以消费者分,消费者组中的消费者都能消费所有partition中的数据。

6. 有哪些情形会造成重复消费?或丢失信息?

先处理后提交offset,会造成重读消费
先提交offset后处理,会造成数据丢失

7.Kafka 分区的目的?
对于kafka集群来说,分区可以做到负载均衡,对于消费者来说,可以提高并发度,提高读取效率

8.Kafka 的高可靠性是怎么实现的?
为了实现高可靠性,kafka使用了订阅的模式,并使用isr和ack应答机制
能进入isr中的follower和leader之间的速率不会相差10秒
当ack=0时,producer不等待broker的ack,不管数据有没有写入成功,都不再重复发该数据
当ack=1时,broker会等到leader写完数据后,就会向producer发送ack,但不会等follower同步数据,如果这时leader挂掉,producer会对新的leader发送新的数据,在old的leader中不同步的数据就会丢失
当ack=-1或者all时,broker会等到leader和isr中的所有follower都同步完数据,再向producer发送ack,有可能造成数据重复

9.topic的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
可以增加

bin/kafka-topics.sh --zookeeper localhost:2181/kafka --alter --topic topic-config --partitions 3

10.topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
不可以,先有的分区数据难以处理

11.简述Kafka的日志目录结构?
每一个分区对应一个文件夹,命名为topic-0,topic-1,每个文件夹内有.index和.log文件

12.如何解决消费者速率低的问题?
增加分区数和消费者数

13.Kafka的那些设计让它有如此高的性能??
1.kafka是分布式的消息队列
2.对log文件进行了segment,并对segment建立了索引
3.(对于单节点)使用了顺序读写,速度可以达到600M/s
4.引用了zero拷贝,在os系统就完成了读写操作

14.kafka启动不起来的原因?
在关闭kafka时,先关了zookeeper,就会导致kafka下一次启动时,会报节点已存在的错误
只要把zookeeper中的zkdata/version-2的文件夹删除即可

15.聊一聊Kafka Controller的作用?
负责kafka集群的上下线工作,所有topic的副本分区分配和选举leader工作

16.Kafka中有那些地方需要选举?这些地方的选举策略又有哪些?
在ISR中需要选择,选择策略为先到先得

17.失效副本是指什么?有那些应对措施?
失效副本为速率比leader相差大于10秒的follower
将失效的follower先提出ISR
等速率接近leader10秒内,再加进ISR

18.Kafka消息是采用Pull模式,还是Push模式?
在producer阶段,是向broker用Push模式
在consumer阶段,是向broker用Pull模式
在Pull模式下,consumer可以根据自身速率选择如何拉取数据,避免了低速率的consumer发生崩溃的问题
但缺点是,consumer要时不时的去询问broker是否有新数据,容易发生死循环,内存溢出

19.Kafka创建Topic时如何将分区放置到不同的Broker中?
首先副本数不能超过broker数
第一分区是随机从Broker中选择一个,然后其他分区相对于0号分区依次向后移
第一个分区是从nextReplicaShift决定的,而这个数也是随机产生的

20.Kafka中的事务是怎么实现的?☆☆☆☆☆
kafka事务有两种
producer事务和consumer事务
producer事务是为了解决kafka跨分区跨会话问题
kafka不能跨分区跨会话的主要问题是每次启动的producer的PID都是系统随机给的
所以为了解决这个问题
我们就要手动给producer一个全局唯一的id,也就是transaction id 简称TID
我们将TID和PID进行绑定,在producer带着TID和PID第一次向broker注册时,broker就会记录TID,并生成一个新的组件__transaction_state用来保存TID的事务状态信息
当producer重启后,就会带着TID和新的PID向broker发起请求,当发现TID一致时
producer就会获取之前的PID,将覆盖掉新的PID,并获取上一次的事务状态信息,从而继续上次工作
consumer事务相对于producer事务就弱一点,需要先确保consumer的消费和提交位置为一致且具有事务功能,才能保证数据的完整,不然会造成数据的丢失或重复

21.Kafka中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序是什么?
拦截器>序列化器>分区器

22.Kafka生产者客户端的整体结构是什么样子的?使用了几个线程来处理?分别是什么?
在这里插入图片描述

使用两个线程:
main线程和sender线程
main线程会依次经过拦截器,序列化器,分区器将数据发送到RecourdAccumlator(线程共享变量)
再由sender线程从RecourdAccumlator中拉取数据发送到kafka broker
相关参数:
batch.size:只有数据积累到batch.size之后,sender才会发送数据。
linger.ms:如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。

23.消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?
offset + 1
图示:
在这里插入图片描述

生产者发送数据offset是从0开始的
在这里插入图片描述

消费者消费的数据offset是从offset+1开始的

链接: Kafka常见面试题.

8.快捷启动Kafka的Python脚本代码

8.1 启动单个Kafka

# -*- coding: utf-8 -*
import os
import time
import subprocess

startZooKeeperServerCmd = 'start bin\windows\zookeeper-server-start.bat config\zookeeper.properties'

startKafkaServerCmd = 'set JMX_PORT=9999 &
				 start bin\windows\kafka-server-start.bat config\server.properties'

print('Starting ZooKeeper Server...')
subprocess.Popen(startZooKeeperServerCmd, shell=True)
time.sleep(10)

# 启动10s后, 轮询 2181端口是否启用
print('Polling...')
startKafkaFalg = False

#每次轮询间隔5秒
interval = 5
count = 6

while count > 0:
    tmpFile = os.popen('netstat -na','r')
    breakWhileFlag = False
    for line in tmpFile.readlines():
        if line.startswith('  TCP    0.0.0.0:2181'):
            breakWhileFlag = True
            break
    print("Not yet.")
    if breakWhileFlag:
        print("It's Ok.")
        startKafkaFalg = True
        break
    else:
        count -= 1
        time.sleep(interval)

if startKafkaFalg:
    time.sleep(interval)
    print("Starting the Kafka .")
    subprocess.Popen(startKafkaServerCmd, shell=True)
else:
    print("Something wrong ...")
    input()#raw_input()

8.2 启动三个Kafka

# -*- coding: utf-8 -*
import os, time, subprocess

startZooKeeperServerCmd =
				 'start bin\windows\zookeeper-server-start.bat config\zookeeper.properties'

startKafkaServerCmd = 
						'set JMX_PORT=%d & start bin\windows\kafka-server-start.bat config\%s'

print('Starting ZooKeeper Server...')
subprocess.Popen(startZooKeeperServerCmd, shell=True)
time.sleep(10)

zooKeeperPortNumber = 2181
kafkaPortNumber = 9092
kafkaPortNumber2 = 9093
kafkaPortNumber3 = 9094

kafkaJmxPortNumber = 9997
kafkaJmxPortNumber2 = 9998
kafkaJmxPortNumber3 = 9999

def polling(portNumber, interval = 5, count = 10):
    while count > 0:
        tmpFile = os.popen('netstat -na','r')
        portNumberStr = str(portNumber)
        print("Polling the port: " + portNumberStr)
        for line in tmpFile.readlines():
            if line.startswith('  TCP    0.0.0.0:' +
            	 portNumberStr) or line.startswith('  TCP    127.0.0.1:' + portNumberStr):
                return True
        print("Not yet. " + str(portNumber))
        count -= 1
        time.sleep(interval)
    print("Polling the port: " + portNumberStr + " unsuccessfully.")
    return False

if polling(zooKeeperPortNumber):
    print("Starting the Kafka cluster...")
    subprocess.Popen(startKafkaServerCmd % (kafkaJmxPortNumber, 'server.properties'), shell=True)

    if polling(kafkaPortNumber):
     subprocess.Popen(startKafkaServerCmd % (kafkaJmxPortNumber2, 'server-1.properties'), shell=True)

    if polling(kafkaPortNumber2):
     subprocess.Popen(startKafkaServerCmd % (kafkaJmxPortNumber3, 'server-2.properties'), shell=True)
else:
    print("Something wrong ...")
    input()#raw_input()
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值