卡夫卡快速入门
Kafka作为一个分布式的流平台,这到底意味着什么?
我们认为,一个流处理平台具有三个关键能力:
- 发布和订阅消息(流),在这方面,它类似于一个消息队列或企业消息系统。
- 以容错(故障转移)的方式存储消息(流)。
- 在消息流发生时处理它们。
什么是kafka的优势?它主要应用于2大类应用:
- 构建实时的流数据管道,可靠地获取系统和应用程序之间的数据。
- 构建实时流的应用程序,对数据流进行转换或反应。
要了解kafka是如何做这些事情的,让我们从下到上深入探讨kafka的能力。
首先几个概念:
- kafka作为一个集群运行在一个或多个服务器上。
- kafka集群存储的消息是以topic为类别记录的。
- 每个消息(也叫记录record,我习惯叫消息)是由一个key,一个value和时间戳构成。
kafka有四个核心API:
- 应用程序使用 Producer API 发布消息到1个或多个topic(主题)中。
- 应用程序使用 Consumer API 来订阅一个或多个topic,并处理产生的消息。
- 应用程序使用 Streams API 充当一个流处理器,从1个或多个topic消费输入流,并生产一个输出流到1个或多个输出topic,有效地将输入流转换到输出流。
- Connector API 可构建或运行可重用的生产者或消费者,将topic连接到现有的应用程序或数据系统。例如,连接到关系数据库的连接器可以捕获表的每个变更。
Client和Server之间的通讯,是通过一条简单、高性能并且和开发语言无关的TCP协议。并且该协议保持与老版本的兼容。Kafka提供了Java Client(客户端)。除了Java客户端外,还有非常多的其它编程语言的客户端。
首先来了解一下Kafka所使用的基本术语:
Topic
Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)。
Producer
发布消息的对象称之为主题生产者(Kafka topic producer)
Consumer
订阅消息并处理发布的消息的对象称之为主题消费者(consumers)
Broker
已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
主题和日志 (Topic和Log)
让我们更深入的了解Kafka中的Topic。
Topic是发布的消息的类别名,一个topic可以有零个,一个或多个消费者订阅该主题的消息。
对于每个topic,Kafka集群都会维护一个分区log,就像下图中所示:
每一个分区都是一个顺序的、不可变的消息队列, 并且可以持续的添加。分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的。
Kafka集群保持所有的消息,直到它们过期(无论消息是否被消费)。实际上消费者所持有的仅有的元数据就是这个offset(偏移量),也就是说offset由消费者来控制:正常情况当消费者消费消息的时候,偏移量也线性的的增加。但是实际偏移量由消费者控制,消费者可以将偏移量重置为更早的位置,重新读取消息。可以看到这种设计对消费者来说操作自如,一个消费者的操作不会影响其它消费者对此log的处理。
再说说分区。Kafka中采用分区的设计有几个目的。一是可以处理更多的消息,不受单台服务器的限制。Topic拥有多个分区意味着它可以不受限的处理更多的数据。第二,分区可以作为并行处理的单元,稍后会谈到这一点。
分布式(Distribution)
Log的分区被分布到集群中的多个服务器上。每个服务器处理它分到的分区。 根据配置每个分区还可以复制到其它服务器作为备份容错。 每个分区有一个leader,零或多个follower。Leader处理此分区的所有的读写请求,而follower被动的复制数据。如果leader宕机,其它的一个follower会被推举为新的leader。 一台服务器可能同时是一个分区的leader,另一个分区的follower。 这样可以平衡负载,避免所有的请求都只让一台或者某几台服务器处理。
Geo-Replication(异地数据同步技术)
Kafka MirrorMaker为群集提供geo-replication
支持。借助MirrorMaker
,消息可以跨多个数据中心或云区域进行复制。 您可以在active/passive场景中用于备份和恢复; 或者在active/passive方案中将数据置于更接近用户的位置,或数据本地化。
生产者(Producers)
生产者往某个Topic上发布消息。生产者也负责选择发布到Topic上的哪一个分区。最简单的方式从分区列表中轮流选择。也可以根据某种算法依照权重选择分区。开发者负责如何选择分区的算法。
消费者(Consumers)
通常来讲,消息模型可以分为两种, 队列和发布-订阅式。 队列的处理方式是 一组消费者从服务器读取消息,一条消息只有其中的一个消费者来处理。在发布-订阅模型中,消息被广播给所有的消费者,接收到消息的消费者都可以处理此消息。Kafka为这两种模型提供了单一的消费者抽象模型: 消费者组 (consumer group)。 消费者用一个消费者组名标记自己。 一个发布在Topic上消息被分发给此消费者组中的一个消费者。 假如所有的消费者都在一个组中,那么这就变成了queue模型。 假如所有的消费者都在不同的组中,那么就完全变成了发布-订阅模型。 更通用的, 我们可以创建一些消费者组作为逻辑上的订阅者。每个组包含数目不等的消费者, 一个组内多个消费者可以用来扩展性能和容错。正如下图所示:
2个kafka集群托管4个分区(P0-P3),2个消费者组,消费组A有2个消费者实例,消费组B有4个。
正像传统的消息系统一样,Kafka保证消息的顺序不变。 再详细扯几句。传统的队列模型保持消息,并且保证它们的先后顺序不变。但是, 尽管服务器保证了消息的顺序,消息还是异步的发送给各个消费者,消费者收到消息的先后顺序不能保证了。这也意味着并行消费将不能保证消息的先后顺序。用过传统的消息系统的同学肯定清楚,消息的顺序处理很让人头痛。如果只让一个消费者处理消息,又违背了并行处理的初衷。 在这一点上Kafka做的更好,尽管并没有完全解决上述问题。 Kafka采用了一种分而治之的策略:分区。 因为Topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。 所以,如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。
Kafka的保证(Guarantees)
- 生产者发送到一个特定的Topic的分区上,消息将会按照它们发送的顺序依次加入,也就是说,如果一个消息M1和M2使用相同的producer发送,M1先发送,那么M1将比M2的offset低,并且优先的出现在日志中。
- 消费者收到的消息也是此顺序。
- 如果一个Topic配置了复制因子(replication factor)为N, 那么可以允许N-1服务器宕机而不丢失任何已经提交(committed)的消息。
有关这些保证的更多详细信息,请参见文档的设计部分。
kafka作为一个消息系统
Kafka的流与传统企业消息系统相比的概念如何?
传统的消息有两种模式:队列
和发布订阅
。 在队列模式中,消费者池从服务器读取消息(每个消息只被其中一个读取); 发布订阅模式:消息广播给所有的消费者。这两种模式都有优缺点,队列的优点是允许多个消费者瓜分处理数据,这样可以扩展处理。但是,队列不像多个订阅者,一旦消息者进程读取后故障了,那么消息就丢了。而发布和订阅
允许你广播数据到多个消费者,由于每个订阅者都订阅了消息,所以没办法缩放处理。
kafka中消费者组有两个概念:队列
:消费者组(consumer group)允许同名的消费者组成员瓜分处理。发布订阅
:允许你广播消息给多个消费者组(不同名)。
kafka的每个topic都具有这两种模式。
kafka有比传统的消息系统更强的顺序保证。
传统的消息系统按顺序保存数据,如果多个消费者从队列消费,则服务器按存储的顺序发送消息,但是,尽管服务器按顺序发送,消息异步传递到消费者,因此消息可能乱序到达消费者。这意味着消息存在并行消费的情况,顺序就无法保证。消息系统常常通过仅设1个消费者来解决这个问题,但是这意味着没用到并行处理。
kafka做的更好。通过并行topic的parition —— kafka提供了顺序保证和负载均衡。每个partition仅由同一个消费者组中的一个消费者消费到。并确保消费者是该partition的唯一消费者,并按顺序消费数据。每个topic有多个分区,则需要对多个消费者做负载均衡,但请注意,相同的消费者组中不能有比分区更多的消费者,否则多出的消费者一直处于空等待,不会收到消息
。
kafka作为一个存储系统
所有发布消息到消息队列
和消费分离的系统,实际上都充当了一个存储系统(发布的消息先存储起来)。Kafka比别的系统的优势是它是一个非常高性能的存储系统
。
写入到kafka的数据将写到磁盘并复制到集群中保证容错性。并允许生产者等待消息应答,直到消息完全写入。
kafka的磁盘结构 - 无论你服务器上有50KB或50TB,执行是相同的。
client来控制读取数据的位置。你还可以认为kafka是一种专用于高性能,低延迟,提交日志存储,复制,和传播特殊用途的分布式文件系统
。
kafka的流处理
仅仅读,写和存储是不够的,kafka的目标是实时的流处理。
在kafka中,流处理持续获取输入topic
的数据,进行处理加工,然后写入输出topic
。例如,一个零售APP,接收销售和出货的输入流
,统计数量或调整价格后输出。
可以直接使用producer和consumer API进行简单的处理。对于复杂的转换,Kafka提供了更强大的Streams API。可构建聚合计算
或连接流到一起
的复杂应用程序。
助于解决此类应用面临的硬性问题:处理无序的数据,代码更改的再处理,执行状态计算等。
Sterams API在Kafka中的核心:使用producer和consumer API作为输入,利用Kafka做状态存储,使用相同的组机制在stream处理器实例之间进行容错保障。
拼在一起
消息传递,存储和流处理的组合看似反常,但对于Kafka作为流式处理平台的作用至关重要。
像HDFS这样的分布式文件系统允许存储静态文件来进行批处理。这样系统可以有效地存储和处理来自过去的历史数据。
传统企业的消息系统允许在你订阅之后处理未来的消息:在未来数据到达时处理它。
Kafka结合了这两种能力,这种组合对于kafka作为流处理应用和流数据管道平台是至关重要的。
批处理以及消息驱动应用程序的流处理的概念:通过组合存储和低延迟订阅,流处理应用可以用相同的方式对待过去和未来的数据。它是一个单一的应用程序,它可以处理历史的存储数据,当它处理到最后一个消息时,它进入等待未来的数据到达,而不是结束。
同样,对于流数据管道(pipeline),订阅实时事件的组合使得可以将Kafka用于非常低延迟的管道;但是,可靠地存储数据的能力使得它可以将其用于必须保证传递的关键数据,或与仅定期加载数据或长时间维护的离线系统集成在一起。流处理可以在数据到达时转换它。
Kafka的使用场景
消息
kafka更好的替换传统的消息系统,消息系统被用于各种场景(解耦数据生产者,缓存未处理的消息,等),与大多数消息系统比较,kafka有更好的吞吐量,内置分区,副本和故障转移,这有利于处理大规模的消息。
根据我们的经验,消息往往用于较低的吞吐量,但需要低的端到端
延迟,并需要提供强大的耐用性的保证。
在这一领域的kafka比得上传统的消息系统,如的ActiveMQ
或RabbitMQ
的。
网站活动追踪
kafka原本的使用场景:用户的活动追踪,网站的活动(网页游览,搜索或其他用户的操作信息)发布到不同的话题中心,这些消息可实时处理,实时监测,也可加载到Hadoop或离线处理数据仓库。
每个用户页面视图都会产生非常高的量。
指标
kafka也常常用于监测数据。分布式应用程序生成的统计数据集中聚合。
日志聚合
许多人使用Kafka作为日志聚合解决方案的替代品。日志聚合通常从服务器中收集物理日志文件,并将它们放在中央位置(可能是文件服务器或HDFS)进行处理。Kafka抽象出文件的细节,并将日志或事件数据更清晰地抽象为消息流。这允许更低延迟的处理并更容易支持多个数据源和分布式数据消费。
流处理
kafka中消息处理一般包含多个阶段。其中原始输入数据是从kafka主题消费的,然后汇总,丰富,或者以其他的方式处理转化为新主题,例如,一个推荐新闻文章,文章内容可能从“articles”主题获取;然后进一步处理内容,得到一个处理后的新内容,最后推荐给用户。这种处理是基于单个主题的实时数据流。从0.10.0.0
开始,轻量,但功能强大的流处理,就可以这样进行数据处理了。
除了Kafka Streams,还有Apache Storm和Apache Samza可选择。
事件采集
事件采集是一种应用程序的设计风格,其中状态的变化根据时间的顺序记录下来,kafka支持这种非常大的存储日志数据的场景。
提交日志
kafka可以作为一种分布式的外部日志,可帮助节点之间复制数据,并作为失败的节点来恢复数据重新同步,kafka的日志压缩功能很好的支持这种用法,这种用法类似于Apacha BookKeeper
项目。
kafka安装和启动
kafka的背景知识已经讲了很多了,让我们现在开始实践吧,假设你现在没有Kafka
和ZooKeeper
环境。
Step 1: 下载代码
下载1.1.0
版本并且解压它。
> tar -xzf kafka_2.12-2.3.0.tgz
> cd kafka_2.12-2.3.0
Step 2: 启动服务
运行kafka需要使用Zookeeper,所以你需要先启动Zookeeper,如果你没有Zookeeper,你可以使用kafka自带打包和配置好的Zookeeper。
> bin/zookeeper-server-start.sh config/zookeeper.properties
[2013-04-22 15:01:37,495] INFO Reading configuration from: config/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig)
...
现在启动kafka服务
> bin/kafka-server-start.sh config/server.properties &
[2013-04-22 15:01:47,028] INFO Verifying properties (kafka.utils.VerifiableProperties)
[2013-04-22 15:01:47,051] INFO Property socket.send.buffer.bytes is overridden to 1048576 (kafka.utils.VerifiableProperties)
...
Step 3: 创建一个主题(topic)
创建一个名为“test”的Topic,只有一个分区和一个备份:
> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
创建好之后,可以通过运行以下命令,查看已创建的topic信息:
> bin/kafka-topics.sh --list --zookeeper localhost:2181
test
或者,除了手工创建topic外,你也可以配置你的broker,当发布一个不存在的topic时自动创建topic。
Step 4: 发送消息
Kafka提供了一个命令行的工具,可以从输入文件或者命令行中读取消息并发送给Kafka集群。每一行是一条消息。
运行producer(生产者),然后在控制台输入几条消息到服务器。
> bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
This is a message
This is another message
Step 5: 消费消息
Kafka也提供了一个消费消息的命令行工具,将存储的信息输出出来。
> bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
This is a message
This is another message
如果你有2台不同的终端上运行上述命令,那么当你在运行生产者时,消费者就能消费到生产者发送的消息。
Step 6: 设置多个broker集群
到目前,我们只是单一的运行一个broker,没什么意思。对于Kafka,一个broker仅仅只是一个集群的大小,所有让我们多设几个broker。
首先为每个broker创建一个配置文件:
> cp config/server.properties config/server-1.properties
> cp config/server.properties config/server-2.properties
现在编辑这些新建的文件,设置以下属性:
config/server-1.properties:
broker.id=1
listeners=PLAINTEXT://:9093
log.dir=/tmp/kafka-logs-1
config/server-2.properties:
broker.id=2
listeners=PLAINTEXT://:9094
log.dir=/tmp/kafka-logs-2
broker.id
是集群中每个节点的唯一且永久的名称,我们修改端口和日志目录是因为我们现在在同一台机器上运行,我们要防止broker在同一端口上注册和覆盖对方的数据。
我们已经运行了zookeeper和刚才的一个kafka节点,所有我们只需要在启动2个新的kafka节点。
> bin/kafka-server-start.sh config/server-1.properties &
...
> bin/kafka-server-start.sh config/server-2.properties &
...
现在,我们创建一个新topic,把备份设置为:3
> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic my-replicated-topic
好了,现在我们已经有了一个集群了,我们怎么知道每个集群在做什么呢?运行命令“describe topics”
> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs:
Topic: my-replicated-topic Partition: 0 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
输出解释:第一行是所有分区的摘要,其次,每一行提供一个分区信息,因为我们只有一个分区,所以只有一行。
- "leader":该节点负责该分区的所有的读和写,每个节点的
leader
都是随机选择的。 - "replicas":备份的节点列表,无论该节点是否是leader或者目前是否还活着,只是显示。
- "isr":“同步备份”的节点列表,也就是活着的节点并且正在同步leader。
我们运行这个命令,看看一开始我们创建的那个节点:
> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
Topic:test PartitionCount:1 ReplicationFactor:1 Configs:
Topic: test Partition: 0 Leader: 0 Replicas: 0 Isr: 0
这并不奇怪,刚才创建的主题没有Replicas,并且在服务器“0”上,我们创建它的时候,集群中只有一个服务器,所以是“0”。
让我们来发布一些信息在新的topic上:
> bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-replicated-topic
...
my test message 1
my test message 2
^C
现在,消费这些消息。
> bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic my-replicated-topic
...
my test message 1
my test message 2
^C
我们要测试集群的容错,kill掉leader,Broker1作为当前的leader,也就是kill掉Broker1。
> ps | grep server-1.properties
7564 ttys002 0:15.91 /System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java...
> kill -9 7564
在Windows上使用:
> wmic process where "caption = 'java.exe' and commandline like '%server-1.properties%'" get processid
ProcessId
6016
> taskkill /pid 6016 /f
备份节点之一成为新的leader,而broker1已经不在同步备份集合里了。
> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs:
Topic: my-replicated-topic Partition: 0 Leader: 2 Replicas: 1,2,0 Isr: 2,0
但是,消息仍然没丢:
> bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic my-replicated-topic
...
my test message 1
my test message 2
^C
Step 7: 使用 Kafka Connect 来 导入/导出 数据
从控制台写入和写回数据是一个方便的开始,但你可能想要从其他来源导入或导出数据到其他系统。对于大多数系统,可以使用kafka Connect,而不需要编写自定义集成代码。
Kafka Connect
是导入和导出数据的一个工具。它是一个可扩展的工具,运行连接器,实现与自定义的逻辑的外部系统交互。在这个快速入门里,我们将看到如何运行Kafka Connect用简单的连接器从文件导入数据到Kafka主题,再从Kafka主题导出数据到文件。
首先,我们首先创建一些“种子”数据用来测试,(ps:种子的意思就是造一些消息,片友秒懂?):
echo -e "foo\nbar" > test.txt
windowns上:
> echo foo> test.txt
> echo bar>> test.txt
接下来,我们开始2个连接器运行在独立的模式,这意味着它们运行在一个单一的,本地的,专用的进程。我们提供3个配置文件作为参数。首先是Kafka Connect处理的配置,包含常见的配置,例如要连接的Kafka broker和数据的序列化格式。其余的配置文件都指定了要创建的连接器。包括连接器唯一名称,和要实例化的连接器类。以及连接器所需的任何其他配置。
> bin/connect-standalone.sh config/connect-standalone.properties config/connect-file-source.properties config/connect-file-sink.properties
kafka附带了这些示例的配置文件,并且使用了刚才我们搭建的本地集群配置并创建了2个连接器:第一个是源连接器,从输入文件中读取并发布到Kafka主题中,第二个是接收连接器,从kafka主题读取消息输出到外部文件。
在启动过程中,你会看到一些日志消息,包括一些连接器实例化的说明。一旦kafka Connect进程已经开始,导入连接器应该读取从
test.txt
和写入到topic
connect-test
,导出连接器从主题
connect-test
读取消息写入到文件
test.sink.txt
. 我们可以通过验证输出文件的内容来验证数据数据已经全部导出:
more test.sink.txt
foo
bar
注意,导入的数据也已经在Kafka主题
connect-test
里,所以我们可以使用该命令查看这个主题:
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic connect-test --from-beginning
{"schema":{"type":"string","optional":false},"payload":"foo"}
{"schema":{"type":"string","optional":false},"payload":"bar"}
连接器继续处理数据,因此我们可以添加数据到文件并通过管道移动:
echo "Another line" >> test.txt
你应该会看到出现在消费者控台输出一行信息并导出到文件。
Step 8: 使用Kafka Stream来处理数据
Kafka Stream是kafka的客户端库,用于实时流处理和分析存储在kafka broker的数据,这个快速入门示例将演示如何运行一个流应用程序。一个WordCountDemo的例子(为了方便阅读,使用的是java8 lambda表达式)
KTable wordCounts = textLines // Split each text line, by whitespace, into words. .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("W+"))) // Ensure the words are available as record keys for the next aggregate operation. .map((key, value) -> new KeyValue<>(value, value)) // Count the occurrences of each word (record key) and store the results into a table named "Counts". .countByKey("Counts")
它实现了wordcount算法,从输入的文本计算出一个词出现的次数。然而,不像其他的WordCount的例子,你可能会看到,在有限的数据之前,执行的演示应用程序的行为略有不同,因为它的目的是在一个无限的操作,数据流。类似的有界变量,它是一种动态算法,跟踪和更新的单词计数。然而,由于它必须假设潜在的无界输入数据,它会定期输出其当前状态和结果,同时继续处理更多的数据,因为它不知道什么时候它处理过的“所有”的输入数据。
现在准备输入数据到kafka的topic中,随后kafka Stream应用处理这个topic的数据。
> echo -e "all streams lead to kafka\nhello kafka streams\njoin kafka summit" > file-input.txt
接下来,使用控制台的producer 将输入的数据发送到指定的topic(streams-file-input)中,(在实践中,stream数据可能会持续流入,其中kafka的应用将启动并运行)
> bin/kafka-topics.sh --create \ --zookeeper localhost:2181 \ --replication-factor 1 \ --partitions 1 \ --topic streams-file-input
> cat /tmp/file-input.txt | ./bin/kafka-console-producer --broker-list localhost:9092 --topic streams-file-input
现在,我们运行 WordCount 处理输入的数据:
./bin/kafka-run-class org.apache.kafka.streams.examples.wordcount.WordCountDemo
不会有任何的STDOUT输出,除了日志,结果不断地写回另一个topic(streams-wordcount-output),demo运行几秒,然后,不像典型的流处理应用程序,自动终止。
现在我们检查WordCountDemo应用,从输出的topic读取。
./bin/kafka-console-consumer --zookeeper localhost:2181 --topic streams-wordcount-output --from-beginning --formatter kafka.tools.DefaultMessageFormatter --property print.key=true --property print.key=true --property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer --property value.deserializer=org.apache.kafka.common.serialization.LongDeserializer
输出数据打印到控台(你可以使用Ctrl-C停止):
all 1
streams 1
lead 1
to 1
kafka 1
hello 1
kafka 2
streams 2
join 1
kafka 3
summit 1
^C
第一列是message的key,第二列是message的value,要注意,输出的实际是一个连续的更新流,其中每条数据(即:原始输出的每行)是一个单词的最新的count,又叫记录键“kafka”。对于同一个key有多个记录,每个记录之后是前一个的更新。
Apache Kafka引入一个新的java客户端(在org.apache.kafka.clients 包中),替代老的Scala客户端,但是为了兼容,将会共存一段时间。为了减少依赖,这些客户端都有一个独立的jar,而旧的Scala客户端继续与服务端保留在同个包下。
Kafka有4个核心API:
- Producer API 允许应用程序发送数据流到kafka集群中的topic。
- Consumer API 允许应用程序从kafka集群的topic中读取数据流。
- Streams API 允许从输入topic转换数据流到输出topic。
- Connect API 通过实现连接器(connector),不断地从一些源系统或应用程序中拉取数据到kafka,或从kafka提交数据到宿系统(sink system)或应用程序。
kafka公开了其所有的功能协议,与语言无关。只有java客户端作为kafka项目的一部分进行维护,其他的作为开源的项目提供,这里提供了非java客户端的列表。
https://cwiki.apache.org/confluence/display/KAFKA/Clients
kafka客户端发布record(消息)
到kafka集群。
新的生产者是线程安全的,在线程之间共享单个生产者实例,通常单例比多个实例要快。
一个简单的例子,使用producer发送一个有序的key/value(键值对),放到java的main
方法里就能直接运行,
Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("acks", "all"); props.put("retries", 0); props.put("batch.size", 16384); props.put("linger.ms", 1); props.put("buffer.memory", 33554432); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); for(int i = 0; i < 100; i++) producer.send(new ProducerRecord<String, String>("my-topic", Integer.toString(i), Integer.toString(i))); producer.close();
生产者的缓冲空间池保留尚未发送到服务器的消息,后台I/O线程负责将这些消息转换成请求发送到集群。如果使用后不关闭生产者,则会泄露这些资源。
send()
方法是异步的,添加消息到缓冲区等待发送,并立即返回。生产者将单个的消息批量在一起发送来提高效率。
ack
是判别请求是否为完整的条件(就是是判断是不是成功发送了)。我们指定了“all”将会阻塞消息,这种设置性能最低,但是是最可靠的。
retries
,如果请求失败,生产者会自动重试,我们指定是0次,如果启用重试,则会有重复消息的可能性。
producer
(生产者)缓存每个分区未发送的消息。缓存的大小是通过 batch.size
配置指定的。值较大的话将会产生更大的批。并需要更多的内存(因为每个“活跃”的分区都有1个缓冲区)。
默认缓冲可立即发送,即便缓冲空间还没有满,但是,如果你想减少请求的数量,可以设置linger.ms
大于0。这将指示生产者发送请求之前等待一段时间,希望更多的消息填补到未满的批中。这类似于TCP的算法,例如上面的代码段,可能100条消息在一个请求发送,因为我们设置了linger(逗留)时间为1毫秒,然后,如果我们没有填满缓冲区,这个设置将增加1毫秒的延迟请求以等待更多的消息。需要注意的是,在高负载下,相近的时间一般也会组成批,即使是 linger.ms=0
。在不处于高负载的情况下,如果设置比0大,以少量的延迟代价换取更少的,更有效的请求。
buffer.memory
控制生产者可用的缓存总量,如果消息发送速度比其传输到服务器的快,将会耗尽这个缓存空间。当缓存空间耗尽,其他发送调用将被阻塞,阻塞时间的阈值通过max.block.ms
设定,之后它将抛出一个TimeoutException。
key.serializer
和value.serializer
示例,将用户提供的key和value对象ProducerRecord转换成字节,你可以使用附带的ByteArraySerializaer或StringSerializer处理简单的string或byte类型。
send()
public Future<RecordMetadata> send(ProducerRecord<K,V> record,Callback callback)
异步发送一条消息到topic,并调用callback
(当发送已确认)。
send是异步的,并且一旦消息被保存在等待发送的消息缓存
中,此方法就立即返回。这样并行发送多条消息而不阻塞去等待每一条消息的响应。
发送的结果是一个RecordMetadata,它指定了消息发送的分区,分配的offset和消息的时间戳。如果topic使用的是CreateTime,则使用用户提供的时间戳或发送的时间(如果用户没有指定指定消息的时间戳)如果topic使用的是LogAppendTime,则追加消息时,时间戳是broker的本地时间。
由于send调用是异步的,它将为分配消息的此消息的RecordMetadata
返回一个Future。如果future调用get(),则将阻塞,直到相关请求完成并返回该消息的metadata,或抛出发送异常。
如果要模拟一个简单的阻塞调用,你可以调用get()
方法。
byte[] key = "key".getBytes(); byte[] value = "value".getBytes(); ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("my-topic", key, value) producer.send(record).get();
完全无阻塞的话,可以利用回调参数提供的请求完成时将调用的回调通知。
ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("the-topic", key, value); producer.send(myRecord, new Callback() { public void onCompletion(RecordMetadata metadata, Exception e) { if(e != null) e.printStackTrace(); System.out.println("The offset of the record we just sent is: " + metadata.offset()); } });
发送到同一个分区的消息回调保证按一定的顺序执行,也就是说,在下面的例子中 callback1
保证执行 callback2
之前:
producer.send(new ProducerRecord<byte[],byte[]>(topic, partition, key1, value1), callback1); producer.send(new ProducerRecord<byte[],byte[]>(topic, partition, key2, value2), callback2);
注意:callback一般在生产者的I/O线程中执行,所以是相当的快的,否则将延迟其他的线程的消息发送。如果你需要执行阻塞或计算昂贵(消耗)的回调,建议在callback主体中使用自己的Executor来并行处理。
pecified by:
send in interface Producer<K,V>
Parameters:
record - 发送的记录(消息)
callback - 用户提供的callback,服务器来调用这个callback来应答结果(null表示没有callback)。
Throws:
InterruptException - 如果线程在阻塞中断。
SerializationException - 如果key或value不是给定有效配置的serializers。
TimeoutException - 如果获取元数据或消息分配内存话费的时间超过max.block.ms。
KafkaException - Kafka有关的错误(不属于公共API的异常)。
kafka消费者API
随着0.9.0版本,我们已经增加了一个新的Java消费者替换我们现有的基于zookeeper的高级和低级消费者。这个客户端还是测试版的质量。为了确保用户平滑升级,我们仍然维护旧的0.8版本的消费者客户端继续在0.9集群上工作,两个老的0.8 API的消费者( 高级消费者 和 低级消费者)。
这个新的消费API,清除了0.8版本的高版本和低版本消费者之间的区别,你可以通过下面的maven,引入依赖到你的客户端。
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.10.1.0</version> </dependency>
如何使用新的消费者,请点击这里。
kafka消费者客户端
Kafka客户端从集群中消费消息,并透明地处理kafka集群中出现故障服务器,透明地调节适应集群中变化的数据分区。也和服务器交互,平衡均衡消费者。
public class KafkaConsumer<K,V> extends Object implements Consumer<K,V>
消费者TCP长连接到broker来拉取消息。故障导致的消费者关闭失败,将会泄露这些连接,消费者不是线程安全的,可以查看更多关于Multi-threaded(多线程)处理的细节。
跨版本兼容性
该客户端可以与0.10.0或更新版本的broker集群进行通信。较早的版本可能不支持某些功能。例如,0.10.0
broker不支持offsetsForTimes
,因为此功能是在版本0.10.1
中添加的。 如果你调用broker版本不可用的API时,将报 UnsupportedVersionException 异常。
偏移量和消费者的位置
kafka为分区中的每条消息保存一个偏移量(offset)
,这个偏移量
是该分区中一条消息的唯一标示符。也表示消费者在分区的位置。例如,一个位置是5的消费者(说明已经消费了0到4的消息),下一个接收消息的偏移量为5的消息。实际上有两个与消费者相关的“位置”概念:
消费者的位置给出了下一条记录的偏移量。它比消费者在该分区中看到的最大偏移量要大一个。 它在每次消费者在调用poll(long)中接收消息时自动增长。
“已提交”的位置是已安全保存的最后偏移量,如果进程失败或重新启动时,消费者将恢复到这个偏移量。消费者可以选择定期自动提交偏移量,也可以选择通过调用commit API来手动的控制(如:commitSync 和 commitAsync)。
这个区别是消费者来控制一条消息什么时候才被认为是已被消费的,控制权在消费者,下面我们进一步更详细地讨论。
消费者组和主题订阅
Kafka的消费者组
概念,通过进程池瓜分消息并处理消息。这些进程可以在同一台机器运行,也可分布到多台机器上,以增加可扩展性和容错性,相同group.id的消费者将视为同一个消费者组
。
分组中的每个消费者都通过subscribe API
动态的订阅一个topic列表。kafka将已订阅topic的消息发送到每个消费者组中。并通过平衡分区在消费者分组中所有成员之间来达到平均。因此每个分区恰好地分配1个消费者(一个消费者组中)。所有如果一个topic有4个分区,并且一个消费者分组有只有2个消费者。那么每个消费者将消费2个分区。
消费者组的成员是动态维护的:如果一个消费者故障。分配给它的分区将重新分配给同一个分组中其他的消费者。同样的,如果一个新的消费者加入到分组,将从现有消费者中移一个给它。这被称为重新平衡分组
,并在下面更详细地讨论。当新分区添加到订阅的topic时,或者当创建与订阅的正则表达式匹配的新topic时,也将重新平衡。将通过定时刷新自动发现新的分区,并将其分配给分组的成员。
从概念上讲,你可以将消费者分组看作是由多个进程组成的单一逻辑订阅者。作为一个多订阅系统,Kafka支持对于给定topic任何数量的消费者组,而不重复。
这是在消息系统中常见的功能的略微概括。所有进程都将是单个消费者分组的一部分(类似传统消息传递系统中的队列的语义),因此消息传递就像队列一样,在组中平衡。与传统的消息系统不同的是,虽然,你可以有多个这样的组。但每个进程都有自己的消费者组(类似于传统消息系统中pub-sub的语义),因此每个进程都会订阅到该主题的所有消息。
此外,当分组重新分配自动发生时,可以通过ConsumerRebalanceListener通知消费者,这允许他们完成必要的应用程序级逻辑,例如状态清除,手动偏移提交等。有关更多详细信息,请参阅Kafka存储的偏移。
它也允许消费者通过使用assign(Collection)
手动分配指定分区,如果使用手动指定分配分区,那么动态分区分配和协调消费者组将失效。
发现消费者故障
订阅一组topic后,当调用poll(long)时,消费者将自动加入到组中。只要持续的调用poll,消费者将一直保持可用,并继续从分配的分区中接收消息。此外,消费者向服务器定时发送心跳。 如果消费者崩溃或无法在session.timeout.ms配置的时间内发送心跳,则消费者将被视为死亡,并且其分区将被重新分配。
还有一种可能,消费可能遇到“活锁”的情况,它持续的发送心跳,但是没有处理。为了预防消费者在这种情况下一直持有分区,我们使用max.poll.interval.ms活跃检测机制。 在此基础上,如果你调用的poll的频率大于最大间隔,则客户端将主动地离开组,以便其他消费者接管该分区。 发生这种情况时,你会看到offset提交失败(调用commitSync()引发的CommitFailedException)。这是一种安全机制,保障只有活动成员能够提交offset。所以要留在组中,你必须持续调用poll。
消费者提供两个配置设置来控制poll循环:
-
max.poll.interval.ms
:增大poll的间隔,可以为消费者提供更多的时间去处理返回的消息(调用poll(long)返回的消息,通常返回的消息都是一批)。缺点是此值越大将会延迟组重新平衡。 -
max.poll.records
:此设置限制每次调用poll返回的消息数,这样可以更容易的预测每次poll间隔要处理的最大值。通过调整此值,可以减少poll间隔,减少重新平衡分组的
对于消息处理时间不可预测地的情况,这些选项是不够的。 处理这种情况的推荐方法是将消息处理移到另一个线程中,让消费者继续调用poll。 但是必须注意确保已提交的offset不超过实际位置。另外,你必须禁用自动提交,并只有在线程完成处理后才为记录手动提交偏移量(取决于你)。 还要注意,你需要pause暂停
分区,不会从poll接收到新消息,让线程处理完之前返回的消息(如果你的处理能力比拉取消息的慢,那创建新线程将导致你机器内存溢出)。
示例
这个消费者API提供了灵活性,以涵盖各种消费场景,下面是一些例子来演示如何使用它们。
自动提交偏移量
这是个【自动提交偏移量】的简单的kafka消费者API。
Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("group.id", "test"); props.put("enable.auto.commit", "true"); props.put("auto.commit.interval.ms", "1000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("foo", "bar")); while (true) { 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()); }
设置enable.auto.commit
,偏移量由auto.commit.interval.ms
控制自动提交的频率。
集群是通过配置bootstrap.servers指定一个或多个broker。不用指定全部的broker,它将自动发现集群中的其余的borker(最好指定多个,万一有服务器故障)。
在这个例子中,客户端订阅了主题foo
和bar
。消费者组叫test
。
broker通过心跳机器自动检测test组中失败的进程,消费者会自动ping
集群,告诉进群它还活着。只要消费者能够做到这一点,它就被认为是活着的,并保留分配给它分区的权利,如果它停止心跳的时间超过session.timeout.ms
,那么就会认为是故障的,它的分区将被分配到别的进程。
这个deserializer
设置如何把byte转成object类型,例子中,通过指定string解析器,我们告诉获取到的消息的key和value只是简单个string类型。
手动控制偏移量
不需要定时的提交offset,可以自己控制offset,当消息认为已消费过了,这个时候再去提交它们的偏移量。这个很有用的,当消费的消息结合了一些处理逻辑,这个消息就不应该认为是已经消费的,直到它完成了整个处理。
Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("group.id", "test"); props.put("enable.auto.commit", "false"); props.put("auto.commit.interval.ms", "1000"); props.put("session.timeout.ms", "30000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("foo", "bar")); final int minBatchSize = 200; List<ConsumerRecord<String, String>> buffer = new ArrayList<>(); while (true) { ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { buffer.add(record); } if (buffer.size() >= minBatchSize) { insertIntoDb(buffer); consumer.commitSync(); buffer.clear(); } }
在这个例子中,我们将消费一批消息并将它们存储在内存中。当我们积累足够多的消息后,我们再将它们批量插入到数据库中。如果我们设置offset自动提交(之前说的例子),消费将被认为是已消费的。这样会出现问题,我们的进程可能在批处理记录之后,但在它们被插入到数据库之前失败了。
为了避免这种情况,我们将在相应的记录插入数据库之后再手动提交偏移量。这样我们可以准确控制消息是成功消费的。提出一个相反的可能性:在插入数据库之后,但是在提交之前,这个过程可能会失败(即使这可能只是几毫秒,这是一种可能性)。在这种情况下,进程将获取到已提交的偏移量,并会重复插入的最后一批数据。这种方式就是所谓的“至少一次”保证,在故障情况下,可以重复。
如果您无法执行这些操作,可能会使已提交的偏移超过消耗的位置,从而导致缺少记录。 使用手动偏移控制的优点是,您可以直接控制记录何时被视为“已消耗”。
注意:使用自动提交也可以“至少一次”。但是要求你必须下次调用poll(long)之前或关闭消费者之前,处理完所有返回的数据。如果操作失败,这将会导致已提交的offset超过消费的位置,从而导致丢失消息。使用手动控制offset的有点是,你可以直接控制消息何时提交。、
上面的例子使用commitSync表示所有收到的消息为”已提交",在某些情况下,你可以希望更精细的控制,通过指定一个明确消息的偏移量为“已提交”。在下面,我们的例子中,我们处理完每个分区中的消息后,提交偏移量。
try { while(running) { ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE); for (TopicPartition partition : records.partitions()) { List<ConsumerRecord<String, String>> partitionRecords = records.records(partition); for (ConsumerRecord<String, String> record : partitionRecords) { System.out.println(record.offset() + ": " + record.value()); } long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset(); consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1))); } } } finally { consumer.close(); }
注意:已提交的offset应始终是你的程序将读取的下一条消息的offset。因此,调用commitSync(offsets)时,你应该加1个到最后处理的消息的offset。
订阅指定的分区
在前面的例子中,我们订阅我们感兴趣的topic,让kafka提供给我们平分后的topic分区。但是,在有些情况下,你可能需要自己来控制分配指定分区,例如:
-
如果这个消费者进程与该分区保存了某种本地状态(如本地磁盘的键值存储),则它应该只能获取这个分区的消息。
-
如果消费者进程本身具有高可用性,并且如果它失败,会自动重新启动(可能使用集群管理框架如YARN,Mesos,或者AWS设施,或作为一个流处理框架的一部分)。 在这种情况下,不需要Kafka检测故障,重新分配分区,因为消费者进程将在另一台机器上重新启动。
要使用此模式,,你只需调用assign(Collection)
消费指定的分区即可:
String topic = "foo";
TopicPartition partition0 = new TopicPartition(topic, 0);
TopicPartition partition1 = new TopicPartition(topic, 1);
consumer.assign(Arrays.asList(partition0, partition1));
一旦手动分配分区,你可以在循环中调用poll(跟前面的例子一样)。消费者分组仍需要提交offset,只是现在分区的设置只能通过调用assign
修改,因为手动分配不会进行分组协调,因此消费者故障不会引发分区重新平衡。每一个消费者是独立工作的(即使和其他的消费者共享GroupId)。为了避免offset提交冲突,通常你需要确认每一个consumer实例的gorupId都是唯一的。
注意,手动分配分区(即,assgin)和动态分区分配的订阅topic模式(即,subcribe)不能混合使用。
offset存储在其他地方
消费者可以不使用kafka内置的offset仓库。可以选择自己来存储offset。要注意的是,将消费的offset和结果存储在同一个的系统中,用原子的方式存储结果和offset,但这不能保证原子,要想消费是完全原子的,并提供的“正好一次”的消费保证比kafka默认的“至少一次”的语义要更高。你需要使用kafka的offset提交功能。
这有结合的例子。
-
如果消费的结果存储在
关系数据库
中,存储在数据库的offset,让提交结果和offset在单个事务中。这样,事物成功,则offset存储和更新。如果offset没有存储,那么偏移量也不会被更新。 -
如果offset和消费结果存储在本地仓库。例如,可以通过订阅一个指定的分区并将offset和索引数据一起存储来构建一个搜索索引。如果这是以原子的方式做的,常见的可能是,即使崩溃引起未同步的数据丢失。索引程序从它确保没有更新丢失的地方恢复,而仅仅丢失最近更新的消息。
每个消息都有自己的offset,所以要管理自己的偏移,你只需要做到以下几点:
-
配置 enable.auto.commit=false
-
使用提供的 ConsumerRecord 来保存你的位置。
-
在重启时用 seek(TopicPartition, long) 恢复消费者的位置。
当分区分配也是手动完成的(像上文搜索索引的情况),这种类型的使用是最简单的。 如果分区分配是自动完成的,需要特别小心处理分区分配变更的情况。可以通过调用subscribe(Collection,ConsumerRebalanceListener)
和subscribe(Pattern,ConsumerRebalanceListener)
中提供的ConsumerRebalanceListener
实例来完成的。例如,当分区向消费者获取时,消费者将通过实现ConsumerRebalanceListener.onPartitionsRevoked(Collection)
来给这些分区提交它们offset。当分区分配给消费者时,消费者通过ConsumerRebalanceListener.onPartitionsAssigned(Collection)
为新的分区正确地将消费者初始化到该位置。
ConsumerRebalanceListener
的另一个常见用法是清除应用已移动到其他位置的分区的缓存。
控制消费的位置
大多数情况下,消费者只是简单的从头到尾的消费消息,周期性的提交位置(自动或手动)。kafka也支持消费者去手动的控制消费的位置,可以消费之前的消息也可以跳过最近的消息。
有几种情况,手动控制消费者的位置可能是有用的。
一种场景是对于时间敏感的消费者处理程序,对足够落后的消费者,直接跳过,从最近的消费开始消费。
另一个使用场景是本地状态存储系统(上一节说的)。在这样的系统中,消费者将要在启动时初始化它的位置(无论本地存储是否包含)。同样,如果本地状态已被破坏(假设因为磁盘丢失),则可以通过重新消费所有数据并重新创建状态(假设kafka保留了足够的历史)在新的机器上重新创建。
kafka使用seek(TopicPartition, long)
指定新的消费位置。用于查找服务器保留的最早和最新的offset的特殊的方法也可用(seekToBeginning(Collection) 和 seekToEnd(Collection))。
消费者流量控制
如果消费者分配了多个分区,并同时消费所有的分区,这些分区具有相同的优先级。在一些情况下,消费者需要首先消费一些指定的分区,当指定的分区有少量或者已经没有可消费的数据时,则开始消费其他分区。
例如流处理,当处理器从2个topic获取消息并把这两个topic的消息合并,当其中一个topic长时间落后另一个,则暂停消费,以便落后的赶上来。
kafka支持动态控制消费流量,分别在future的poll(long)中使用pause(Collection)
和 resume(Collection)
来暂停消费指定分配的分区,重新开始消费指定暂停的分区。
多线程处理
Kafka消费者不是线程安全的。所有网络I/O
都发生在进行调用应用程序的线程中。用户的责任是确保多线程访问正确同步的。非同步访问将导致ConcurrentModificationException。
此规则唯一的例外是wakeup()
,它可以安全地从外部线程来中断活动操作。在这种情况下,将从操作的线程阻塞并抛出一个WakeupException。这可用于从其他线程来关闭消费者。 以下代码段显示了典型模式:
public class KafkaConsumerRunner implements Runnable { private final AtomicBoolean closed = new AtomicBoolean(false); private final KafkaConsumer consumer; public void run() { try { consumer.subscribe(Arrays.asList("topic")); while (!closed.get()) { ConsumerRecords records = consumer.poll(10000); // Handle new records } } catch (WakeupException e) { // Ignore exception if closing if (!closed.get()) throw e; } finally { consumer.close(); } } // Shutdown hook which can be called from a separate thread public void shutdown() { closed.set(true); consumer.wakeup(); } }
在单独的线程中,可以通过设置关闭标志和唤醒消费者来关闭消费者。
closed.set(true); consumer.wakeup();
我们没有多线程模型的例子。但留下几个操作可用来实现多线程处理消息。
-
每个线程一个消费者
每个线程自己的消费者实例。这里是这种方法的优点和缺点:
- PRO: 这是最容易实现的
- PRO: 因为它不需要在线程之间协调,所以通常它是最快的。
- PRO: 它按顺序处理每个分区(每个线程只处理它接受的消息)。
- CON: 更多的消费者意味着更多的TCP连接到集群(每个线程一个)。一般kafka处理连接非常的快,所以这是一个小成本。
- CON: 更多的消费者意味着更多的请求被发送到服务器,但稍微较少的数据批次可能导致I/O吞吐量的一些下降。
- CON: 所有进程中的线程总数受到分区总数的限制。
-
解耦消费和处理
另一个替代方式是一个或多个消费者线程,它来消费所有数据,其消费所有数据并将ConsumerRecords实例切换到由实际处理记录处理的处理器线程池来消费的阻塞队列。这个选项同样有利弊:
- PRO: 可扩展消费者和处理进程的数量。这样单个消费者的数据可分给多个处理器线程来执行,避免对分区的任何限制。
- CON: 跨多个处理器的顺序保证需要特别注意,因为线程是独立的执行,后来的消息可能比遭到的消息先处理,这仅仅是因为线程执行的运气。如果对排序没有问题,这就不是个问题。
- CON: 手动提交变得更困难,因为它需要协调所有的线程以确保处理对该分区的处理完成。
这种方法有多种玩法,例如,每个处理线程可以有自己的队列,消费者线程可以使用TopicPartition
hash到这些队列中,以确保按顺序消费,并且提交也将简化。
Kafka Streams API
2.3 Streams API
在0.10.0增加了一个新的客户端库,Kafka Stream,Kafka Stream具有Alpha的优点,你可以使用maven引入到你的项目:
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-streams</artifactId> <version>0.10.0.0</version> </dependency>
如何使用,请点击这里。(注意,@InterfaceStability.Unstable注解的类,是公共API,在未来可能改变,不保证向后兼容)
KafkaStreams客户端(0.10.1.1 API)
Kafka Streams从一个或多个输入topic进行连续的计算并输出到0或多个外部topic中。
可以通过TopologyBuilder类定义一个计算逻辑处理器
DAG拓扑。或者也可以通过提供的高级别KStream DSL来定义转换的KStreamBuilder。(PS:计算逻辑其实就是自己的代码逻辑)
KafkaStreams类管理Kafka Streams实例的生命周期。一个stream实例可以在配置文件中为处理器
指定一个或多个Thread。
KafkaStreams实例可以作为单个streams处理客户端(也可能是分布式的),与其他的相同应用ID的实例进行协调(无论是否在同一个进程中,在同一台机器的其他进程中,或远程机器上)。这些实例将根据输入topic分区的基础上来划分工作,以便所有的分区都被消费掉。如果实例添加或失败,所有实例将重新平衡它们之间的分区分配,以保证负载平衡。
在内部,KafkaStreams实例包含一个正常的KafkaProducer
和KafkaConsumer
实例,用于读取和写入,
一个简单的例子:
Map<String, Object> props = new HashMap<>(); props.put(StreamsConfig.APPLICATION_ID_CONFIG, "my-stream-processing-application"); props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(StreamsConfig.KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); props.put(StreamsConfig.VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); StreamsConfig config = new StreamsConfig(props); KStreamBuilder builder = new KStreamBuilder(); builder.stream("my-input-topic").mapValues(value -> value.length().toString()).to("my-output-topic"); KafkaStreams streams = new KafkaStreams(builder, config); streams.start();
Kafka Connect API
springbootd和kafka集成
1. 前言
对于使用Apache Kafka的Spring项目,我们在Spring核心提供了Kafka消息的集成。提供了公共的接入“模板”,作为消息发送的高级抽象层,还为消息的POJO提供支持。
3. 介绍
本示例提供一个快速的入门例子,直接运行即可。
3.1. 快速游览(Quick Tour for the Impatient)
这是Spring Kafka的五分钟速览。
先决条件:您的Apache Kafka已经安装并且运行了。然后,您必须有spring-kafka JAR及其所有依赖项。 最简单的方法是在构建工具中声明一个依赖项。以下示例显示了如何使用Maven
进行操作:
<dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.4.1.RELEASE</version> </dependency>
Gradle的引入:
compile 'org.springframework.kafka:spring-kafka:2.4.1.RELEASE'
使用Spring Boot时,如果忽略该版本,则Spring Boot将自动引入与您的Boot版本兼容的正确版本:
<dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency>
Gradle的方式:
compile 'org.springframework.kafka:spring-kafka'
3.1.1. 兼容性
适用于以下的版本:
- Apache Kafka Clients 2.2.0
- Spring Framework 5.2.x
- 最小的 Java 版本: 8
3.1.2. 一个非常非常快速的例子
如下例所示,您可以使用普通Java发送和接收消息:
package com.example.kafka; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.IntegerDeserializer; import org.apache.kafka.common.serialization.IntegerSerializer; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; import org.springframework.kafka.listener.ContainerProperties; import org.springframework.kafka.listener.KafkaMessageListenerContainer; import org.springframework.kafka.listener.MessageListener; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest class KafkaTests01 { private Logger logger = LoggerFactory.getLogger(getClass()); private String group = "group01"; private String topic1 = "topic1"; @Test public void testAutoCommit() throws Exception { logger.info("Start auto"); // 启动消费者 ContainerProperties containerProps = new ContainerProperties("topic1", "topic2"); final CountDownLatch latch = new CountDownLatch(4); containerProps.setMessageListener(new MessageListener<Integer, String>() { @Override public void onMessage(ConsumerRecord<Integer, String> message) { logger.info("received: " + message); latch.countDown(); } }); KafkaMessageListenerContainer<Integer, String> container = createContainer(containerProps); container.setBeanName("testAuto"); container.start(); // 启动消费者 Thread.sleep(1000); // wait a bit for the container to start // 启动生产者 KafkaTemplate<Integer, String> template = createTemplate(); template.setDefaultTopic(topic1); template.sendDefault(0, "foo"); template.sendDefault(2, "bar"); template.sendDefault(0, "baz"); template.sendDefault(2, "qux"); template.flush(); assertTrue(latch.await(60, TimeUnit.SECONDS)); container.stop(); // 关闭消费者 logger.info("Stop auto"); } private KafkaMessageListenerContainer<Integer, String> createContainer(ContainerProperties containerProps) { Map<String, Object> props = consumerProps(); DefaultKafkaConsumerFactory<Integer, String> cf = new DefaultKafkaConsumerFactory<Integer, String>(props); KafkaMessageListenerContainer<Integer, String> container = new KafkaMessageListenerContainer<>(cf, containerProps); return container; } private KafkaTemplate<Integer, String> createTemplate() { Map<String, Object> senderProps = senderProps(); ProducerFactory<Integer, String> pf = new DefaultKafkaProducerFactory<Integer, String>(senderProps); KafkaTemplate<Integer, String> template = new KafkaTemplate<>(pf); return template; } private Map<String, Object> consumerProps() { Map<String, Object> props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, group); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true); props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100"); props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); return props; } private Map<String, Object> senderProps() { Map<String, Object> props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ProducerConfig.RETRIES_CONFIG, 0); props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); props.put(ProducerConfig.LINGER_MS_CONFIG, 1); props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); return props; } }
3.1.3. 使用Java配置
你也可以使用Java的Spring配置来完成与上一个示例中相同的效果。 以下示例显示了如何执行此操作:
@Autowired private Listener listener; @Autowired private KafkaTemplate<Integer, String> template; @Test public void testSimple() throws Exception { template.send("annotated1", 0, "foo"); template.flush(); assertTrue(this.listener.latch1.await(10, TimeUnit.SECONDS)); } @Configuration @EnableKafka public class Config { @Bean ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); return factory; } @Bean public ConsumerFactory<Integer, String> consumerFactory() { return new DefaultKafkaConsumerFactory<>(consumerConfigs()); } @Bean public Map<String, Object> consumerConfigs() { Map<String, Object> props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString()); ... return props; } @Bean public Listener listener() { return new Listener(); } @Bean public ProducerFactory<Integer, String> producerFactory() { return new DefaultKafkaProducerFactory<>(producerConfigs()); } @Bean public Map<String, Object> producerConfigs() { Map<String, Object> props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString()); ... return props; } @Bean public KafkaTemplate<Integer, String> kafkaTemplate() { return new KafkaTemplate<Integer, String>(producerFactory()); } } public class Listener { private final CountDownLatch latch1 = new CountDownLatch(1); @KafkaListener(id = "foo", topics = "annotated1") public void listen1(String foo) { this.latch1.countDown(); } }
3.1.4. Spring Boot更简单的方式
Spring Boot可以更加简单。 下面的Spring Boot应用示例将三个消息发送到一个主题,然后接收它们,然后停止:
package com.example.kafka.demo03; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SpringBootApplication public class Application implements CommandLineRunner { public static Logger logger = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { SpringApplication.run(Application.class, args).close(); } @Autowired private KafkaTemplate<String, String> template; private final CountDownLatch latch = new CountDownLatch(3); @Override public void run(String... args) throws Exception { this.template.send("myTopic", "foo1"); this.template.send("myTopic", "foo2"); this.template.send("myTopic", "foo3"); latch.await(60, TimeUnit.SECONDS); logger.info("All received"); } @KafkaListener(topics = "myTopic") public void listen(ConsumerRecord<?, ?> cr) throws Exception { logger.info(cr.toString()); latch.countDown(); } }
配置application.properties
:
spring.kafka.consumer.group-id=foo spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.listener.missing-topics-fatal=false
spring.kafka.consumer.group-id
指定消费者组id。spring.kafka.consumer.auto-offset-reset
确保新的消费者组能获得我们之前发送的消息,为了测试方便(生产配置latest,只获取最新的消息)。spring.kafka.listener.missing-topics-fatal
监听的topic如果不存在,则不报错
Kafka Broker配置(0.10版)
3.1 Broker配置
基本配置如下:
- broker.id
- log.dirs
- zookeeper.connect
下文将详细论述了主题级别配置和默认值。
名称 | 描述 | 类型 | 默认 | 有效值 | 重要程度 | |
---|---|---|---|---|---|---|
zookeeper.connect | zookeeper host string | string | 高 | |||
advertised.host.name | 过时的:当advertised.listeners 或listeners 没设置时候才使用。请改用advertised.listeners 。Hostname发布到Zookeeper供客户端使用。在IaaS环境中,Broker可能需要绑定不同的接口。如果没有设置,将会使用host.name (如果配置了)。否则将从java.net.InetAddress.getCanonicalHostName() 获取。 |
string | null | 高 | ||
advertised.listeners | 发布到Zookeeper供客户端使用监听(如果不同)。在IaaS环境中,broker可能需要绑定不同的接口。如果没设置,则使用listeners 。 |
string | null | 高 | ||
advertised.port | 过时的:当advertised.listeners 或listeners 没有设置才使用。请改用advertised.listeners 。端口发布到Zookeeper供客户端使用,在IaaS环境中,broker可能需要绑定到不同的端口。如果没有设置,将和broker绑定的同一个端口。 |
int | null | 高 | ||
auto.create.topics.enable | 启用自动创建topic | boolean | true | 高 | ||
auto.leader.rebalance.enable | 启用自动平衡leader。如果需要,后台线程会定期检查并触发leader平衡。 | boolean | true | 高 | ||
background.threads | 用于各种后台处理任务的线程数 | int | 10 | [1,...] | 高 | |
broker.id | 服务器的broker id。如果未设置,将生成一个独一无二的broker id。要避免zookeeper生成的broker id和用户配置的broker id冲突,从reserved.broker.max.id + 1开始生成。 | int | -1 | 高 | ||
compression.type | 为给定topic指定最终的压缩类型。支持标准的压缩编码器('gzip', 'snappy', 'lz4')。也接受'未压缩',就是没有压缩。保留由producer设置的原始的压缩编码。 | string | producer | 高 | ||
delete.topic.enable | 启用删除topic。如果此配置已关闭,通过管理工具删除topic将没有任何效果 | boolean | false | 高 | ||
host.name | 不赞成:当listeners 没有设置才会使用。请改用listeners 。如果设置,它将只绑定到此地址。如果没有设置,它将绑定到所有接口 |
string | "" | 高 | ||
leader.imbalance.check.interval.seconds | 由控制器触发分区再平衡检查的频率 | long | 300 | 高 | ||
leader.imbalance.per.broker.percentage | 允许每个broker的leader比例不平衡。如果每个broker的值高于此值,控制器将触发leader平衡,该值以百分比的形式指定。 | int | 10 | 高 | ||
listeners | 监听列表 - 监听逗号分隔的URL列表和协议。指定hostname为0.0.0.0绑定到所有接口,将hostname留空则绑定到默认接口。合法的listener列表是:PLAINTEXT://myhost:9092,TRACE://:9091 PLAINTEXT://0.0.0.0:9092, TRACE://localhost:9093 | string | null | 高 | ||
log.dir | 保存日志数据的目录 (补充log.dirs属性) | string | /tmp/kafka-logs | 高 | ||
log.dirs | 保存日志数据的目录。如果未设置,则使用log.dir中的值 | string | null | 高 | ||
log.flush.interval.messages | 消息刷新到磁盘之前,累计在日志分区的消息数 | long | 9223372036854775807 | [1,...] | 高 | |
log.flush.interval.ms | topic中的消息在刷新到磁盘之前保存在内存中的最大时间(以毫秒为单位),如果未设置,则使用log.flush.scheduler.interval.ms中的值 | null | 高 | |||
log.flush.offset.checkpoint.interval.ms | 我们更新的持续记录的最后一次刷新的频率。作为日志的恢复点。 | int | 60000 | [0,...] | 高 | |
log.flush.scheduler.interval.ms | 日志刷新的频率(以毫秒为单位)检查是否有任何日志需要刷新到磁盘 | long | 9223372036854775807 | 高 | ||
log.retention.bytes | 删除日志之前的最大大小 | long | -1 | 高 | ||
log.retention.hours | 删除日志文件保留的小时数(以小时为单位)。第三级是log.retention.ms属性 | int | 168 | 高 | ||
log.retention.minutes | 删除日志文件之前保留的分钟数(以分钟为单位)。次于log.retention.ms属性。如果没设置,则使用log.retention.hours的值。 | int | null | 高 | ||
log.retention.ms | 删除日志文件之前保留的毫秒数(以毫秒为单位),如果未设置,则使用log.retention.minutes的值。 | long | null | 高 | ||
log.roll.hours | 新建一个日志段的最大时间(以小时为单位),次于log.roll.ms属性。 | int | 168 | [1,...] | 高 | |
log.roll.jitter.hours | 从logRollTimeMillis(以小时为单位)减去最大抖动,次于log.roll.jitter.ms属性。 | int | 0 | [0,...] | 高 | |
log.roll.ms | 新建一个日志段之前的最大事时间(以毫秒为单位)。如果未设置,则使用log.roll.hours的值。 | long | null | 高 | ||
log.segment.bytes | 单个日志文件的最大大小 | int | 1073741824 | [14,...] | 高 | |
log.segment.delete.delay.ms | 从文件系统中删除文件之前的等待的时间 | long | 60000 | [0,...] | 高 | |
message.max.bytes | 服务器可以接收的消息的最大大小 | int | 1000012 | [0,...] | 高 | |
min.insync.replicas | 当producer设置acks为"all"(或"-1")时。min.insync.replicas指定必须应答成功写入的replicas最小数。如果不能满足最小值,那么producer抛出一个异常(NotEnoughReplicas或NotEnoughReplicasAfterAppend)。 当一起使用时,min.insync.replicas和acks提供最大的耐用性保证。一个典型的场景是创建一个复制因子3的topic,设置min.insync.replicas为2,并且ack是“all”。如果多数副本没有接到写入时,将会抛出一个异常。 |
int | 1 | [1,...] | 高 | |
num.io.threads | 服务器用于执行网络请求的io线程数 | int | 8 | [1,...] | 高 | |
num.network.threads | 服务器用于处理网络请求的线程数。 | int | 3 | [1,...] | 高 | |
num.recovery.threads.per.data.dir | 每个数据的目录线程数,用于启动时日志恢复和关闭时flush。 | int | 1 | [1,...] | 高 | |
num.replica.fetchers | 从源broker复制消息的提取线程数。递增该值可提高follower broker的I/O的并发。 | int | 1 | 高 | ||
offset.metadata.max.bytes | offset提交关联元数据条目的最大大小 | int | 4096 | 高 | ||
offsets.commit.required.acks | commit之前需要的应答数,通常,不应覆盖默认的(-1) | short | -1 | 高 | ||
offsets.commit.timeout.ms | Offset提交延迟,直到所有副本都收到提交或超时。 这类似于生产者请求超时。 | int | 5000 | [1,...] | 高 | |
offsets.load.buffer.size | 当加载offset到缓存时,从offset段读取的批量大小。 | int | 5242880 | [1,...] | 高 | |
offsets.retention.check.interval.ms | 检查过期的offset的频率。 | long | 600000 | [1,...] | 高 | |
offsets.retention.minutes | offset topic的日志保留时间(分钟) | int | 1440 | [1,...] | 高 | |
offsets.topic.compression.codec | 压缩编码器的offset topic - 压缩可以用于实现“原子”提交 | int | 0 | 高 | ||
offsets.topic.num.partitions | offset commit topic的分区数(部署之后不应更改) | int | 50 | [1,...] | 高 | |
offsets.topic.replication.factor | offset topic复制因子(ps:就是备份数,设置的越高来确保可用性)。为了确保offset topic有效的复制因子,第一次请求offset topic时,活的broker的数量必须最少最少是配置的复制因子数。 如果不是,offset topic将创建失败或获取最小的复制因子(活着的broker,复制因子的配置) | short | 3 | [1,...] | 高 | |
offsets.topic.segment.bytes | offset topic段字节应该相对较小一点,以便于加快日志压缩和缓存加载 | int | 104857600 | [1,...] | 高 | |
port | 不赞成:当listener 没有设置才使用。请改用listeners 。该port监听和接收连接。 |
int | 9092 | 高 | ||
queued.max.requests | 在阻塞网络线程之前允许的排队请求数 | int | 500 | [1,...] | 高 | |
quota.consumer.default | 过时的:当默认动态的quotas没有配置或在Zookeeper时。如果每秒获取的字节比此值高,所有消费者将通过clientId/consumer区分限流。 | long | 9223372036854775807 | [1,...] | 高 | |
quota.producer.default | 过时的:当默认动态的quotas没有配置,或在zookeeper时。如果生产者每秒比此值高,所有生产者将通过clientId区分限流。 | long | 9223372036854775807 | [1,...] | 高 | |
replica.fetch.min.bytes Minimum | 每个获取响应的字节数。如果没有满足字节数,等待replicaMaxWaitTimeMs。 | int | 1 | 高 | ||
replica.fetch.wait.max.ms | 跟随者副本发出每个获取请求的最大等待时间,此值应始终小于replica.lag.time.max.ms,以防止低吞吐的topic的ISR频繁的收缩。 | int | 500 | 高 | ||
replica.high.watermark. checkpoint.interval.ms |
达到高“水位”保存到磁盘的频率。 | long | 5000 | 高 | ||
replica.lag.time.max.ms | 如果一个追随者没有发送任何获取请求或至少在这个时间的这个leader的没有消费完。该leader将从isr中移除这个追随者。 | long | 10000 | 高 | ||
replica.socket.receive.buffer.bytes | 用于网络请求的socket接收缓存区 | int | 65536 | 高 | ||
replica.socket.timeout.ms | 网络请求的socket超时,该值最少是replica.fetch.wait.max.ms | int | 30000 | 高 | ||
request.timeout.ms | 该配置控制客户端等待请求的响应的最大时间,。如果超过时间还没收到消费。客户端将重新发送请求,如果重试次数耗尽,则请求失败。 | int | 30000 | 高 | ||
socket.receive.buffer.bytes | socket服务的SO_RCVBUF缓冲区。如果是-1,则默认使用OS的。 | int | 102400 | 高 | ||
socket.request.max.bytes | socket请求的最大字节数 | int | 104857600 | [1,...] | 高 | |
socket.send.buffer.bytes | socket服务的SO_SNDBUF缓冲区。如果是-1,则默认使用OS的。 | int | 102400 | 高 | ||
unclean.leader.election.enable | 是否启用不在ISR中的副本参与选举leader的最后的手段。这样做有可能丢失数据。 | boolean | true | 高 | ||
zookeeper.connection.timeout.ms | 连接zookeeper的最大等待时间,如果未设置,则使用zookeeper.session.timeout.ms。 | int | null | 高 | ||
zookeeper.session.timeout.ms | Zookeeper会话的超时时间 | int | 6000 | 高 | ||
zookeeper.set.acl | 设置客户端使用安全的ACL | boolean | false | 高 | ||
broker.id.generation.enable | 启用自动生成broker id。启用该配置时应检查reserved.broker.max.id。 | boolean | true | 中等 | ||
broker.rack | broker机架,用于机架感知副本分配的失败容错。例如:RACK1 , us-east-1d |
string | null | 中等 | ||
connections.max.idle.ms Idle | 连接超时:闲置时间超过该设置,则服务器socket处理线程关闭这个连接。 | long | 600000 | 中等 | ||
controlled.shutdown.enable | 启用服务器的关闭控制。 | boolean | true | 中等 | ||
controlled.shutdown.max.retries | 控制因多种原因导致的shutdown失败,当这样失败发生,尝试重试的次数 | int | 3 | 中等 | ||
controlled.shutdown.retry.backoff.ms | 在每次重试之前,系统需要时间从导致先前故障的状态(控制器故障转移,复制延迟等)恢复。 此配置是重试之前等待的时间数。 | long | 5000 | 中等 | ||
controller.socket.timeout.ms | 控制器到broker通道的sockt超时时间 | int | 30000 | 中 | ||
default.replication.factor | 自动创建topic的默认的副本数 | int | 1 | 中 | ||
fetch.purgatory.purge.interval.requests | 拉取请求清洗间隔(请求数) | int | 1000 | 中 | ||
group.max.session.timeout.ms | 已注册的消费者允许的最大会话超时时间,设置的时候越长使消费者有更多时间去处理心跳之间的消息。但察觉故障的时间也拉长了。 | int | 300000 | 中 | ||
group.min.session.timeout.ms | 已经注册的消费者允许最小的会话超时时间,更短的时间去快速的察觉到故障,代价是频繁的心跳,这可能会占用大量的broker资源。 | int | 6000 | 中 | ||
inter.broker.protocol.version | 指定broker内部通讯使用的版本。通常在更新broker时使用。有效的值为:0.8.0, 0.8.1, 0.8.1.1, 0.8.2, 0.8.2.0, 0.8.2.1, 0.9.0.0, 0.9.0.1。查看ApiVersion找到的全部列表。 | string | 0.10.1-IV2 | 中 | ||
log.cleaner.backoff.ms | 当没有日志要清理时,休眠的时间 | long | 15000 | [0,...] | 中 | |
log.cleaner.dedupe.buffer.size | 用于日志去重的内存总量(所有cleaner线程) | long | 134217728 | 中 | ||
log.cleaner.delete.retention.ms | 删除记录保留多长时间? | long | 86400000 | 中 | ||
log.cleaner.enable | 在服务器上启用日志清洗处理?如果使用的任何topic的cleanup.policy=compact包含内部的offset topic,应启动。如果禁用,那些topic将不会被压缩并且会不断的增大。 | boolean | true | 中 | ||
log.cleaner.io.buffer.load.factor | 日志cleaner去重缓冲负载因子。去重缓冲区的百分比,较高的值将允许同时清除更多的日志,但将会导致更多的hash冲突。 | double | 0.9 | 中 | ||
log.cleaner.io.buffer.size | 所有日志清洁器线程I/O缓存的总内存 | int | 524288 | [0,...] | 中 | |
log.cleaner.io.max.bytes.per.second | 日志清理器限制,以便其读写i/o平均小与此值。 | double | 1.7976931348623157E308 | 中 | ||
log.cleaner.min.cleanable.ratio | 脏日志与日志的总量的最小比率,以符合清理条件 | double | 0.5 | 中 | ||
log.cleaner.min.compaction.lag.ms | 一条消息在日志保留不压缩的最小时间,仅适用于正在压缩的日志。 | long | 0 | 中 | ||
log.cleaner.threads | 用于日志清除的后台线程数 | int | 1 | [0,...] | 中 | |
log.cleanup.policy | 超过保留时间段的默认清除策略。逗号分隔的有效的策略列表。有效的策略有:“delete”和“compact” | list | [delete] | [compact, delete] | 中 | |
log.index.interval.bytes | 添加一个条目到offset的间隔 | index | int | 4096 | [0,...] | 中 |
log.index.size.max.bytes | offset index的最大大小(字节) | int | 10485760 | [4,...] | 中 | |
log.message.format.version | 指定追加到日志中的消息格式版本。例如: 0.8.2, 0.9.0.0, 0.10.0。通过设置一个特定消息格式版本,用户需要保证磁盘上所有现有的消息小于或等于指定的版本。错误的设置将导致旧版本的消费者中断,因为消费者接收一个不理解的消息格式。 | string | 0.10.1-IV2 | 中 | ||
log.message.timestamp.difference.max.ms | 如果log.message.timestamp.type=CreateTime,broker接收消息时的时间戳和消息中指定的时间戳之间允许的最大差异。如果时间戳超过此阈值,则消息将被拒绝。如果log.message.timestamp.type=LogAppendTime,则此配置忽略。 | long | 9223372036854775807 | [0,...] | 中 | |
log.message.timestamp.type | 定义消息中的时间戳是消息创建时间或日志追加时间。该值可设置为CreateTime 或 LogAppendTime |
string | CreateTime | [CreateTime, LogAppendTime] | 中 | |
log.preallocate | 在创建新段时预分配文件?如果你在Windowns上使用kafka,你可能需要设置它为true。 | boolean | false | 中 | ||
log.retention.check.interval.ms | 日志清除程序检查日志是否满足被删除的频率(以毫秒为单位) | long | 300000 | [1,...] | 中 | |
max.connections.per.ip | 允许每个ip地址的最大连接数。 | int | 2147483647 | [1,...] | 中 | |
max.connections.per.ip.overrides | per-ip或hostname覆盖默认最大连接数 | string | "" | 中 | ||
num.partitions | topic的默认分区数 | int | 1 | [1,...] | 中 | |
principal.builder.class | 实现PrincipalBuilder接口类的完全限定名,该接口目前用于构建与SSL SecurityProtocol连接的Principal。 | class | class org.apache.kafka. common.security.auth .DefaultPrincipalBuilder |
中 | ||
producer.purgatory.purge.interval.requests | 生产者请求purgatory的清洗间隔(请求数) | int | 1000 | 中 | ||
replica.fetch.backoff.ms | 当拉取分区发生错误时休眠的时间 | 1000 | [0,...] | 中 | ||
replica.fetch.max.bytes | 拉取每个分区的消息的字节数。这不是绝对的最大值,如果提取的第一个非空分区中的第一个消息大于这个值,则消息仍然返回,以确保进展。通过message.max.bytes (broker配置)或max.message.bytes (topic配置)定义broker接收的最大消息大小。 | int | 1048576 | [0,...] | 中 | |
replica.fetch.response.max.bytes | 预计整个获取响应的最大字节数,这不是绝对的最大值,如果提取的第一个非空分区中的第一个消息大于这个值,则消息仍然返回,以确保进展。通过message.max.bytes (broker配置)或max.message.bytes (topic配置)定义broker接收的最大消息大小。 | int | 10485760 | [0,...] | 中 | |
reserved.broker.max.id | broker.id的最大数 | int | 1000 | [0,...] | 中 | |
sasl.enabled.mechanisms | 可用的SASL机制列表,包含任何可用的安全提供程序的机制。默认情况下只有GSSAPI是启用的。 | list | [GSSAPI] | 中 | ||
sasl.kerberos.kinit.cmd | Kerberos kinit 命令路径。 | string | /usr/bin/kinit | 中 | ||
sasl.kerberos.min.time.before.relogin | 登录线程在刷新尝试的休眠时间。 | long | 60000 | 中 | ||
sasl.kerberos.principal.to.local.rules | principal名称映射到一个短名称(通常是操作系统用户名)。按顺序,使用与principal名称匹配的第一个规则将其映射其到短名称。忽略后面的规则。默认情况下,{username}/{hostname}@{REALM} 映射到 {username}。 | list | [DEFAULT] | 中 | ||
sasl.kerberos.service.name | Kafka运行的Kerberos principal名称。 可以在JAAS或Kafka的配置文件中定义。 | string | null | 中 | ||
sasl.kerberos.ticket.renew.jitter | 添加到更新时间的随机抖动的百分比 | time. double | 0.05 | 中 | ||
sasl.kerberos.ticket.renew.window.factor | 登录线程休眠,直到从上次刷新到ticket的到期的时间已到达(指定窗口因子),在此期间它将尝试更新ticket。 | double | 0.8 | 中 | ||
sasl.mechanism.inter.broker.protocol | SASL机制,用于broker之间的通讯,默认是GSSAPI。 | string | GSSAPI | 中 | ||
security.inter.broker.protocolSecurity | broker之间的通讯协议,有效值有:PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL。 | string | PLAINTEXT | 中 | ||
ssl.cipher.suites | 密码套件列表。认证,加密,MAC和秘钥交换算法的组合,用于使用TLS或SSL的网络协议交涉 网络连接的安全设置,默认情况下,支持所有可用的密码套件。 |
list | null | 中 | ||
ssl.client.auth | 配置请求客户端的broker认证。常见的设置:ssl.client.auth=required 需要客户端认证。ssl.client.auth=requested 客户端认证可选,不同于requested ,客户端可选择不提供自身的身份验证信息* ssl.client.auth=none 不需要客户端身份认证 |
string | none | [required, requested, none] | 中 | |
ssl.enabled.protocols | 已启用的SSL连接协议列表。 | list | [TLSv1.2, TLSv1.1, TLSv1] | 中 | ||
ssl.key.password | 秘钥库文件中的私钥密码。对客户端是可选的。 | password | null | 中 | ||
ssl.keymanager.algorithm | 用于SSL连接的密钥管理工厂算法。默认值是Java虚拟机的密钥管理工厂算法。 | string | SunX509 | 中 | ||
ssl.keystore.location | 密钥仓库文件的位置。客户端可选,并可用于客户端的双向认证。 | string | null | 中 | ||
ssl.keystore.password | 密钥仓库文件的仓库密码。客户端可选,只有ssl.keystore.location配置了才需要。 | password | null | 中 | ||
ssl.keystore.type | 密钥仓库文件的格式。客户端可选。 | string | JKS | 中 | ||
ssl.protocol | 用于生成SSLContext,默认是TLS,适用于大多数情况。允许使用最新的JVM,LS, TLSv1.1 和TLSv1.2。 SSL,SSLv2和SSLv3 老的JVM也可能支持,由于有已知的安全漏洞,不建议使用。 | string | TLS | 中 | ||
ssl.provider | 用于SSL连接的安全提供程序的名称。默认值是JVM的安全程序。 | string | null | 中 | ||
ssl.trustmanager.algorithm | 信任管理工厂用于SSL连接的算法。默认为Java虚拟机配置的信任算法。 | string | PKIX | 中 | ||
ssl.truststore.location | 信任仓库文件的位置 | string | null | 中 | ||
ssl.truststore.password | 信任仓库文件的密码 | password | null | 中 | ||
ssl.truststore.type | 信任仓库文件的文件格式 | string | JKS | 中 | ||
authorizer.class.name | 用于认证的授权程序类 | string | "" | 低 | ||
metric.reporters | 度量报告的类列表,通过实现MetricReporter 接口,允许插入新度量标准类。JmxReporter包含注册JVM统计。 |
list | [] | 低 | ||
metrics.num.samples | 维持计算度量的样本数。 | int | 2 | [1,...] | 低 | |
metrics.sample.window.ms | 计算度量样本的时间窗口 | long | 30000 | [1,...] | 低 | |
quota.window.num | 在内存中保留客户端限额的样本数 | int | 11 | [1,...] | 低 | |
quota.window.size.seconds | 每个客户端限额的样本时间跨度 | int | 1 | [1,...] | 低 | |
replication.quota.window.num | 在内存中保留副本限额的样本数 | int | 11 | [1,...] | 低 | |
replication.quota.window.size.seconds | 每个副本限额样本数的时间跨度 | int | 1 | [1,...] | 低 | |
ssl.endpoint.identification.algorithm | 端点身份标识算法,使用服务器证书验证服务器主机名。 | string | null | 低 | ||
ssl.secure.random.implementation | 用于SSL加密操作的SecureRandom PRNG实现。 | string | null | 低 | ||
zookeeper.sync.time.ms | ZK follower可落后与leader多久。 | int | 2000 | 低 |
更多关于broker配置的详情,可以在scala类中的kafka.server.KafkaConfig
找到。
可查看之前的版本:Kafka Broker配置(0.8.2)