kafka学习笔记
1、kafka在windows上的安装、运行
(1)安装jdk
(2)安装zookeeper
(3)安装kafka
①下载地址:http://kafka.apache.org/downloads,要下载Binary downloads这个类型,如 kafka_2.11-2.2.0.tgz,不要下载源文件,这种方便使用,下载后,解压。
②编辑文件"config/server.properties",找到并编辑:
log.dirs=E:/tools/kafka/kafka-logs #kafka日志保存目录,注意:路径要么是"/"分割,要么是转义字符"\\",这样会生成正确的路径(层级,子目录)。
zookeeper.connect=localhost:2181 #代表kafka所连接的zookeeper所在的服务器IP以及端口,可根据需要更改
③配置好后,kafka会按照默认配置,在9092端口上运行,并连接zookeeper的默认端口2181
(4)运行kafka
提示:请确保启动kafka服务器前,Zookeeper实例已经在运行,因为kafka的运行是需要zookeeper这种分布式应用程序协调服务。
①进入kafka安装目录E:\tools\kafka
②按下shift+鼠标右键,选择"在此处打开命令窗口",打开命令行。
③在命令行中输入:.\bin\windows\kafka-server-start.bat .\config\server.properties 回车,启动kafka服务器。注意是 \ 而不是 /
到目前为止,zookeeper以及kafka都已正确运行。注意:保持zookeeper和kafka-server运行状态,不要关闭。
(5)创建主题(topic)
①创建主题,命名为"test0811",replication-factor=1(因为只有一个kafka服务器在运行)。可根据集群中kafka服务器个数来修改replication-factor的数量,以便提高系统容错性等。
②在E:\tools\kafka\bin\windows目录下打开新的命令行,输入命令: kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test0811,回车,输出 Created topic "test0811"表示创建主题成功,该窗口可以关闭
③输入命令 kafka-topics.bat --list --zookeeper localhost:2181 查询连接到指定zookeeper上kafka服务器上的所有topic
(6)创建生产者(producer)和消费者(consumer)
①在E:\tools\kafka\bin\windows目录下打开新的命令行,输入命令 kafka-console-producer.bat --broker-list localhost:9092 --topic test0811 ,启动producer,该窗口不要关闭
②同样在该目录下打开新的命令行,输入命令 kafka-console-consumer.bat --zookeeper localhost:2181 --topic test0811,启动consumer,该窗口不要关闭
现在生产者、消费者均已创建完成,在producer命令行窗口中任意输入内容,回车,在consumer命令行窗口中即可看到相应的内容。至此,已完成kafka在windows下的安装和基本的使用。
2、kafka API的使用
(1)配置maven依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>1.1.0</version>
</dependency>
(2)启动kafka server(即启动kafka broker)
*kafka基础知识
Broker -- broker承担消息的存储转发工作
Zookeeper -- zookeeper保存kafka的元信息(broker列表,topic列表,消息的消费进度)
Producer -- producer是消息的生产者,产生消息
Consumer -- consumer是消息的消费者,消费消息
Topic -- topic对应一个具体的队列,在kafka的概念中,一般一个应用一个队列
Partition -- 应用数据往往呈现部分有序的特点,因此在kafka队列中引入partition的概念,即将topic划分为多个partition,单个partition内保证有序,partition间不保证,这样做的好处是充分利用kafka集群的能力,均匀负载和提高性能
Replication -- replication主要是为了kafka集群的高可用性,保证部分节点失效的恶劣情况下,队列数据不丢失
(3)具体代码实现
生产者代码
import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; public class ProducerDemo { public static void main(String[] args) { Properties properties = new Properties(); /* * bootstrap.servers是Kafka集群的IP地址,如果Broker数量超过1个,则使用逗号分隔, * 如"192.168.1.110:9092,192.168.1.110:9092",其中,192.168.1.110是服务器ip,9092是所监听的端口 */ properties.put("bootstrap.servers", "localhost:9092"); properties.put("acks", "all"); properties.put("retries", 0); properties.put("batch.size", 16384); properties.put("linger.ms", 1); properties.put("buffer.memory", 33554432); /* * 序列化类型。 Kafka消息是以键值对的形式发送到Kafka集群的,其中Key是可选的,Value可以是任意类型。但是在Message被发送到Kafka集群之前, * Producer需要把不同类型的消息序列化为二进制类型。本例是发送文本消息到Kafka集群,所以使用的是StringSerializer */ properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = null; try { producer = new KafkaProducer<String, String>(properties); for (int i = 0; i < 5; i++) { String msg = "Message " + i; producer.send(new ProducerRecord<String, String>("test", msg)); System.out.println("Sent:" + msg); } } catch (Exception e) { e.printStackTrace(); } finally { producer.close(); } } }
消费者代码
import java.util.Arrays; import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; public class ConsumerDemo { public static void main(String[] args) { Properties properties = new Properties(); properties.put("bootstrap.servers", "localhost:9092"); properties.put("group.id", "group-1"); properties.put("enable.auto.commit", "true"); properties.put("auto.commit.interval.ms", "1000"); properties.put("auto.offset.reset", "earliest"); properties.put("session.timeout.ms", "30000"); properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties); //订阅的topic列表 kafkaConsumer.subscribe(Arrays.asList("test")); while (true) { //Consumer调用poll方法来轮循Kafka集群的消息,其中的参数100是超时时间 ConsumerRecords<String, String> records = kafkaConsumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.printf("offset = %d, value = %s", record.offset(), record.value()); System.out.println(); } } } }
3、kafka如何彻底删除topic及数据
(step1)在server.properties中配置 auto.create.topics.enable = false,并认真把生产者和消费者程序彻底全部停止
如果需要删除的topic此时正在被应用程序produce和consume,则这些生产者和消费者都需要停止。因为如果有程序正在生产或消费该topic,则该topic的offset信息一致会在broker更新,调用kafka delete命令无法删除该topic,同时,需要设置
auto.create.topics.enable = false,默认设置为true,即当produce或者consume不存在的topic时kafka会自动创建这个topic,这样会给删除topic带来很多意想不到的问题。所以,这一步很重要,必须设置 auto.create.topics.enable = false,并认真把
生产者和消费者程序彻底全部停止
(step2)在server.properties中配置 delete.topic.enable = true
topic删除命令 .\bin\windows\kafka-topics --delete --zookeeper 【zookeeper server:port】 --topic 【topic name】,但如果没有设置 delete.topic.enable=true,则调用kafka 的delete命令无法真正将topic删除,而是显示(marked for deletion)
(step3)调用命令删除topic
topic删除命令 .\bin\windows\kafka-topics --delete --zookeeper 【zookeeper server:port】 --topic 【topic name】
(step4)删除kafka存储目录(server.properties文件的log.dirs配置)相关的topic数据目录
注意:如果kafka 有多个 broker,且每个broker 配置了多个数据盘(比如 /data/kafka-logs,/data1/kafka-logs ...),且topic也有多个分区和replica,则需要对所有broker的所有数据盘进行扫描,删除该topic的所有分区数据。
一般而言,经过上面4步就可以正常删除掉topic和topic的数据。但是,如果经过上面四步,还是无法正常删除topic,则需要对kafka在zookeeer的存储信息进行删除。具体操作如下:
(注意:以下步骤里面,kafka在zk里面的节点信息是采用默认值,如果你的系统修改过kafka在zk里面的节点信息,则需要根据系统的实际情况找到准确位置进行操作)
(step5)删除kafka在zookeeer上的存储信息
zookeeper客户端连接到zookeeper服务器,找到topic所在的目录:ls /brokers/topics,找到要删除的topic,然后执行命令 rmr /brokers/topics/【topic name】即可,此时topic被彻底删除。
如果topic 是被标记为 marked for deletion,则通过命令 ls /admin/delete_topics,找到要删除的topic,然后执行命令:rmr /admin/delete_topics/【topic name】
完成之后,调用命令: .\bin\windows\kafka-topics.sh --list --zookeeper 【zookeeper server:port】查看现在kafka的topic信息。正常情况下删除的topic就不会再显示。但是,如果还能够查询到删除的topic,则重启zk和kafka即可。
4、kafka发布消息时如何选择Partition
partition是kafka消息存储的基本单位,那kafka发送消息到有多个partition的topic时是到底选择哪个partition呢?默认情况下(当key为null)采用Round-robin轮询调度算法选择partition,也就是风水轮流转,其实现类是DefaultPartition。但我们实际应用中为保持相关
消息按顺序存储到同一个partition,就必须将消息发送到指定的partition,有以下三种方法:
- 给消息指定Partition编号
- 给消息指定key
- 自定义Partitioner-实现org.apache.kafka.clients.producer.Partitioner,实现partition选择策略,并通过属性注册
(1)创建有多个partition的topic进行测试 , kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic 【topic名】
查看创建的topic信息 kafka-topics.bat --describe --zookeeper localhost:2181 --topic 【topic名】

(2)代码测试
import java.util.Properties; import java.util.concurrent.Future; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; public class ProducerDemo { public static void main(String[] args) { Properties properties = new Properties(); /* * bootstrap.servers是Kafka集群的IP地址,如果Broker数量超过1个,则使用逗号分隔, * 如"192.168.1.110:9092,192.168.1.110:9092",其中,192.168.1.110是服务器ip,9092是所监听的端口 */ properties.put("bootstrap.servers", "localhost:9092"); properties.put("acks", "all"); properties.put("retries", 0); properties.put("batch.size", 16384); properties.put("linger.ms", 1); properties.put("buffer.memory", 33554432); /* * 序列化类型。 Kafka消息是以键值对的形式发送到Kafka集群的,其中Key是可选的,Value可以是任意类型。但是在Message被发送到Kafka集群之前, * Producer需要把不同类型的消息序列化为二进制类型。本例是发送文本消息到Kafka集群,所以使用的是StringSerializer */ properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = null; try { producer = new KafkaProducer<String, String>(properties); ProducerRecord<String, String> producerRecord = null; Future<RecordMetadata> sendFuture = null; RecordMetadata recordMetadata = null; for (int i = 0; i < 10; i++) { String msg = "hello " + i;
// 未指定消息的partition编号、key,则使用默认的DefaultPartitioner,采用Round-robin简单轮询策略 producerRecord = new ProducerRecord<String, String>("fanghao", msg); sendFuture = producer.send(producerRecord); recordMetadata = sendFuture.get(); System.out.println("Sent:" + msg + "to partition:" + recordMetadata.partition() + ",offset:" + recordMetadata.offset()); } } catch (Exception e) { e.printStackTrace(); } finally { producer.close(); } } }
这里创建 ProducerRecord 是用的 new ProducerRecord<>("topic名", i), 它实际是通过
this(topic, null, null, null, value, null);
调用方法
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value, Iterable<Header> headers)
所以 partition, timestamp, key, headers 都是 null, 当 partition 和 key 都为 null 时就要采用 Round-robin 了,在 ProducerRecord 没有 partition 和 key 的情况下,发送 10 消息所用的 Partition 是下面那样子的:
Sent:hello 0to partition:2,offset:0 Sent:hello 1to partition:1,offset:0 Sent:hello 2to partition:0,offset:0 Sent:hello 3to partition:2,offset:1 Sent:hello 4to partition:1,offset:1 Sent:hello 5to partition:0,offset:1 Sent:hello 6to partition:2,offset:2 Sent:hello 7to partition:1,offset:2 Sent:hello 8to partition:0,offset:2 Sent:hello 9to partition:2,offset:3
注意到这个 DefaultPartitioner 中的 Round-robin 算法,在三个 Partition 时并不是以自然顺序 0, 1, 2 的顺序。这里三个 Partition 时顺序是 0, 2, 1 这样的顺序,而且每次从哪个索引号开始也是随机的,所以有时会是 2, 1, 0, 或 1, 0, 2, 反正每个 Partition 机会均等。
(3)指定partition编号
import java.util.List; import java.util.Properties; import java.util.concurrent.Future; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.PartitionInfo; public class ProducerDemo { public static void main(String[] args) { Properties properties = new Properties(); /* * bootstrap.servers是Kafka集群的IP地址,如果Broker数量超过1个,则使用逗号分隔, * 如"192.168.1.110:9092,192.168.1.110:9092",其中,192.168.1.110是服务器ip,9092是所监听的端口 */ properties.put("bootstrap.servers", "localhost:9092"); properties.put("acks", "all"); properties.put("retries", 0); properties.put("batch.size", 16384); properties.put("linger.ms", 1); properties.put("buffer.memory", 33554432); /* * 序列化类型。 Kafka消息是以键值对的形式发送到Kafka集群的,其中Key是可选的,Value可以是任意类型。但是在Message被发送到Kafka集群之前, * Producer需要把不同类型的消息序列化为二进制类型。本例是发送文本消息到Kafka集群,所以使用的是StringSerializer */ properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = null; try { producer = new KafkaProducer<String, String>(properties); ProducerRecord<String, String> producerRecord = null; Future<RecordMetadata> sendFuture = null; RecordMetadata recordMetadata = null; //可通过 producer.partitionsFor("topic名")获取partition列表 List<PartitionInfo> partitionsFor = producer.partitionsFor("fanghao"); for (int i = 0; i < 10; i++) { String msg = "hello " + i; // producerRecord = new ProducerRecord<String, String>("fanghao", msg); //创建ProducerRecord时指定partition编号,但key为null(partition编号从0开始) producerRecord = new ProducerRecord<String, String>("fanghao", 0, null, msg); sendFuture = producer.send(producerRecord); recordMetadata = sendFuture.get(); System.out.println("Sent:" + msg + "to partition:" + recordMetadata.partition() + ",offset:" + recordMetadata.offset()); } } catch (Exception e) { e.printStackTrace(); } finally { producer.close(); } } }
指定了partition编号,但key是null时,执行效果如下:
Sent:hello 0to partition:0,offset:3 Sent:hello 1to partition:0,offset:4 Sent:hello 2to partition:0,offset:5 Sent:hello 3to partition:0,offset:6 Sent:hello 4to partition:0,offset:7 Sent:hello 5to partition:0,offset:8 Sent:hello 6to partition:0,offset:9 Sent:hello 7to partition:0,offset:10 Sent:hello 8to partition:0,offset:11 Sent:hello 9to partition:0,offset:12
同时测试一下指定了partition编号和key:
import java.util.List; import java.util.Properties; import java.util.concurrent.Future; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.PartitionInfo; public class ProducerDemo { public static void main(String[] args) { Properties properties = new Properties(); /* * bootstrap.servers是Kafka集群的IP地址,如果Broker数量超过1个,则使用逗号分隔, * 如"192.168.1.110:9092,192.168.1.110:9092",其中,192.168.1.110是服务器ip,9092是所监听的端口 */ properties.put("bootstrap.servers", "localhost:9092"); properties.put("acks", "all"); properties.put("retries", 0); properties.put("batch.size", 16384); properties.put("linger.ms", 1); properties.put("buffer.memory", 33554432); /* * 序列化类型。 Kafka消息是以键值对的形式发送到Kafka集群的,其中Key是可选的,Value可以是任意类型。但是在Message被发送到Kafka集群之前, * Producer需要把不同类型的消息序列化为二进制类型。本例是发送文本消息到Kafka集群,所以使用的是StringSerializer */ properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = null; try { producer = new KafkaProducer<String, String>(properties); ProducerRecord<String, String> producerRecord = null; Future<RecordMetadata> sendFuture = null; RecordMetadata recordMetadata = null; //可通过 producer.partitionsFor("topic名")获取partition列表 List<PartitionInfo> partitionsFor = producer.partitionsFor("fanghao"); for (int i = 0; i < 10; i++) { String msg = "hello " + i; // producerRecord = new ProducerRecord<String, String>("fanghao", msg); //创建ProducerRecord时指定partition编号,但key为null(partition编号从0开始) // producerRecord = new ProducerRecord<String, String>("fanghao", 0, null, msg); //创建ProducerRecord时指定partition编号(partition编号从0开始),同时指定key producerRecord = new ProducerRecord<String, String>("fanghao", 0, msg, msg); sendFuture = producer.send(producerRecord); recordMetadata = sendFuture.get(); System.out.println("Sent:" + msg + "to partition:" + recordMetadata.partition() + ",offset:" + recordMetadata.offset()); } } catch (Exception e) { e.printStackTrace(); } finally { producer.close(); } } }
指定了partition编号和key,执行效果如下:
Sent:hello 0to partition:0,offset:13 Sent:hello 1to partition:0,offset:14 Sent:hello 2to partition:0,offset:15 Sent:hello 3to partition:0,offset:16 Sent:hello 4to partition:0,offset:17 Sent:hello 5to partition:0,offset:18 Sent:hello 6to partition:0,offset:19 Sent:hello 7to partition:0,offset:20 Sent:hello 8to partition:0,offset:21 Sent:hello 9to partition:0,offset:22
由以上测试效果可看出,只要指定了partition编号,即使指定了key,这个key也不会参与决策将消息发送到哪一个partition
(4)指定key【在没有指定partition编号时,如果指定了key,kafka将依据key哈希出partition编号来】
import java.util.Properties; import java.util.concurrent.Future; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; public class ProducerDemo { public static void main(String[] args) { Properties properties = new Properties(); /* * bootstrap.servers是Kafka集群的IP地址,如果Broker数量超过1个,则使用逗号分隔, * 如"192.168.1.110:9092,192.168.1.110:9092",其中,192.168.1.110是服务器ip,9092是所监听的端口 */ properties.put("bootstrap.servers", "localhost:9092"); properties.put("acks", "all"); properties.put("retries", 0); properties.put("batch.size", 16384); properties.put("linger.ms", 1); properties.put("buffer.memory", 33554432); /* * 序列化类型。 Kafka消息是以键值对的形式发送到Kafka集群的,其中Key是可选的,Value可以是任意类型。但是在Message被发送到Kafka集群之前, * Producer需要把不同类型的消息序列化为二进制类型。本例是发送文本消息到Kafka集群,所以使用的是StringSerializer */ properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = null; try { producer = new KafkaProducer<String, String>(properties); ProducerRecord<String, String> producerRecord = null; Future<RecordMetadata> sendFuture = null; RecordMetadata recordMetadata = null; for (int i = 0; i < 10; i++) { String msg = "hello " + i; //创建ProducerRecord时指定key producerRecord = new ProducerRecord<String, String>("fanghao", i<7 ? "aa" : "bb", msg); sendFuture = producer.send(producerRecord); recordMetadata = sendFuture.get(); System.out.println("Sent:" + msg + "to partition:" + recordMetadata.partition() + ",offset:" + recordMetadata.offset()); } } catch (Exception e) { e.printStackTrace(); } finally { producer.close(); } } }
未指定partition编号,指定key时的执行效果如下:
Sent:hello 0to partition:0,offset:23 Sent:hello 1to partition:0,offset:24 Sent:hello 2to partition:0,offset:25 Sent:hello 3to partition:0,offset:26 Sent:hello 4to partition:0,offset:27 Sent:hello 5to partition:0,offset:28 Sent:hello 6to partition:0,offset:29 Sent:hello 7to partition:1,offset:3 Sent:hello 8to partition:1,offset:4 Sent:hello 9to partition:1,offset:5
只要 Partition 的数目不变,上面的代码执行千百遍所选择的 Partition 都会是一样的。一般来说我们不建议使用 Key 来算出 Partition 编号,因为极有可能消息不能平均的分布到每一个 Partition, 除非是一个 UUID。比如用户输入的不确定的字符串,或是一个数字序列 123450001, 123450002, 123450003, 变化部分在后端,很容易使得个别 Partition 很繁忙,而有些却闲得蛋疼,降低了 Topic 运输数据的效率。
(5)实现自己的partitioner
自定义的 Partitioner 需要实现 org.apache.kafka.clients.producer.Partition 接口(含三个实现方法)。下面是一个粗陋的自定义实现
import java.util.Map; import org.apache.kafka.clients.producer.Partitioner; import org.apache.kafka.common.Cluster; public class MyPartitioner implements Partitioner { @Override public void configure(Map<String, ?> configs) { } @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) { return 1; } @Override public void close() { } }
然后在创建KafkaProducer时指定partitioner.class属性
import java.util.Properties; import java.util.concurrent.Future; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; public class ProducerDemo { public static void main(String[] args) { Properties properties = new Properties(); /* * bootstrap.servers是Kafka集群的IP地址,如果Broker数量超过1个,则使用逗号分隔, * 如"192.168.1.110:9092,192.168.1.110:9092",其中,192.168.1.110是服务器ip,9092是所监听的端口 */ properties.put("bootstrap.servers", "localhost:9092"); properties.put("acks", "all"); properties.put("retries", 0); properties.put("batch.size", 16384); properties.put("linger.ms", 1); properties.put("buffer.memory", 33554432); //指定partitioner.class属性 // properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.fanghao.MyPartitioner"); properties.put("partitioner.class", "com.fanghao.MyPartitioner"); /* * 序列化类型。 Kafka消息是以键值对的形式发送到Kafka集群的,其中Key是可选的,Value可以是任意类型。但是在Message被发送到Kafka集群之前, * Producer需要把不同类型的消息序列化为二进制类型。本例是发送文本消息到Kafka集群,所以使用的是StringSerializer */ properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = null; try { producer = new KafkaProducer<String, String>(properties); ProducerRecord<String, String> producerRecord = null; Future<RecordMetadata> sendFuture = null; RecordMetadata recordMetadata = null; for (int i = 0; i < 10; i++) { String msg = "hello " + i; //创建ProducerRecord时指定key producerRecord = new ProducerRecord<String, String>("fanghao", msg); sendFuture = producer.send(producerRecord); recordMetadata = sendFuture.get(); System.out.println("Sent:" + msg + "to partition:" + recordMetadata.partition() + ",offset:" + recordMetadata.offset()); } } catch (Exception e) { e.printStackTrace(); } finally { producer.close(); } } }
自定义partitioner执行效果如下:
Sent:hello 0to partition:1,offset:9 Sent:hello 1to partition:1,offset:10 Sent:hello 2to partition:1,offset:11 Sent:hello 3to partition:1,offset:12 Sent:hello 4to partition:1,offset:13 Sent:hello 5to partition:1,offset:14 Sent:hello 6to partition:1,offset:15 Sent:hello 7to partition:1,offset:16 Sent:hello 8to partition:1,offset:17 Sent:hello 9to partition:1,offset:18
(6)Consumer poll消息时是只从一个partition中获取若干条记录,还是同时从指定topic的多个partition中获取一批消息?
答:kafka的consumer客户端轮询消息时,会轮询指定topic的每一个partition,并将获得的消息组装在一个ConsumerRecords<K, V>对象中
5、kafka中的消费者组(Consumer Group)
(1)消费者组是kafka实现单播和广播两种消息模型的手段,订阅了同一个topic的每一个消费者组都可以拿到相同的全部数据,而一个partition只会绑定到同一个消费者组里的一个消费者实例,其中的消息都会被该实例消费
(2)一个消费者组里的消费者实例数量与一个topic的partition数量不一致时会有什么样的表现呢?
- 消费者数量小于partition数量:一个消费者可能会绑定多个partition(凡发送到该partiton的消息都会被该消费者消费)
- 消费者数量大于partition数量:存在消费者空闲,无法读取消息
- 消费者数量等于partition数量:一个消费者绑定一个partition(凡发送到该partiton的消息都会被该消费者消费)
6、kafka的日志
kafka的消息存储在日志文件中。kafka消息是以主题为单位进行归类,各个主题之间是彼此独立的,互不影响,每个主题又可以分为一个或多个分区,每个分区各自存在一个记录消息数据的日志文件。

如上图所示,创建了一个demo-topic主题,配置了7个partition,对应的每个partition下存在一个[Topic-Partition]命名的消息日志文件夹,其中存在着很多类型的文件,如 .index、.timestamp、.log、.snapshot等,其中文件名一致的文件集合就称为LogSegment。
日志文件存在多种后缀文件,重点需要关注 .index、.timestamp、.log 三种类型。其他的日志类型功能作用,请查询下面图表:

每个 LogSegment 都有一个基准偏移量,用来表示当前 LogSegment 中第一条消息的 offset。偏移量是一个 64 位的长整形数,固定是20位数字,长度未达到,用 0 进行填补,索引文件和日志文件都由该作为文件名命名规则(00000000000000000000.index、00000000000000000000.timestamp、00000000000000000000.log)。特别说明一下,如果日志文件名为 00000000000000000121.log ,则当前日志文件的一条数据偏移量就是 121,偏移量是从 0 开始的。
如果想要查看日志文件中保存了kafka中的哪些消息内容,可以通过 kafka-run-class.sh (kafka-run-class.bat)脚本查看 .log :
.\bin\windows\kafka-run-class.bat kafka.tools.DumpLogSegments --files .\kafka-logs\fanghao-0\00000000000000000000.log
.\bin\windows\kafka-run-class.bat kafka.tools.DumpLogSegments --files .\kafka-logs\fanghao-0\00000000000000000000.log --print-data-log


浙公网安备 33010602011771号