Consumer API


Kafka官网文档给了基本格式

http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/clients/consumer/KafkaConsumer.html

JavaAPI 模板

自动提交offset

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());
}

手动提交offset

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "false");
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

在这之前需要明白一点,自动提交是有可能造成重复消费的

比如我们设置的props.put("auto.commit.interval.ms", "1000");——提交offset值的时间间隔为1s

现在有这么几条数据等待消费


157 hello offset



287 hello world

295 abc test 900ms

351 hello abc 1000ms


157 hello offset 为这一次提交offset值的起点,351 hello abc 为提交offset值的重点

295 abc test 是到900ms的时候提交的offset,如果在此时发生了宕机,重新开始就会从157 hello offset再次进行消费,就会造成重复消费的情况

package cn.itcast.kafka.demo2;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.Arrays;
import java.util.Properties; public class MyConsumer {
public static void main(String[] args) {
Properties props = new Properties();
//指定Kafka服务器地址
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
//指定消费者组的名字
props.put("group.id", "testGroup");
//允许程序自动提交offset,保存到kafka当中的一个topic中去
props.put("enable.auto.commit", "true");
//每隔多长时间提交一次offset的值
props.put("auto.commit.interval.ms", "1000");
//数据key和value的序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//定义KafkaConsumer
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
//订阅topic:test,并消费其中的数据
consumer.subscribe(Arrays.asList("test"));
//死循环拉取数据
while (true) {
//所有拉取到的数据都封装在了ConsumerRecords
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
long offset = record.offset();
String key = record.key();
System.out.printf("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
} }
}
}

自定义 手动提交offset
package cn.itcast.kafka.demo2;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties; public class ManualOffsetCommit {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
props.put("group.id", "testGroup2");
//关闭自动提交offset值,改为手动提交
props.put("enable.auto.commit", "false");
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("test"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
long offset = record.offset();
String key = record.key();
System.out.printf("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
}
// ConsumerRecords 里面的数据消费完后,需要提交offset值
// 使用异步提交的方法,不会阻塞程序的消费
// consumer.commitSync();
// 同步提交
consumer.commitSync();
}
}
}

消费完每个分区后手动提交offset
package cn.itcast.kafka.demo2;

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.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition; import java.util.*; public class CommitPartition {
public static void main(String[] args) {
Properties props = new Properties();
//指定kafka服务器地址
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
//指定消费者组的名字
props.put("group.id", "testGroup4");
//关闭自动提交offset值,改为手动提交
props.put("enable.auto.commit", "false");
//数据key和value的序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//定义kafkaConsumer
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props);
//订阅topic:test 并消费其中的数据
kafkaConsumer.subscribe(Arrays.asList("test"));
//调用poll方法,获取所有的数据,包含各个分区的数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(3000);
//获取topic中所有分区
Set<TopicPartition> partitions = consumerRecords.partitions();
//循环消费数据
for (TopicPartition topicPartition : partitions) {
//获取一个分区立面的所有数据
List<ConsumerRecord<String, String>> records = consumerRecords.records(topicPartition);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
String key = record.key();
long offset = record.offset();
System.out.println("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
}
//提交partition的offset值
//Map<TopicPartition, OffsetAndMetadata> offsets //获取分区里面最后一条数据的offset值
long offset = records.get(records.size() - 1).offset();
Map<TopicPartition, OffsetAndMetadata> topicPartitionOffsetAndMetadataMap = Collections.singletonMap(topicPartition, new OffsetAndMetadata(offset)); //处理完成一个分区里面的数据后提交offset
kafkaConsumer.commitSync(topicPartitionOffsetAndMetadataMap);
} }
}

消费指定分区数据
package cn.itcast.kafka.demo2;

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.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition; import java.util.*; /**
* 消费指定分区
*/
public class ConsumerMyPartition {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
//指定消费者组的名字
props.put("group.id", "testGroup4");
//关闭自动提交offset值,改为手动提交
props.put("enable.auto.commit", "false");
//数据key和value的序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//定义kafkaComsumer
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props); //Collection<TopicPartition> partitions
//创建一个集合 泛型为TopicPartition
TopicPartition topicPartition = new TopicPartition("test", 0);
TopicPartition topicPartition1 = new TopicPartition("test", 1); List<TopicPartition> topicPartitions = Arrays.asList(topicPartition, topicPartition1); //通过assign方法注册消费topic:test中的某些分区
kafkaConsumer.assign(topicPartitions); while (true) {
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(3000);
//获取所有分区
Set<TopicPartition> partitions = consumerRecords.partitions();
for (TopicPartition topicPartition2 : partitions) {
//获取一个分区中的所有数据
List<ConsumerRecord<String, String>> records = consumerRecords.records(topicPartition2);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
String key = record.key();
long offset = record.offset();
System.out.println("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
}
long offset = records.get(records.size() - 1).offset();
kafkaConsumer.commitSync(Collections.singletonMap(topicPartition2, new OffsetAndMetadata(offset))); }
}
}
}

重复消费和数据丢失



以上图为例,Consumer需要将数据写入到Hbase后,再提交offset值。那么就可以有四种上传情况的发生:

一、写入Hbase成功,提交offset成功 —— 这就是正常的消费情况

二、写入Hbase失败,提交offset失败 —— 不会有什么影响,继续进行消费即可

三、写入Hbase成功,但是offset提交失败 —— 这就会造成重复消费

