https://www.cnblogs.com/hei12138/p/7805475.html
前面几张图,把kafka最重要的分区介绍的很清楚。
上面的,还有下面这两篇文章中,spring kafka的使用方法还是有些不同的
http://blog.csdn.net/isea533/article/details/73864435
https://www.cnblogs.com/xiaojf/p/6613559.html
2020.5.17 kafka用offset来记录一个分区中的一条唯一消息。 已经消费的位置,存储在kafka服务端(早期是zookeeper中,后来是一个topic中)
https://www.jianshu.com/p/449074d97daf
每个分区只有一个消费者,所以服务端记录offset时,是不关心消费者id的(每个topic的每个消费组,记录一个offset)。
另外,一个生产者的消息,可以指定发到哪个分区,也可以根据消息的key.hashcode决定哪个分区(没有key,可以随机或轮流)。一个消息,有两个必填参数:主题和value。选填key和分区。
zookeeper集群、kafka集群搭建
https://www.cnblogs.com/fengzhiwu/p/5942566.html
-----gaofeng---2019-1-2----
kafka是用scalar开发的,后来又转向java。所以它的版本号很奇怪。比如 kafka_2.11-0.11.0.1,前面的2.11是scalar的版本号,后面的0.11.0.1是java的版本号。它的api也有很多套(scalar的,java的,老的(原始的,高级的),新的),看着很乱。
我们使用新的java的api。
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.11.0.1</version> --这个是2017 Sep, 最新的是 2018 Nov, 2.1.0
</dependency>
2020.10.10 使用kafka_2.11-2.1.1.tgz 这个版本做个实验
2.1.1
- Released Feb 15, 2019
- Release Notes
- Source download: kafka-2.1.1-src.tgz (asc, sha512)
- Binary downloads:
- Scala 2.11 - kafka_2.11-2.1.1.tgz (asc, sha512)
- Scala 2.12 - kafka_2.12-2.1.1.tgz (asc, sha512)
1、启动zookeeper
./zookeeper-server-start.sh ../config/zookeeper.properties
2、启动kafka
./kafka-server-start.sh ../config/server.properties
3、查询topic列表
./kafka-topics.sh --zookeeper 127.0.0.1:2181 --list
4、创建topic
./kafka-topics.sh --zookeeper 127.0.0.1:2181 --create --topic test1 --partitions 1 --replication-factor 1
5、启动一个消费者消费
./kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --from-beginning --topic test1
6、创建生产者,并手工发消息
./kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic test1
输入一段文本,并回车,在消费者那边就可以看到这个消息已经被消费(打印)了
7、查看当前的offset
group + topic 定位一个offset, --dry-run 表示干跑,不执行,可以用于查看
[root@LIN-49AB0E67FE4 bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group group_test1 --topic test1 --to-current --dry-run
TOPIC PARTITION NEW-OFFSET
test1 0 1469
8、查看最新的offset
[root@LIN-49AB0E67FE4 bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group group_test --topic test1 --to-latest --dry-run
TOPIC PARTITION NEW-OFFSET
test1 0 1470
9、重置到指定的offset
[root@LIN-49AB0E67FE4 bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group group_test --topic test1 --to-offset 1460 --execut
10、重置到最早的offset
[root@LIN-49AB0E67FE4 bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group group_test --topic test1 --to-earliest --execut
11、重置到最新的offset
[root@LIN-49AB0E67FE4 bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group group_test --topic test1 --to-latest --execut
通过java代码读取消息,也可以验证这一点
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.12</artifactId>
<version>2.1.1</version>
</dependency>
package gaofeng.test1;
import java.util.Properties;
import java.util.Random;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
public class MyKafkaProducer {
public static void main(String[] args) throws InterruptedException {
Properties p = new Properties();
p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");//kafka地址,多个地址用逗号分割
p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(p);
try {
String topic = "test1";
while (true) {
String msg = "Hello," + new Random().nextInt(100);
ProducerRecord<String, String> record = new ProducerRecord<>(topic, msg);
kafkaProducer.send(record);
System.out.println("消息发送成功:" + msg);
Thread.sleep(500);
}
} finally {
kafkaProducer.close();
}
}
}
package gaofeng.test1;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
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 org.apache.kafka.common.serialization.StringDeserializer;
public class MyKafkaConsumer {
public static void main(String[] args) {
Properties p = new Properties();
p.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");//kafka地址,多个地址用逗号分割
p.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
p.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
p.put(ConsumerConfig.GROUP_ID_CONFIG, "group_test1");
p.put(ConsumerConfig.CLIENT_ID_CONFIG, "client1");
p.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "60000");//默认是6秒,这里改为60秒是为了做实验
String topic = "test1";
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(p);
try {
kafkaConsumer.subscribe(Collections.singletonList(topic));// 订阅消息
while (true) {
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(5));
System.out.println("recv:" + records.count());
for (ConsumerRecord<String, String> record : records) {
System.out.println(String.format("topic:%s,offset:%d,消息:%s", //
record.topic(), record.offset(), record.value()));
}
}
}finally {
kafkaConsumer.close();//会自动调用 kafkaConsumer.commitSync();
}
}
}
这里把自动提交周期改为60秒,是为了验证周期没到,提前结束java程序,下次再启动,会重复消费这个消息。
上的finally是没有用的,因为直接结束java虚拟机。
另外,如果调用了kafkaConsumer.close(),也会自动提交,不用等到60秒的周期。
公司系统出现重复消费问题,是因为底层的close函数调用失败,没有进行自动提交导致的。
还验证了group1消费后没有提交,和group2没有关系,他们有独立的offset.