备注:这是我的个人学习笔记,仅供个人学习。
文章目录
Kafka消息队列应用场景
传统的消息队列主要应用场景包括:缓冲/消峰,异步通信,解耦
缓冲/消峰:解决生产消息和消费消息的处理速度不一致问题
解耦:允许独立扩展或者修改两边的处理过程
异步通信:把消息放入一个消息队列中,并不立刻处理,再需要的时候再去处理
消息队列的两种模式
点对点模式:
发布、订阅模式:
Kafka基础架构
我有100T的数据,一个服务器存不下,我就可以分开几个分区,分别存储。broker代表服务器,也就是hadoop01,Hadoop02。每个服务器中会有不同的主题,一个主题会有多个分区,每个分区的数据只能有一个消费者进行消费。Topic可以理解为一个队列,消息在排着队等待消费。同时每个分区也有副本,当Leader挂了以后,Follower会成为新的Leader。无论是生产者发送消息的对象,或者消费者消费消息的对象都是Leader。
Kafka启动
先开Zookeeper,再开Kafka。关闭也是先关Zookeeper,再关Kafka。
[root@hadoop01 kafka_2.12-3.0.0]# bin/kafka-server-start.sh -daemon config/server.properties
Kafka命令的使用
创建主题
bin/kafka-topics.sh --bootstrap-server hadoop01:9092 --topic first --create --partitions 1 -replication-factor 3
# --bootstrap-server 连接kafka的主机名和端口号,默认9092
# --topic 操作topic的名称
# --create 创建
# --partitions 分区数量
# --replication-factor 副本数量
查看主题
bin/kafka-topics.sh --bootstrap-server hadoop01:9092 --topic first --describe
修改分区数
bin/kafka-topics.sh --bootstrap-server hadoop01:9092 --topic first --alter --partitions 3
查看分区
需要注意的是!
分区数只能增加不能减少
修改副本
kafka不支持在命令行中修改副本的数量
生产者
启动生产者
bin/kafka-console-producer.sh --bootstrap-server hadoop01:9092 --topic first
启动消费者
bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic first
在生产者中发送消息,消费者消费消息
让消费者重新消费消息,从开始处
bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic first --from-beginning
生产者发送原理
首先,生产者创建一个main线程,通过send方法,发送数据,经过拦截器,然后序列化数据,因为要发到不同的节点上所以需要序列化,再到分区器,分区器中有一个缓冲队列,默认是32M,每一个批次(batch)默认16k。当batch.size或者linger.ms达到设定的时候就会发送数据。发送数据之后kafka集群会进行一个acks的应答。
0:消息发送完成,不用等落盘,就应答
1:消息发送给Leader,Leader落盘再应答
-1/all :等Leader和所有副本都落盘再应答
linger.ms 默认 0ms
batch.size 默认 16k
KafkaAPI
添加依赖
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
生产者
异步发送
在这里插入代码片public class Producer {
public static void main(String[] args) {
// 配置
Properties properties = new Properties();
// 往配置中put信息(配置)
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 创建kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
// 发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String,String>("first","atguigu"+i));
}
// 关闭资源
kafkaProducer.close();
}
}
运行程序,并且运行消费者
bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic first
回调 异步发送
其实就是在发送数据的地方new 一个 callback
callback返回主题,分区等信息
public class Producer {
public static void main(String[] args) {
// 配置
Properties properties = new Properties();
// 往配置中put信息(配置)
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 创建kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
// 发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first", "atguigu" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e == null){
System.out.println("主题:"+recordMetadata.topic()+" 分区:"+recordMetadata.partition());
}
}
});
}
// 关闭资源
kafkaProducer.close();
}
}
运行结果
同步发送
其实就是在发送数据的地方,加一个 .get()
发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first", "atguigu" + i)).get();
}
异步发送和同步发送的区别:
异步发送就是可以不用等缓冲队列中的数据都发送完成再发送。
同步发送,要等缓冲队列中的数据都发送完成后再发送。
分区
kafka默认分区
- 如果指名partition的情况下,直接将值作为partition值
- 如果没有指明partition,但是又key,会对key的hash值与分区数取余,得到partition值
例如key1的hash值为5,topic有2个分区,5%2,余1,所以存入1号分区
- 既没有partition值又没有key的值,kafka采用粘性分区器,随机选择一个分区,当该分区满了,或者达到batch值或者linger.ms设定的值的时候,kafka会再随机选择除了上一个之外的分区
演示:
指定partition为1
根据key的hash值进行partition分区
企业中,会有很多张表,我希望将其中的 订单表 发送到kafka的某一个分区,我们只需要将 订单表的表名作为key然后取hash值,这个时候同一张表的数据就会发到同一个分区中.
不指定分区,随机
自定义分区器
继承接口Partitioner,重写方法
@param topic 主题
@param key 消息的 key
@param keyBytes 消息的 key 序列化后的字节数组
@param value 消息的 value
@param valueBytes 消息的 value 序列化后的字节数组
@param cluster 集群元数据可以查看分区信息
public class PartitionTest implements Partitioner {
@Override
public int partition(String s, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
//对value进行分区
String msgValue = value.toString();
int partition;
if (msgValue.contains("atguigu")){
partition = 0;
}else {
partition = 1;
}
return partition;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
在生产者中加入自定义分区器配置
运行,当值为hello的时候,消息发送到1号分区
当值为atguigu的时候,消息发送到0号分区
生产者提高吞吐量
public class Producer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 缓冲区大小,默认32M
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
// 批次大小,默认16k
properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
// 时间延迟,默认0ms
properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
// 压缩类型,默认none,可配置值gzip,snappy,lz4,zstd
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
}
kafkaProducer.close();
}
}
数据的可靠性
0 :数据从Producer发送给Leader后,还没有来得及消费和同步副本,Leader挂了,此时会造成数据的丢失
1 :数据从Producer发送给Leader后,落盘成功,并且给Producer应答,但是还没来得及同步副本,Leader挂了,此时也造成了数据的丢失
-1 :数据从Producer发送给Leader后,Leader同步完所有的副本,给Producer应答,这个比较可靠,也不是十分可靠,因为分区副本设置为1个,或者ISR里应答的最小副本数量(min.insync.replicas默认为1)设置为1,和ack=1的效果是一样的,仍然有丢数据的风险。
(Leader维护了一个动态的in-sync replica set(ISR),意为和
Leader保持同步的Follower+Leader集合(leader:0,isr:0,1,2)。
如果Follower长时间未向Leader发送通信请求或同步数据,则
该Follower将被踢出ISR。该时间阈值由replica.lag.time.max.ms参
数设定,默认30s。例如2超时,(leader:0, isr:0,1)。
)
数据完全可靠条件 = ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
可靠性总结:
acks=0,生产者发送过来数据就不管了,可靠性差,效率高;
acks=1,生产者发送过来数据Leader应答,可靠性中等,效率中等;
acks=-1,生产者发送过来数据Leader和ISR队列里面所有Follwer应答,可靠性高,效率低;
在生产环境中,acks=0很少使用;acks=1,一般用于传输普通日志,允许丢个别数据;acks=-1,一般用于传输和钱相关的数据,
对可靠性要求比较高的场景。
// acks
properties.put(ProducerConfig.ACKS_CONFIG,"1");
// 重试次数
properties.put(ProducerConfig.RETRIES_CONFIG,3);
如果acks = -1 ,这里的数据还是有可能出现问题,那就是数据重复的问题
Producer发送数据给Leader,Leader写入完成,并把数据同步到副本,就在给Producer应答前,Leader挂了,Follower会被选为Leader,Producer会因为没有得到应答会将数据再重新发送,此时的数据就会造成重复。
数据重复
幂等性原理
重复数据的判断标准:
具有<PID, Partition, SeqNumber>相同主键的消息提交时,Broker只会持久化一条。其
中PID是Kafka每次重启都会分配一个新的;Partition 表示分区号;Sequence Number是单调自增的。
开启参数 enable.idempotence 默认为 true,false 关闭。
事务
public class Translation {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
// 设置事务id(必须),事务id保证唯一就行
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"translation01");
// 初始化事务
kafkaProducer.initTransactions();
// 开启事务
kafkaProducer.beginTransaction();
try {
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
}
// 提交事务
kafkaProducer.commitTransaction();
}catch (Exception e){
// 终止事务
kafkaProducer.abortTransaction();
}finally {
kafkaProducer.close();
}
}
}
数据有序
怎么保证数据有序,有两种
- 未开启幂等性
max.in.flight.requests.per.connection
需要设置为1。
- 开启幂等性
max.in.flight.requests.per.connection
需要设置小于等于5。
因为,启用幂等性后,kafka服务端会缓存producer发来的最近5个request的元数据,
会根据<pid,partition,SeqNumber>来确定数据是否重复,那传过来的数据,根据序列化号进行一个排序,无论如何最近5个数据都是有序的。
Broker
zookeeper存储的Kafka信息
Broker的工作流程
-
启动kafka服务后,每个broker中都会有Controller,然后再zookeeper中进行注册,启动完成就去注册
-
看controller谁先注册的,就谁说了算,谁就辅助选举Leader
-
选举处理的Controller监听Brokers的节点变化
-
controller决定Leader选举,选举规则就是保证isr存活为前提,按照AR中的顺序
-
controller将节点信息上传到zookeeper
-
其他的controller从zookeeper中同步相关信息,为了防止controller Leader挂了,挂了后其他controller能够上位
-
生产者往Leader中发送数据,然后同步数据到Follower
-
当Leader挂了,controller Leader就会监听到节点的变化
-
获取ISR
-
选取新的leader(参照第4点)
-
更新Leader和ISR
Kafka副本
副本的作用:提高可靠性
Kafka副本默认1个
Kafka中副本分为Leader和Follow,Kafka生产者只会把数据发往Leader,然后Follower找Leader进行同步数据。
副本的统称AR
AR=ISR+OSR
ISR存活的副本,OSR延迟过多的副本
Leader的选举流程
- 启动Kafka服务后,broker向ZK进行注册
- 看那个broker先注册,那个controller就是controller Leader,辅助Leader的选举
- controller监听brokers的节点变化
- controller进行Leader的选举,选举规则就是按照AR顺序进行上位
- controller将节点信息上传到ZK
- 其他的controller从ZK同步相关的信息
Leader和Follower故障处理细节
LEO:每个副本最新的offset+1
HW:所有副本最小的LEO
-
Follower故障
Follower发生了故障,首先将Follower从ISR中移除
这个期间Leader和其他Follower继续接受数据
等Follower恢复后,Follower会读取本地的磁盘记录,上次的HW,并将log文件高于HW的部分删除,从HW处向Leader进行同步
等Folower的LEO大于等于该Partition的HW,即Follower追上Leader之后,重新加入ISR
-
Leader故障
Leader发生了故障,会从ISR中选出一个新的Leader
为保证多个副本之间的数据一致性,其余的Follower会先将各自的log文件高于HW的部分截掉,然后从新的Leader同步数据
这里只能保证数据的一致性,并不能保证数据不丢失或者不重复。
因为上一个Leader已经处理了数据的5,6,7了,新的Leader还没有处理,所以同步过后,会造成5,6,7的丢失
手动调整分区副本
创建新主题,名称为three(4个分区,2个副本)
bin/kafka-topics.sh --bootstrap-server
hadoop102:9092 --create --partitions 4 --replication-factor 2 --
topic three
查看分区情况
创建副本存储计划
vim increase-replication-factor.json
输入以下内容
{
"version":1,
"partitions":[{"topic":"three","partition":0,"replicas":[0,1]},
{"topic":"three","partition":1,"replicas":[0,1]},
{"topic":"three","partition":2,"replicas":[1,0]},
{"topic":"three","partition":3,"replicas":[1,0]}]
}
执行副本计划
bin/kafka-reassign-partitions.sh --
bootstrap-server hadoop102:9092 --reassignment-json-file
increase-replication-factor.json --execute
验证副本计划
bin/kafka-reassign-partitions.sh --
bootstrap-server hadoop102:9092 --reassignment-json-file
increase-replication-factor.json --verify
增加副本
创建topic(3个分区,一个副本)
bin/kafka-topics.sh --bootstrap-server
hadoop102:9092 --create --partitions 3 --replication-factor 1 --
topic four
只能手动增加副本,不能通过命令行的方式进行操作
手动增加副本
vim increase-replication-factor.json
{"version":1,
"partitions":[
{"topic":"four","partition":0,"replicas":[0,1,2]},
{"topic":"four","partition":1,"replicas":[0,1,2]},
{"topic":"four","partition":2,"replicas":[0,1,2]}
]}
文件存储
一个Topic下有多个Partitions,一个partition有一个log文件,这个只是一个虚拟的,log文件下又存储着Segment,每个Segment 有1G的数据。
index和log文件以当前segment的第一条消息的offset命名。所以他的有序的
index文件和log文件
注意:
-
index为稀疏索引,大约每往log文件写入4kb数据,就会往index文件写入一条索引。(参数log.index.interval.bytes默认4kb。
) -
Index文件中保存的offset为相对offset,这样能确保offset的值所占空间不会过大,
因此能将offset的值控制在固定大小
文件清理策略
Kafka 中默认的日志保存时间为 7 天,可以通过调整如下参数修改保存时间。
⚫ log.retention.hours,最低优先级小时,默认 7 天。
⚫ log.retention.minutes,分钟。
⚫ log.retention.ms,最高优先级毫秒。
⚫ log.retention.check.interval.ms,负责设置检查周期,默认 5 分钟。
超过了时间,提供了两种日志清理策略 delete 和 compact
-
delte
⚫ log.cleanup.policy = delete 所有数据启用删除策略
基于时间,默认打开,以segment中最大时间戳作为该文件的时间戳
基于大小,默认关闭,超过设置的大小,删除最早的(默认是-1,表示无穷大) -
compact
对于相同key的不同value值,只保留最后一个版本。
⚫ log.cleanup.policy = compact 所有数据启用压缩策略
压缩后的数据offset是不连续的。
适合使用在消息的key是用户ID,value是用户的资料,这样就保留了最新的用户资料了。
高效读写数据(重要!!!)
1)Kafka 本身是分布式集群,可以采用分区技术,并行度高
2)读数据采用稀疏索引,可以快速定位要消费的数据
3)顺序写磁盘,Kafka的生产者生产数据,要写入log文件中,写的过程一直都是追加到文件末端,魏顺序写(能达到100M/S)
4)页缓冲+零拷贝技术,Kafka重度依赖底层操作系统提供的PageCache功能。当上层有写操作时,操作系统只是将数据写入PageCache。当读操作发生时,先从PageCache中查找,如果找不到,再去磁盘中读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用
Kafka消费者
Kakfa消费方式
kafka采用的消费方式是pull(拉取)方式。consumer从broker中主动拉取数据。
这个模式的不足之处在于,如果Kafka中没有数据,消费者可能会陷入循环中,一直返回空数据。
消费者工作流程
生产者向broker发送数据,一个消费者可以消费多个分区的数据,但是每个分区只能由一个消费者组里的一个消费者消费。每个消费者的offset有消费者提交到系统主题保存
消费者组原理
一个消费者组中的每个消费者只能消费一个分区的数据,最多一个分区一个消费者,多出的消费者就会闲置。
消费者组之间互不影响。
消费者组初始化流程(重要!!!)
coordinator:辅助实现消费者组的初始化和分区的分配。
coordinator节点选择 = groupid的hashcode值 % 50( __consumer_offsets的分区数量)
例如: groupid的hashcode值 = 1,1% 50 = 1,那么__consumer_offsets 主题的1号分区,在哪个broker上,就选择这个节点的coordinator
作为这个消费者组的老大。消费者组下的所有的消费者提交offset的时候就往这个分区去提交offset。
重要:
面试题:
什么时候会触发再平衡机制?
答:
每个消费者都会和coordinator保持心跳(默认3s),一旦超时(session.timeout.ms=45s),该消费者会被移除,并触发再平衡;
或者消费者处理消息的时间过长(max.poll.interval.ms5分钟),也会触发再平衡
消费者API操作
消费者消费某一个主题
public class Consumer {
public static void main(String[] args) {
Properties properties = new Properties();
// 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
// 对Key Value进行反序列化操作
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 重要!!!,一定要设置一个groupID
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
ArrayList<String> topic = new ArrayList<>();
topic.add("first");
kafkaConsumer.subscribe(topic);
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
启动生产者,往里面发送数据,运行效果
消费某一个分区
public class Consumer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
KafkaConsumer kafkaConsumer = new KafkaConsumer<String,String>(properties);
ArrayList<Object> partitions = new ArrayList<>();
// 创建一个TopicPartition
partitions.add(new TopicPartition("first",0));
// 分区就使用assign
kafkaConsumer.assign(partitions);
while (true){
ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(3));
for (Object consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
往分区0中发送数据
//生产者的代码省略,这里是重要代码
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first",0,"","atguigu"+i));
}
消费者运行截图
Range以及再平衡
创建一个7个分区的topic
启动消费者
确保他们在同一个消费者组里
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
启动生产者,让他们往不同的分区发送数据
for (int i = 0; i < 500; i++) {
kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
Thread.sleep(1);
}
当我挂了一个消费者后(45 S )
可以看到5,6号分区的数据,分在了一个消费者中,
所以
消费者1 消费3,4,5,6
消费者2 消费0,1,2
所以容易造成数据倾斜
以下就是Range的策略
RoundRobin分区策略
// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFI
G, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
也就是轮询的办法
Sticky分区策略
粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前,
考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销
首先会尽量均衡随机的放置分区
到消费者上面,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分
区不变化。
// 修改分区分配策略
ArrayList<String> startegys = new ArrayList<>();
startegys.add("org.apache.kafka.clients.consumer.StickyAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, startegys);
offset位移
offset的默认维护位置
0.9版本之前offset维护在Zookeeper之前,0.9版本之后维护在系统主题当中(__consumer__offsets)
自动提交offset
⚫ enable.auto.commit:是否开启自动提交offset功能,默认是true
⚫ auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s
// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
// 提交 offset 的时间周期 1000ms,默认 5s
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
手动提交offset
- 同步提交:等待offset提交完毕后,再去消费下一批数据
- 异步提交:发送完提交offset请求后,不用等提交完毕,就开始消费下一批数据
// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
// 同步提交 offset
consumer.commitSync();
// 异步提交 offset
consumer.commitAsync();
指定offset消费
auto.offset.reset = earliest | latest | none 默认是 latest。
(1)earliest:自动将偏移量重置为最早的偏移量,–from-beginning。
(2)latest(默认值):自动将偏移量重置为最新偏移量。
(3)none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。
public class Consumer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
KafkaConsumer kafkaConsumer = new KafkaConsumer<String,String>(properties);
ArrayList<String> topic = new ArrayList<>();
topic.add("first");
kafkaConsumer.subscribe(topic);
Set<TopicPartition> assignment = kafkaConsumer.assignment();
while (assignment.size() == 0) {
kafkaConsumer.poll(Duration.ofSeconds(1));
// 获取消费者分区分配信息(有了分区分配信息才能开始消费)
assignment = kafkaConsumer.assignment();
}
// 指定消费的offset
for (TopicPartition partition : assignment) {
kafkaConsumer.seek(partition,100);
}
while (true){
ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(3));
for (Object consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
指定时间消费
public class Consumer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092,hadoop02:9092");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
KafkaConsumer kafkaConsumer = new KafkaConsumer<String,String>(properties);
ArrayList<String> topic = new ArrayList<>();
topic.add("first");
kafkaConsumer.subscribe(topic);
Set<TopicPartition> assignment = kafkaConsumer.assignment();
while (assignment.size() ==0){
kafkaConsumer.poll(Duration.ofSeconds(1));
// 获取消费者分区分配信息(有了分区分配信息才能开始消费)
assignment = kafkaConsumer.assignment();
}
// 把时间转换为offset
HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
// 封装集合存储,每个分区对应一天前的数据
for (TopicPartition topicPartition : assignment) {
topicPartitionLongHashMap.put(topicPartition,System.currentTimeMillis()-1*24*3600*1000);
}
// 获取从 1 天前开始消费的每个分区的 offset
Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
// 遍历每个分区,对每个分区设置消费时间。
for (TopicPartition partition : assignment) {
OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(partition);
kafkaConsumer.seek(partition,offsetAndTimestamp.offset());
}
while (true){
ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(3));
for (Object consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}