四、写入Hbase失败,但是offset提交成功 —— 这样就会造成数据丢失



Kafka一共有三种消费模型:

exactly once —— 没有出错

at least once —— 重复消费

at most once —— 数据丢失

出现后两种模型的原因一般是offset没有管理好

实际工作中大多数公司的解决办法是将offset的值保存到redis或者hbase当中



数据消费存在高阶API (High Level API)低阶API (High Level API)

高阶API是将offset值默认保存在zk中,早期的Kafka一般默认使用高阶API。

低阶API就是将offset值保存在kafka自带的一个topic种

【Kafka】Consumer API的更多相关文章

  1. 【Kafka】Producer API

    Producer API Kafka官网文档给了基本格式 地址:http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/cli ...

  2. 【Kafka】Consumer配置

    从0.9.0.0开始,下面是消费者的配置. 名称 描述 类型 默认值 bootstrap.servers 消费者初始连接kafka集群时的地址列表.不管这边配置的什么地址,消费者会使用所有的kafka ...

  3. 【Kafka】Stream API

    Stream API Kafka官方文档给了基本格式 http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/streams/ ...

  4. 【Kafka】JavaAPI操作

    目录 先创建Maven工程导入jar包 Producer API Consumer API Stream API 先创建Maven工程导入jar包 帮助文档地址:http://kafka.apache ...

  5. 【译】Android API 规范

    [译]Android API 规范 译者按: 修改R代码遇到Lint tool的报错,搜到了这篇文档,aosp仓库地址:Android API Guidelines. 58e9b5f Project ...

  6. 【kafka】Java连接出现Connection refused: no further information的解决方法

    在Linux机器(ip:10.102.16.203)安装完kafka(参考:kafka的安装及使用),在windows上使用Java接口访问服务时(参考:Java实现Kafka的生产者.消费者),报异 ...

  7. 【Kafka】《Kafka权威指南》——分区partition

    在上篇的例子里([Kafka]<Kafka权威指南>--写数据), ProducerRecord 对象包含了目标主题.键和值. Kafka 的消息是 一个个 键值对, ProducerRe ...

  8. 【Kafka】数据分区策略

    数据分区策略 四种策略 一.指定分区号,数据会直接发送到所指定的分区 二.没有指定分区号,指定了数据的key,可以通过key获取hashCode决定数据发送到哪个分区 三.都没有指定的话,会采取rou ...

  9. 【Kafka】Kafka-分区数-备份数-如何设置-怎么确定-怎么修改

    Kafka-分区数-备份数-如何设置-怎么确定-怎么修改 kafka partition 数量 更新_百度搜索 kafka重新分配partition - - CSDN博客 如何为Kafka集群选择合适 ...

随机推荐

  1. C - Battle City BFS+优先队列

    Many of us had played the game "Battle city" in our childhood, and some people (like me) e ...

  2. Java前台传值至后台中文乱码

    记一次常见问题 今天导入了一个网上下载的项目,运行后发现,前台传值 到Servlet,Servlet保存至数据库,数据库保存的中文数据出现乱码,检查了一下表中的编码是utf8没错. 输出测试了一下 原 ...

  3. [php] phpStudy+XDebug配置

    一.配置前说明: 1.phpStudy集成了XDebug扩展,所以不用单独下载XDebug. 2.打开XDebug扩展:其它选项菜单 > PHP扩展 > Xdebug 二.配置步骤: ph ...

  4. 《剑指Offer》- 连续子数组的最大和或最小和

    前言 本文是<剑指Offer>系列(JavaScript版)的第一篇,题目是"连续子数组的最大和或最小和". 话不多说,开始"打怪"修炼... 一. ...

  5. 2019-2020-1 20199325《Linux内核原理与分析》第五周作业

    第五周作业主要是选择一个系统调用(13号系统调用time除外),使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用,在实验楼Linux虚拟机环境下完成实验. 系统调用的列表参见 http ...

  6. (一)PL/SQL简介

    PL/SQL PL/SQL也是一种程序语言,叫做过程化SQL语言(Procedural Language/SQL).PL/SQL是Oracle数据库对SQL语句的扩展.在普通SQL语句的使用上增加了编 ...

  7. 数据结构入门第二课(浙大mooc)

    数据结构入门第二课 目录 数据结构入门第二课 引子 多项式的表示 方法1 顺序结构表示多项式各项 方法2 顺序结构表示非零项 方法3 链表结构存储非零项 多项式问题的启示 线性表 线性表的抽象数据类型 ...

  8. 数据挖掘入门系列教程(十一点五)之CNN网络介绍

    在前面的两篇博客中,我们介绍了DNN(深度神经网络)并使用keras实现了一个简单的DNN.在这篇博客中将介绍CNN(卷积神经网络),然后在下一篇博客中将使用keras构建一个简单的CNN,对cifa ...

  9. 基于docker-compose部署LNMP

    一.配置环境 [root@docker ~]# systemctl stop firewalld[root@docker ~]# iptables -F[root@docker ~]# setenfo ...

  10. javescrip内嵌样式与外联样式怎么做?

    对于前端初学者,个人JS样式常用的有两种:内嵌样式 ,外联样式:下面通过一个简单的鼠标点击出现设定的验证数字为例进行演示: 先看下效果: 鼠标点击前效果: 鼠标点击后效果: 图中的这个ojbk是我js ...